diff --git a/.gitignore b/.gitignore index 801ede80..126bdb12 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,15 @@ target # Deployment files (user-specific) scripts/deployments.json -.env \ No newline at end of file +.env + +# Claude Code +.claude/ + +# TLDR tool +.tldr/ +.tldrignore + +# Test artifacts +leafs/testMerkl.json +leafs/testMerkl.log \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..c80adc06 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +scarb 2.13.1 diff --git a/README.md b/README.md index 238a983e..e62ffb45 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,133 @@ scarb build snforge test ``` +## Get Started as a Vault Builder + +This guide walks you through deploying and configuring vaults as a builder, covering both custodial and non-custodial approaches. + +### Step 1: Deploy Your Vault + +Use the deployment script to create your vault with the desired configuration: + +```bash +cd scripts +npm run deploy:vault +``` + +The script (`scripts/deployVault.ts`) will prompt you for: +- **Vault Details**: Name, symbol, underlying asset address +- **Fee Configuration**: Management, performance, and redemption fees +- **Operational Parameters**: Report delay, max delta percentage, fees recipient +- **Vault Type**: Choose between custodial or non-custodial + +#### Custodial vs Non-Custodial Vaults + +**Custodial Vaults:** +- Use an existing, pre-deployed VaultAllocator +- Suitable when you trust a third-party allocator +- Faster deployment (vault + redeem request only) +- Limited control over fund allocation strategies + +**Non-Custodial Vaults:** +- Deploy a new VaultAllocator and Manager specific to your vault +- Full control over fund allocation and strategy management +- Requires additional setup for Merkle tree verification system +- Higher security and customization capabilities + +### Step 2: Non-Custodial Setup (Advanced Fund Management) + +For non-custodial vaults, you need to set up the Merkle tree verification system for secure fund allocation: + +#### 2.1 Configure Allocation Strategies + +The VaultAllocator (`packages/vault_allocator`) provides secure fund allocation through: +- **Manager Contract**: Merkle proof-based call verification +- **Decoders & Sanitizers**: Pre-built integrations for protocols (AVNU, Vesu, ERC-4626) +- **Merkle Tree Verification**: Whitelist system for allowed operations + +#### 2.2 Generate Merkle Tree Configuration + +Run the merkle tree generation script to create your allocation whitelist: + +```bash +./export_merkle.sh [config_name] +``` + +This script: +1. Executes test scenarios to generate valid operation leafs +2. Creates a Merkle tree with allowed operations +3. Outputs a JSON configuration file in `leafs/[config_name].json` + +The generated file contains: +- **Metadata**: Vault, allocator, and manager addresses +- **Leafs**: Whitelisted operations with their proofs +- **Tree**: Complete Merkle tree structure + +#### 2.3 Set Management Root + +Configure the Manager contract with your Merkle root: + +```typescript +// Using the deployed manager address from step 1 +const manager = new Contract(managerAbi, managerAddress, account); +await manager.set_manage_root(vaultAddress, merkleRoot); +``` + +### Step 3: Off-Chain Integration with Curator SDK + +Use the Curator SDK (`sdk/src/curator/index.ts`) to generate secure calldata for vault operations: + +#### 3.1 Initialize the SDK + +```typescript +import { VaultCuratorSDK } from './sdk/src/curator'; + +// Load configuration from generated merkle file +const curator = VaultCuratorSDK.fromFile('./leafs/your_config.json'); +``` + +#### 3.2 Generate Operation Calldata + +The SDK provides helper methods for common operations: + +```typescript +// Bring liquidity to vault +const calls = curator.bringLiquidityHelper(true, amount); // true = with approval + +// Multi-step operations (approve + deposit) +const calls = curator.depositHelper({ + target: vaultAddress, + assets: depositAmount, + receiver: userAddress, + withApproval: true +}); + +// Advanced DeFi operations +const calls = curator.multiRouteSwapHelper(swapParams, { withApproval: true }); +const calls = curator.ModifyPositionV1Helper(vesuParams, approvalParams); +``` + +#### 3.3 Execute Operations + +```typescript +// Execute the generated calls +const response = await account.execute(calls); +``` + +### Key Benefits + +- **Security**: Merkle proof verification ensures only whitelisted operations +- **Flexibility**: Support for multiple DeFi protocols (AVNU, Vesu, ERC-4626) +- **Efficiency**: Batch operations with automatic approval handling +- **Auditability**: All operations are pre-defined and verifiable + +### Next Steps + +1. **Monitor**: Use the backend services for vault monitoring and analytics +2. **Optimize**: Adjust strategies based on performance metrics +3. **Scale**: Deploy multiple vaults with different strategies +4. **Integrate**: Use the SDK in your applications for seamless vault management + ### Scripts & Backend For deployment scripts and configuration utilities, see [scripts/README.md](scripts/README.md). diff --git a/Scarb.lock b/Scarb.lock index b67ee2d0..5e188316 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,16 +1,37 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "alexandria_bytes" +version = "0.6.1" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:b1a402330fe5356ca0a4f24d2b8dd1fb0a7e2568bf35c349319cadb53f0ed6fd" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.6.1" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:f011951982ca26ce64c212ca5aa739bd16461dae96a4fb9a8f8e360b1c5e7b94" + [[package]] name = "alexandria_math" -version = "0.6.0" +version = "0.6.1" source = "registry+https://scarbs.xyz/" -checksum = "sha256:1e08ebba0ed9f7217b8efc283d2ad41730257cf41a47ca88a94fb0fafad22e9e" +checksum = "sha256:19160f0993a6643e8e71a3bce03e54a37f26b8cad94f21668d7cc9afd08d2047" + +[[package]] +name = "ekubo" +version = "0.1.0" +source = "git+https://github.com/ekuboprotocol/starknet-contracts#cf2e95fd124bfb19491e1a7da9ffba4b63cc6c4e" [[package]] name = "openzeppelin" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_access", "openzeppelin_account", @@ -28,8 +49,8 @@ dependencies = [ [[package]] name = "openzeppelin_access" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_interfaces", "openzeppelin_introspection", @@ -37,8 +58,8 @@ dependencies = [ [[package]] name = "openzeppelin_account" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_interfaces", "openzeppelin_introspection", @@ -47,8 +68,8 @@ dependencies = [ [[package]] name = "openzeppelin_finance" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_access", "openzeppelin_interfaces", @@ -57,11 +78,10 @@ dependencies = [ [[package]] name = "openzeppelin_governance" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_access", - "openzeppelin_account", "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_token", @@ -70,26 +90,26 @@ dependencies = [ [[package]] name = "openzeppelin_interfaces" -version = "2.1.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "2.1.0-alpha.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" [[package]] name = "openzeppelin_introspection" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_interfaces", ] [[package]] name = "openzeppelin_merkle_tree" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" [[package]] name = "openzeppelin_presets" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_access", "openzeppelin_account", @@ -103,19 +123,18 @@ dependencies = [ [[package]] name = "openzeppelin_security" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_interfaces", ] [[package]] name = "openzeppelin_token" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_access", - "openzeppelin_account", "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_utils", @@ -123,28 +142,28 @@ dependencies = [ [[package]] name = "openzeppelin_upgrades" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "3.0.0-alpha.3" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" [[package]] name = "openzeppelin_utils" -version = "3.0.0-alpha.1" -source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +version = "2.1.0-alpha.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#1e5a44ec50d7625395ca8322eec971ce8abb5bc1" dependencies = [ "openzeppelin_interfaces", ] [[package]] name = "snforge_scarb_plugin" -version = "0.48.0" +version = "0.48.1" source = "registry+https://scarbs.xyz/" -checksum = "sha256:8630dcf3fa4df36a3d45e6a2d053cf84c548ab154e829fece99373ae5852921c" +checksum = "sha256:2dd27e8215eea8785b3930e9f452e11b429ca262b1c1fbb071bfc173b9ebc125" [[package]] name = "snforge_std" -version = "0.48.0" +version = "0.48.1" source = "registry+https://scarbs.xyz/" -checksum = "sha256:981139c83359089540652c44f4a1a888c77409eedaa148377927cc63a604b67b" +checksum = "sha256:89f759fa685d48ed0ba7152d2ac2eb168da08dfa8b84b2bee96e203dc5b2413e" dependencies = [ "snforge_scarb_plugin", ] @@ -172,7 +191,9 @@ dependencies = [ name = "vault_allocator" version = "0.1.0" dependencies = [ + "alexandria_bytes", "alexandria_math", + "ekubo", "openzeppelin", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index b4943bfe..360c83a2 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -14,8 +14,8 @@ edition.workspace = true [workspace.package] version = "0.1.0" edition = "2024_07" -cairo-version = "2.12.0" -scarb-version = "2.12.0" +cairo-version = "2.14.0" +scarb-version = "2.14.0" authors = ["ForgeYields "] description = "Standard vault infrastructure for Starknet" documentation = "" @@ -27,10 +27,12 @@ keywords = [ ] [workspace.dependencies] -starknet = "2.12.0" +starknet = "2.14.0" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts" } -snforge_std = "0.48.0" +snforge_std = "0.48.1" alexandria_math = "0.6.0" +alexandria_bytes = "0.6.0" +ekubo = { git = "https://github.com/ekuboprotocol/starknet-contracts" } [dependencies] diff --git a/export_merkle.py b/export_merkle.py new file mode 100644 index 00000000..cfc6e9e4 --- /dev/null +++ b/export_merkle.py @@ -0,0 +1,56 @@ +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})") diff --git a/export_merkle.sh b/export_merkle.sh index 138eb1c7..8b4989a3 100755 --- a/export_merkle.sh +++ b/export_merkle.sh @@ -3,7 +3,7 @@ set -euo pipefail # --- Config --- PKG="vault_allocator" -TEST="vault_allocator::test::creator::test_creator" +TEST="vault_allocator::test::creator::creator_sdk_test::test_creator" OUT_DIR="leafs" # Nom du fichier = premier argument (sinon "merkle") diff --git a/export_merkle_ts.sh b/export_merkle_ts.sh new file mode 100755 index 00000000..3e7a02bd --- /dev/null +++ b/export_merkle_ts.sh @@ -0,0 +1,20 @@ +#!/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 using TypeScript --- +cd scripts && pnpm exec tsx exportMerkle.ts "../$LOG_PATH" "../$OUT_PATH" diff --git a/leafs/fyWBTC.json b/leafs/fyWBTC.json new file mode 100644 index 00000000..e407bc8f --- /dev/null +++ b/leafs/fyWBTC.json @@ -0,0 +1,200 @@ +{ + "metadata": { + "vault": "", + "vault_allocator": "3258880465856233510553829905629849032717898702344145949430060583241133018680", + "manager": "", + "decoder_and_sanitizer": "", + "root": "212658268691449657621986746749331260262952449122366947685227185176526965047", + "tree_capacity": 16, + "leaf_used": 13 + }, + "leafs": [ + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "632184196056345004513825143425280990590956427993302822618957287447348818434" + ], + "description": "Approve avnu_router to spend STRK", + "leaf_index": 0, + "leaf_hash": "3164408410402617224400032578549662571532750893872500165938317175959720396399" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "632184196056345004513825143425280990590956427993302822618957287447348818434", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "3258880465856233510553829905629849032717898702344145949430060583241133018680" + ], + "description": "Multi route swap STRK for WBTC", + "leaf_index": 1, + "leaf_hash": "3114613607010838538117892849938711693577364266165250089638946958975706061700" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "632184196056345004513825143425280990590956427993302822618957287447348818434", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "3258880465856233510553829905629849032717898702344145949430060583241133018680" + ], + "description": "Multi route swap STRK for SolvBTC", + "leaf_index": 2, + "leaf_hash": "1798338001101173151783443998923089114935259976924703499176892769401881849934" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "632184196056345004513825143425280990590956427993302822618957287447348818434" + ], + "description": "Approve avnu_router to spend SolvBTC", + "leaf_index": 3, + "leaf_hash": "182741493460629321372418089386706546890536010432763507011887207434844082770" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "632184196056345004513825143425280990590956427993302822618957287447348818434", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "3258880465856233510553829905629849032717898702344145949430060583241133018680" + ], + "description": "Multi route swap SolvBTC for WBTC", + "leaf_index": 4, + "leaf_hash": "2605292298572200848180219052926124619350578512278927256491360350076978342161" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "632184196056345004513825143425280990590956427993302822618957287447348818434", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "3258880465856233510553829905629849032717898702344145949430060583241133018680" + ], + "description": "Multi route swap SolvBTC for WBTC", + "leaf_index": 5, + "leaf_hash": "2605292298572200848180219052926124619350578512278927256491360350076978342161" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "3475252094465534150446669812792040965529065299943233505330779571674053440258", + "selector": "405852601487139132244494309743039711091605094719341446212637486410648343561", + "argument_addresses": [ + "196268403159008932410419402999721616371951519129", + "970889662749748738054523748069740135214383638861" + ], + "description": "Initiate token withdraw WBTC", + "leaf_index": 6, + "leaf_hash": "2265942702980092815266072674939931034936886366689290283742389048756110272062" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2516568162210255095453483626839089014569257854319119258892841327983140950402" + ], + "description": "Approve ekubo_adapter to spend WBTC", + "leaf_index": 7, + "leaf_hash": "544495096483226268221200017943815409614707587732244626391175268599110065946" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2516568162210255095453483626839089014569257854319119258892841327983140950402" + ], + "description": "Approve ekubo_adapter to spend SolvBTC", + "leaf_index": 8, + "leaf_hash": "1114062149259863212593195361317848415283926476324427638221539960838035626860" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "1485162454000453106748438092882350352049376254989977238717084794132152066047", + "argument_addresses": [], + "description": "Deposit liquidity to Ekubofor WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 9, + "leaf_hash": "2806250769693476291528283195594704364946079577331271925846894282784089520240" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "322637753074552370500544931377150993467524337001753746958704872129235461672", + "argument_addresses": [], + "description": "Withdraw liquidity from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 10, + "leaf_hash": "2461457247526497726225381752851386584506241042129252794668080772037477418255" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "38427523166402100478120264784512528971576573049949957236189387122891476727", + "argument_addresses": [], + "description": "Collect fees from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 11, + "leaf_hash": "2399868272939267778338841054190844684504850012171049873539801608610504948729" + }, + { + "decoder_and_sanitizer": "1291450050557258653214425734797667027104808014275009832326468111778175109966", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "1154545024571392208615796453779759631244452775454593974158722847429584475265", + "argument_addresses": [], + "description": "Harvest rewards from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 12, + "leaf_hash": "1515569761750502248791694538737894540310335943791560078400578525689663319854" + } + ], + "tree": [ + [ + "3164408410402617224400032578549662571532750893872500165938317175959720396399", + "3114613607010838538117892849938711693577364266165250089638946958975706061700", + "1798338001101173151783443998923089114935259976924703499176892769401881849934", + "182741493460629321372418089386706546890536010432763507011887207434844082770", + "2605292298572200848180219052926124619350578512278927256491360350076978342161", + "2605292298572200848180219052926124619350578512278927256491360350076978342161", + "2265942702980092815266072674939931034936886366689290283742389048756110272062", + "544495096483226268221200017943815409614707587732244626391175268599110065946", + "1114062149259863212593195361317848415283926476324427638221539960838035626860", + "2806250769693476291528283195594704364946079577331271925846894282784089520240", + "2461457247526497726225381752851386584506241042129252794668080772037477418255", + "2399868272939267778338841054190844684504850012171049873539801608610504948729", + "1515569761750502248791694538737894540310335943791560078400578525689663319854", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691" + ], + [ + "3452090685552635048692996451778125121475985160419859455597827895335557393222", + "912031770645449830725984238031380150076062082154329492733981015904633114813", + "964606937231418114862894306193543345863893723394147267983877549582533495481", + "1424493249959530700965926275449285041767608322359443292215122043525151409668", + "1324197623245339233654358020817254585475036716208502816949236092471743425856", + "1170255905258666606362050465114777407980330211734501434897244971371153953980", + "1360431631175813446061039837860717152951460849099306345727007273338972570784", + "238030899799779168456945008849198713173740159230645696776744044218681768341" + ], + [ + "3422196048702974753052904776296961222393566603126853311427715449963427029045", + "84842223034372265454718938007274563580552662105280755609117358022892901506", + "2168675722688725516157728341513520475949884865462575986499868210414505971807", + "2445239424379843680109838283159695676601958342088350627204623225027007523675" + ], + [ + "846115156733557394226438556794002355426827923545187732197806829238532919402", + "2714032024708637856878418836631551147050070830191045375934654810077130475331" + ], + [ + "212658268691449657621986746749331260262952449122366947685227185176526965047" + ] + ] +} \ No newline at end of file diff --git a/leafs/fyWBTC.log b/leafs/fyWBTC.log new file mode 100644 index 00000000..5c63b41e --- /dev/null +++ b/leafs/fyWBTC.log @@ -0,0 +1,48 @@ + Compiling snforge_scarb_plugin v0.48.1 +warning: hiding a lifetime that's elided elsewhere is confusing + --> src/args.rs:79:43 + | +79 | pub fn unnamed_only(&self) -> Result { + | ^^^^^ ----------- the same lifetime is hidden here + | | + | the lifetime is elided here + | + = help: the same lifetime is referred to in inconsistent ways, making the signature confusing + = note: `#[warn(mismatched_lifetime_syntaxes)]` on by default +help: use `'_` for type paths + | +79 | pub fn unnamed_only(&self) -> Result, Diagnostic> { + | ++++ +warning: hiding a lifetime that's elided elsewhere is confusing + --> src/args.rs:88:20 + | +88 | pub fn unnamed(&self) -> UnnamedArgs { + | ^^^^^ ----------- the same lifetime is hidden here + | | + | the lifetime is elided here + | + = help: the same lifetime is referred to in inconsistent ways, making the signature confusing +help: use `'_` for type paths + | +88 | pub fn unnamed(&self) -> UnnamedArgs<'_> { + | ++++ +warning: `snforge_scarb_plugin` (lib) generated 2 warnings + Finished `release` profile [optimized] target(s) in 0.03s + Compiling test(vault_allocator_unittest) vault_allocator v0.1.0 (/Users/sachagraff/Documents/GitHub/starknet_vault_kit/packages/vault_allocator/Scarb.toml) + Finished `dev` profile target(s) in 7 seconds + + +Collected 1 test(s) from vault_allocator package +Running 1 test(s) from src/ +vault_allocator: 3258880465856233510553829905629849032717898702344145949430060583241133018680 +root: 212658268691449657621986746749331260262952449122366947685227185176526965047 +tree_capacity: 16 +leaf_used: 13 +leaf_additional_data: [ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2009894490435840142178314390393166646092438090257831307886760648929397478285, selector: 949021990203918389843157787496164629863144228991510976554585288817234167820, argument_addresses: [632184196056345004513825143425280990590956427993302822618957287447348818434], description: "Approve avnu_router to spend STRK", leaf_index: 0, leaf_hash: 3164408410402617224400032578549662571532750893872500165938317175959720396399 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 632184196056345004513825143425280990590956427993302822618957287447348818434, selector: 493099248799488417046068732150865584992754802357103958402415916279525943907, argument_addresses: [2009894490435840142178314390393166646092438090257831307886760648929397478285, 1806018566677800621296032626439935115720767031724401394291089442012247156652, 3258880465856233510553829905629849032717898702344145949430060583241133018680], description: "Multi route swap STRK for WBTC", leaf_index: 1, leaf_hash: 3114613607010838538117892849938711693577364266165250089638946958975706061700 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 632184196056345004513825143425280990590956427993302822618957287447348818434, selector: 493099248799488417046068732150865584992754802357103958402415916279525943907, argument_addresses: [2009894490435840142178314390393166646092438090257831307886760648929397478285, 2522838177878422711967992029571128884451814651829189911296693586560466229864, 3258880465856233510553829905629849032717898702344145949430060583241133018680], description: "Multi route swap STRK for SolvBTC", leaf_index: 2, leaf_hash: 1798338001101173151783443998923089114935259976924703499176892769401881849934 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2522838177878422711967992029571128884451814651829189911296693586560466229864, selector: 949021990203918389843157787496164629863144228991510976554585288817234167820, argument_addresses: [632184196056345004513825143425280990590956427993302822618957287447348818434], description: "Approve avnu_router to spend SolvBTC", leaf_index: 3, leaf_hash: 182741493460629321372418089386706546890536010432763507011887207434844082770 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 632184196056345004513825143425280990590956427993302822618957287447348818434, selector: 493099248799488417046068732150865584992754802357103958402415916279525943907, argument_addresses: [2522838177878422711967992029571128884451814651829189911296693586560466229864, 1806018566677800621296032626439935115720767031724401394291089442012247156652, 3258880465856233510553829905629849032717898702344145949430060583241133018680], description: "Multi route swap SolvBTC for WBTC", leaf_index: 4, leaf_hash: 2605292298572200848180219052926124619350578512278927256491360350076978342161 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 632184196056345004513825143425280990590956427993302822618957287447348818434, selector: 493099248799488417046068732150865584992754802357103958402415916279525943907, argument_addresses: [2522838177878422711967992029571128884451814651829189911296693586560466229864, 1806018566677800621296032626439935115720767031724401394291089442012247156652, 3258880465856233510553829905629849032717898702344145949430060583241133018680], description: "Multi route swap SolvBTC for WBTC", leaf_index: 5, leaf_hash: 2605292298572200848180219052926124619350578512278927256491360350076978342161 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 3475252094465534150446669812792040965529065299943233505330779571674053440258, selector: 405852601487139132244494309743039711091605094719341446212637486410648343561, argument_addresses: [196268403159008932410419402999721616371951519129, 970889662749748738054523748069740135214383638861], description: "Initiate token withdraw WBTC", leaf_index: 6, leaf_hash: 2265942702980092815266072674939931034936886366689290283742389048756110272062 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 1806018566677800621296032626439935115720767031724401394291089442012247156652, selector: 949021990203918389843157787496164629863144228991510976554585288817234167820, argument_addresses: [2516568162210255095453483626839089014569257854319119258892841327983140950402], description: "Approve ekubo_adapter to spend WBTC", leaf_index: 7, leaf_hash: 544495096483226268221200017943815409614707587732244626391175268599110065946 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2522838177878422711967992029571128884451814651829189911296693586560466229864, selector: 949021990203918389843157787496164629863144228991510976554585288817234167820, argument_addresses: [2516568162210255095453483626839089014569257854319119258892841327983140950402], description: "Approve ekubo_adapter to spend SolvBTC", leaf_index: 8, leaf_hash: 1114062149259863212593195361317848415283926476324427638221539960838035626860 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2516568162210255095453483626839089014569257854319119258892841327983140950402, selector: 1485162454000453106748438092882350352049376254989977238717084794132152066047, argument_addresses: [], description: "Deposit liquidity to Ekubofor WBTC and SolvBTC via ekubo_adapter", leaf_index: 9, leaf_hash: 2806250769693476291528283195594704364946079577331271925846894282784089520240 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2516568162210255095453483626839089014569257854319119258892841327983140950402, selector: 322637753074552370500544931377150993467524337001753746958704872129235461672, argument_addresses: [], description: "Withdraw liquidity from Ekubo for WBTC and SolvBTC via ekubo_adapter", leaf_index: 10, leaf_hash: 2461457247526497726225381752851386584506241042129252794668080772037477418255 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2516568162210255095453483626839089014569257854319119258892841327983140950402, selector: 38427523166402100478120264784512528971576573049949957236189387122891476727, argument_addresses: [], description: "Collect fees from Ekubo for WBTC and SolvBTC via ekubo_adapter", leaf_index: 11, leaf_hash: 2399868272939267778338841054190844684504850012171049873539801608610504948729 }, ManageLeafAdditionalData { decoder_and_sanitizer: 1291450050557258653214425734797667027104808014275009832326468111778175109966, target: 2516568162210255095453483626839089014569257854319119258892841327983140950402, selector: 1154545024571392208615796453779759631244452775454593974158722847429584475265, argument_addresses: [], description: "Harvest rewards from Ekubo for WBTC and SolvBTC via ekubo_adapter", leaf_index: 12, leaf_hash: 1515569761750502248791694538737894540310335943791560078400578525689663319854 }] +tree: [[3164408410402617224400032578549662571532750893872500165938317175959720396399, 3114613607010838538117892849938711693577364266165250089638946958975706061700, 1798338001101173151783443998923089114935259976924703499176892769401881849934, 182741493460629321372418089386706546890536010432763507011887207434844082770, 2605292298572200848180219052926124619350578512278927256491360350076978342161, 2605292298572200848180219052926124619350578512278927256491360350076978342161, 2265942702980092815266072674939931034936886366689290283742389048756110272062, 544495096483226268221200017943815409614707587732244626391175268599110065946, 1114062149259863212593195361317848415283926476324427638221539960838035626860, 2806250769693476291528283195594704364946079577331271925846894282784089520240, 2461457247526497726225381752851386584506241042129252794668080772037477418255, 2399868272939267778338841054190844684504850012171049873539801608610504948729, 1515569761750502248791694538737894540310335943791560078400578525689663319854, 3205128972529973231252252659884931471622226491573146866138760422619344340691, 3205128972529973231252252659884931471622226491573146866138760422619344340691, 3205128972529973231252252659884931471622226491573146866138760422619344340691], [3452090685552635048692996451778125121475985160419859455597827895335557393222, 912031770645449830725984238031380150076062082154329492733981015904633114813, 964606937231418114862894306193543345863893723394147267983877549582533495481, 1424493249959530700965926275449285041767608322359443292215122043525151409668, 1324197623245339233654358020817254585475036716208502816949236092471743425856, 1170255905258666606362050465114777407980330211734501434897244971371153953980, 1360431631175813446061039837860717152951460849099306345727007273338972570784, 238030899799779168456945008849198713173740159230645696776744044218681768341], [3422196048702974753052904776296961222393566603126853311427715449963427029045, 84842223034372265454718938007274563580552662105280755609117358022892901506, 2168675722688725516157728341513520475949884865462575986499868210414505971807, 2445239424379843680109838283159695676601958342088350627204623225027007523675], [846115156733557394226438556794002355426827923545187732197806829238532919402, 2714032024708637856878418836631551147050070830191045375934654810077130475331], [212658268691449657621986746749331260262952449122366947685227185176526965047]] +[PASS] vault_allocator::test::creator::creator_fyWBTC::test_creator (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~313880000) +Tests: 1 passed, 0 failed, 0 ignored, 0 filtered out + +Latest block number = 4099289 for url = https://rpc.starknet.lava.build/ + + diff --git a/packages/vault/src/lib.cairo b/packages/vault/src/lib.cairo index 12aa3603..742d79fc 100644 --- a/packages/vault/src/lib.cairo +++ b/packages/vault/src/lib.cairo @@ -28,6 +28,7 @@ pub mod redeem_request { #[cfg(test)] pub mod test { + pub mod mock_erc721_receiver; pub mod utils; pub mod units { pub mod redeem_request; diff --git a/packages/vault/src/redeem_request/errors.cairo b/packages/vault/src/redeem_request/errors.cairo index 3798485f..693e6bf0 100644 --- a/packages/vault/src/redeem_request/errors.cairo +++ b/packages/vault/src/redeem_request/errors.cairo @@ -10,4 +10,8 @@ pub mod Errors { pub fn not_vault_owner() { panic!("Caller is not vault owner"); } + + pub fn not_implemented() { + panic!("Not implemented"); + } } diff --git a/packages/vault/src/redeem_request/redeem_request.cairo b/packages/vault/src/redeem_request/redeem_request.cairo index acabc1e0..d44fae23 100644 --- a/packages/vault/src/redeem_request/redeem_request.cairo +++ b/packages/vault/src/redeem_request/redeem_request.cairo @@ -7,8 +7,14 @@ mod RedeemRequest { use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; + use openzeppelin::interfaces::accounts::ISRC6_ID; + use openzeppelin::interfaces::introspection::{ISRC5Dispatcher, ISRC5DispatcherTrait}; + use openzeppelin::interfaces::token::erc721::{ + ERC721ABI, IERC721ReceiverDispatcher, IERC721ReceiverDispatcherTrait, IERC721_RECEIVER_ID, + }; use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component::ERC721MixinImpl; use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; @@ -21,6 +27,7 @@ mod RedeemRequest { use vault::redeem_request::interface::{IRedeemRequest, RedeemRequestInfo}; use vault::vault::vault::Vault::OWNER_ROLE; + component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: ERC721Component, storage: erc721, event: ERC721Event); component!( @@ -28,8 +35,6 @@ mod RedeemRequest { ); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; #[abi(embed_v0)] impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl; @@ -72,6 +77,96 @@ mod RedeemRequest { self.vault.write(vault); } + #[abi(embed_v0)] + impl ERC721ABIImpl of ERC721ABI { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + ERC721MixinImpl::balance_of(self, account) + } + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + ERC721MixinImpl::owner_of(self, token_id) + } + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span, + ) { + ERC721MixinImpl::safe_transfer_from(ref self, from, to, token_id, data); + } + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, + ) { + Errors::not_implemented(); + } + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + ERC721MixinImpl::approve(ref self, to, token_id); + } + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool, + ) { + ERC721MixinImpl::set_approval_for_all(ref self, operator, approved); + } + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + ERC721MixinImpl::get_approved(self, token_id) + } + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress, + ) -> bool { + ERC721MixinImpl::is_approved_for_all(self, owner, operator) + } + fn name(self: @ContractState) -> ByteArray { + ERC721MixinImpl::name(self) + } + fn symbol(self: @ContractState) -> ByteArray { + ERC721MixinImpl::symbol(self) + } + fn token_uri(self: @ContractState, token_id: u256) -> ByteArray { + ERC721MixinImpl::token_uri(self, token_id) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC721MixinImpl::balance_of(self, account) + } + fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721MixinImpl::owner_of(self, tokenId) + } + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span, + ) { + ERC721MixinImpl::safe_transfer_from(ref self, from, to, tokenId, data); + } + fn transferFrom( + ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256, + ) { + Errors::not_implemented(); + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC721MixinImpl::set_approval_for_all(ref self, operator, approved); + } + fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721MixinImpl::get_approved(self, tokenId) + } + fn isApprovedForAll( + self: @ContractState, owner: ContractAddress, operator: ContractAddress, + ) -> bool { + ERC721MixinImpl::is_approved_for_all(self, owner, operator) + } + + fn tokenURI(self: @ContractState, tokenId: u256) -> ByteArray { + ERC721MixinImpl::token_uri(self, tokenId) + } + + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + ERC721MixinImpl::supports_interface(self, interface_id) + } + } + #[abi(embed_v0)] impl RedeemRequestImpl of IRedeemRequest { // ───────────────────────────────────────────────────────────────────── @@ -99,7 +194,7 @@ mod RedeemRequest { ) -> u256 { self._assert_vault(); let id = self.id_len.read(); - self.erc721.mint(to, id); + self.erc721.safe_mint(to, id, array![].span()); self.id_to_info.write(id, redeem_request_info); self.id_len.write(id + 1); id @@ -136,4 +231,18 @@ mod RedeemRequest { } } } + fn _check_on_erc721_received( + from: ContractAddress, to: ContractAddress, token_id: u256, data: Span, + ) -> bool { + let src5_dispatcher = ISRC5Dispatcher { contract_address: to }; + + if src5_dispatcher.supports_interface(IERC721_RECEIVER_ID) { + IERC721ReceiverDispatcher { contract_address: to } + .on_erc721_received( + get_caller_address(), from, token_id, data, + ) == IERC721_RECEIVER_ID + } else { + src5_dispatcher.supports_interface(ISRC6_ID) + } + } } diff --git a/packages/vault/src/test/mock_erc721_receiver.cairo b/packages/vault/src/test/mock_erc721_receiver.cairo new file mode 100644 index 00000000..d488b832 --- /dev/null +++ b/packages/vault/src/test/mock_erc721_receiver.cairo @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod MockERC721Receiver { + use openzeppelin::interfaces::erc721::{ERC721ReceiverMixin, IERC721_RECEIVER_ID}; + use openzeppelin::introspection::src5::SRC5Component; + use starknet::ContractAddress; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.src5.register_interface(IERC721_RECEIVER_ID); + } + + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[abi(embed_v0)] + impl ERC721ReceiverMixinImpl of ERC721ReceiverMixin { + fn on_erc721_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span, + ) -> felt252 { + IERC721_RECEIVER_ID + } + + fn onERC721Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span, + ) -> felt252 { + IERC721_RECEIVER_ID + } + + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + self.src5.supports_interface(interface_id) + } + } +} diff --git a/packages/vault/src/test/units/redeem_request.cairo b/packages/vault/src/test/units/redeem_request.cairo index 01e11b49..c4d26343 100644 --- a/packages/vault/src/test/units/redeem_request.cairo +++ b/packages/vault/src/test/units/redeem_request.cairo @@ -13,7 +13,7 @@ use vault::redeem_request::interface::{ }; use vault::test::utils::{ DUMMY_ADDRESS, OTHER_DUMMY_ADDRESS, OWNER, cheat_caller_address_once, deploy_counter, - deploy_erc20_mock, deploy_redeem_request, deploy_vault, + deploy_erc20_mock, deploy_erc721_receiver_at, deploy_redeem_request, deploy_vault, }; use vault::vault::vault::Vault; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; @@ -22,6 +22,8 @@ fn set_up() -> (ContractAddress, IRedeemRequestDispatcher) { let underlying_assets = deploy_erc20_mock(); let vault = deploy_vault(underlying_assets); let redeem_request = deploy_redeem_request(vault.contract_address); + deploy_erc721_receiver_at(DUMMY_ADDRESS()); + deploy_erc721_receiver_at(OTHER_DUMMY_ADDRESS()); (vault.contract_address, redeem_request) } @@ -267,7 +269,8 @@ fn test_erc721_functionality() { assert(erc721_dispatcher.get_approved(id) == OTHER_DUMMY_ADDRESS(), 'Approved incorrect'); cheat_caller_address_once(redeem_request.contract_address, OTHER_DUMMY_ADDRESS()); - erc721_dispatcher.transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id); + erc721_dispatcher + .safe_transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id, array![].span()); assert(erc721_dispatcher.owner_of(id) == OTHER_DUMMY_ADDRESS(), 'Owner after transfer'); assert(erc721_dispatcher.balance_of(DUMMY_ADDRESS()) == 0, 'Balance after transfer'); @@ -299,10 +302,12 @@ fn test_set_approval_for_all() { ); cheat_caller_address_once(redeem_request.contract_address, OTHER_DUMMY_ADDRESS()); - erc721_dispatcher.transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_1); + erc721_dispatcher + .safe_transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_1, array![].span()); cheat_caller_address_once(redeem_request.contract_address, OTHER_DUMMY_ADDRESS()); - erc721_dispatcher.transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_2); + erc721_dispatcher + .safe_transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_2, array![].span()); assert(erc721_dispatcher.balance_of(DUMMY_ADDRESS()) == 0, 'Original owner balance'); assert(erc721_dispatcher.balance_of(OTHER_DUMMY_ADDRESS()) == 2, 'New owner balance'); @@ -329,7 +334,8 @@ fn test_complex_scenario_mint_burn_transfer() { assert(redeem_request.id_len() == 2, 'Initial ID length'); cheat_caller_address_once(redeem_request.contract_address, DUMMY_ADDRESS()); - erc721_dispatcher.transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_1); + erc721_dispatcher + .safe_transfer_from(DUMMY_ADDRESS(), OTHER_DUMMY_ADDRESS(), id_1, array![].span()); assert(erc721_dispatcher.owner_of(id_1) == OTHER_DUMMY_ADDRESS(), 'ID 1 new owner'); assert(erc721_dispatcher.balance_of(DUMMY_ADDRESS()) == 1, 'Balance after transfer'); diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index c02ad023..291cc308 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -25,8 +25,8 @@ use vault::redeem_request::interface::{IRedeemRequestDispatcher, IRedeemRequestD use vault::test::utils::{ DUMMY_ADDRESS, FEES_RECIPIENT, MANAGEMENT_FEES, MAX_DELTA, ORACLE, OTHER_DUMMY_ADDRESS, OWNER, 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, + cheat_caller_address_once, deploy_counter, deploy_erc20_mock, deploy_erc721_receiver_at, + deploy_redeem_request, deploy_vault, }; use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; use vault::vault::vault::Vault; @@ -41,6 +41,10 @@ fn set_up() -> (ContractAddress, IVaultDispatcher, IRedeemRequestDispatcher) { vault.register_redeem_request(redeem_request.contract_address); cheat_caller_address_once(vault.contract_address, OWNER()); vault.register_vault_allocator(VAULT_ALLOCATOR()); + deploy_erc721_receiver_at(DUMMY_ADDRESS()); + deploy_erc721_receiver_at(OTHER_DUMMY_ADDRESS()); + deploy_erc721_receiver_at(OWNER()); + (underlying_assets, vault, redeem_request) } @@ -761,80 +765,6 @@ fn test_request_redeem_exact_max_ok() { ], ); } - -#[test] -fn test_request_redeem_fee_exempt_when_owner_is_fees_recipient() { - let (underlying, vault, redeem_request) = set_up(); - - cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_fees_config(FEES_RECIPIENT(), REDEEM_FEES(), MANAGEMENT_FEES(), PERFORMANCE_FEES()); - - let deposit_amount = Vault::WAD; - let erc20_dispatcher = ERC20ABIDispatcher { contract_address: underlying }; - - cheat_caller_address_once(underlying, OWNER()); - erc20_dispatcher.transfer(FEES_RECIPIENT(), deposit_amount); - - cheat_caller_address_once(underlying, FEES_RECIPIENT()); - erc20_dispatcher.approve(vault.contract_address, deposit_amount); - - let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - cheat_caller_address_once(vault.contract_address, FEES_RECIPIENT()); - let shares = erc4626_dispatcher.deposit(deposit_amount, FEES_RECIPIENT()); - - let total_supply_before = ERC20ABIDispatcher { contract_address: vault.contract_address } - .total_supply(); - let epoch = vault.epoch(); - let redeem_nominal_before = vault.redeem_nominal(epoch); - - let mut spy = spy_events(); - cheat_caller_address_once(vault.contract_address, FEES_RECIPIENT()); - let id = vault.request_redeem(shares, DUMMY_ADDRESS(), FEES_RECIPIENT()); - - let expected_assets = shares; - let total_supply_after = ERC20ABIDispatcher { contract_address: vault.contract_address } - .total_supply(); - - assert(total_supply_after == total_supply_before - shares, 'TotalSupply incorrect'); - assert( - vault.redeem_nominal(epoch) == redeem_nominal_before + expected_assets, - 'Redeem - nominal incorrect', - ); - assert( - ERC20ABIDispatcher { contract_address: vault.contract_address } - .balance_of(FEES_RECIPIENT()) == 0, - 'Fees recipient balance', - ); - - let id_info = redeem_request.id_to_info(id); - assert(id_info.epoch == epoch, 'Epoch not set correctly'); - assert(id_info.nominal == expected_assets, 'Nominal not set correctly'); - - let erc721_dispatcher = ERC721ABIDispatcher { - contract_address: redeem_request.contract_address, - }; - assert(erc721_dispatcher.owner_of(id) == DUMMY_ADDRESS(), 'Owner not set correctly'); - - spy - .assert_emitted( - @array![ - ( - vault.contract_address, - Vault::Event::RedeemRequested( - Vault::RedeemRequested { - owner: FEES_RECIPIENT(), - receiver: DUMMY_ADDRESS(), - shares, - assets: expected_assets, - id, - epoch, - }, - ), - ), - ], - ); -} #[test] fn test_request_redeem_third_party_spender_uses_allowance_and_decreases_it() { let (underlying, vault, _) = set_up(); @@ -2930,39 +2860,6 @@ fn test_deposit_limit() { ); } -#[test] -fn test_mint_limit() { - let (underlying, vault, _) = set_up(); - let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; - let deposit_cap = Vault::WAD * 100; - cheat_caller_address_once(vault.contract_address, OWNER()); - vault.set_deposit_limit(deposit_cap); - let mint_limit_config = Vault::WAD * 50; - cheat_caller_address_once(vault.contract_address, OWNER()); - 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(), 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 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_mint_limit(Bounded::MAX); - - assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == Bounded::MAX, 'Max mint not unlimited'); -} - #[test] #[should_panic(expected: ('Caller is missing role',))] @@ -2973,15 +2870,6 @@ fn test_set_deposit_limit_unauthorized() { 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] fn test_deposit_with_limit() { diff --git a/packages/vault/src/test/utils.cairo b/packages/vault/src/test/utils.cairo index bd3dc487..37a570ff 100644 --- a/packages/vault/src/test/utils.cairo +++ b/packages/vault/src/test/utils.cairo @@ -93,6 +93,15 @@ pub fn deploy_vault(underlying_asset: ContractAddress) -> IVaultDispatcher { IVaultDispatcher { contract_address: vault_allocator_address } } +pub fn deploy_erc721_receiver_at(targetAddress: ContractAddress) -> ContractAddress { + let vault_erc721_receiver = declare("MockERC721Receiver").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + let (vault_erc721_receiver_address, _) = vault_erc721_receiver + .deploy_at(@calldata, targetAddress) + .unwrap(); + vault_erc721_receiver_address +} + pub fn deploy_redeem_request(vault: ContractAddress) -> IRedeemRequestDispatcher { let redeem_request = declare("RedeemRequest").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index 0afcf2cc..c5f9d151 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -46,11 +46,9 @@ 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 get_deposit_limit(self: @TContractState) -> u256; - fn get_mint_limit(self: @TContractState) -> u256; } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index 3963c0d5..a9322a63 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, Bounded}; + use core::num::traits::{Bounded, Zero}; use openzeppelin::access::accesscontrol::AccessControlComponent; use openzeppelin::interfaces::erc20::{ ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, @@ -32,9 +32,7 @@ pub mod Vault { use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::security::pausable::PausableComponent; use openzeppelin::token::erc20::extensions::erc4626::ERC4626Component::Fee; - use openzeppelin::token::erc20::extensions::erc4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, - }; + use openzeppelin::token::erc20::extensions::erc4626::{DefaultConfig, ERC4626Component}; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, }; @@ -87,6 +85,38 @@ pub mod Vault { impl ERC4626Impl = ERC4626Component::ERC4626Impl; impl ERC4626InternalImpl = ERC4626Component::InternalImpl; + + impl ERC4626FeesImpl of ERC4626Component::FeeConfigTrait { + fn calculate_deposit_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + Option::None + } + + fn calculate_mint_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + Option::None + } + + fn calculate_withdraw_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee_shares = contract_state._calculate_fee_shares(shares); + Option::Some(Fee::Shares(fee_shares)) + } + + fn calculate_redeem_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee_shares = contract_state._calculate_fee_shares(shares); + Option::Some(Fee::Shares(fee_shares)) + } + } + + // --- Custom ERC4626 Limits Implementation --- // Custom implementation of deposit/withdraw limits // Uses max u256 as sentinel value for "unlimited" @@ -104,7 +134,7 @@ pub mod Vault { } else { let total_assets = self.get_total_assets(); if total_assets >= limit { - Option::Some(0) + Option::Some(0) } else { Option::Some(limit - total_assets) } @@ -117,23 +147,12 @@ pub mod Vault { fn mint_limit( self: @ERC4626Component::ComponentState, receiver: ContractAddress, ) -> Option { - let contract_state = self.get_contract(); - let limit = contract_state.mint_limit.read(); - if limit == Bounded::MAX { - Option::None - } else { - 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) - } - } - } + let deposit_limit_opt = self.deposit_limit(receiver); + match deposit_limit_opt { + Option::None => Option::None, + Option::Some(deposit_remaining) => { + Option::Some(self._convert_to_shares(deposit_remaining, Rounding::Floor)) + }, } } @@ -214,8 +233,7 @@ pub mod Vault { 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 + deposit_limit: u256 // Maximum deposit amount } // --- Events --- @@ -223,11 +241,17 @@ pub mod Vault { #[derive(Drop, starknet::Event)] pub enum Event { // Component events + #[flat] ERC20Event: ERC20Component::Event, + #[flat] ERC4626Event: ERC4626Component::Event, + #[flat] SRC5Event: SRC5Component::Event, + #[flat] AccessControlEvent: AccessControlComponent::Event, + #[flat] UpgradeableEvent: UpgradeableComponent::Event, + #[flat] PausableEvent: PausableComponent::Event, // Vault-specific events RedeemRequested: RedeemRequested, // Emitted when a redemption is requested @@ -328,7 +352,6 @@ pub mod Vault { // max u256 is used as sentinel value for "no limit" let max_limit: u256 = Bounded::MAX; self.deposit_limit.write(max_limit); - self.mint_limit.write(max_limit); self .emit( Report { @@ -587,19 +610,14 @@ pub mod Vault { } // Calculate and collect redemption fees - let fees_recipient = self.fees_recipient.read(); - let redeem_fees = if (owner == fees_recipient) { - 0 - } else { - self.redeem_fees.read() - }; - let fee_shares = (shares * redeem_fees) - / WAD; // Fee calculation: shares * fee_rate / 1e18 + let fee_shares = self._calculate_fee_shares(shares); if (fee_shares.is_non_zero()) { self .erc20 - .update(owner, fees_recipient, fee_shares); // Transfer fee shares to recipient + .update( + owner, self.fees_recipient.read(), fee_shares, + ); // Transfer fee shares to recipient } let remaining_shares = shares - fee_shares; // Shares after fee deduction @@ -965,30 +983,19 @@ pub mod Vault { // --- Limit Configuration Functions --- - /// Set the deposit limit (max u256 for unlimited, any other value including 0 for specific limit) + /// 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); - } - /// 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() - } - fn due_assets_from_owner(self: @ContractState, owner: ContractAddress) -> u256 { let balance = ERC721ABIDispatcher { contract_address: self.redeem_request.read().contract_address, @@ -1114,5 +1121,10 @@ pub mod Vault { } total_redeem_assets } + + + fn _calculate_fee_shares(self: @ContractState, shares: u256) -> u256 { + (shares * self.redeem_fees.read()) / WAD + } } } diff --git a/packages/vault_allocator/Scarb.toml b/packages/vault_allocator/Scarb.toml index 1541c1fc..7a08d15a 100644 --- a/packages/vault_allocator/Scarb.toml +++ b/packages/vault_allocator/Scarb.toml @@ -15,43 +15,73 @@ fmt.workspace = true [[tool.snforge.fork]] name = "MAINNET" -url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" -block_id.tag = "latest" +url = "https://rpc.starknet.lava.build" +block_id.number = "4343594" [[tool.snforge.fork]] -name = "AVNU" -url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" -block_id.number = "1781028" +name = "LZ" +url = "https://rpc.starknet.lava.build" +block_id.number = "4343594" [[tool.snforge.fork]] -name = "SSCL" -url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" -block_id.number = "1784627" +name = "STARKGATE_MIDDLEWARE" +url = "https://rpc.starknet.lava.build" +block_id.number = "4336541" [[tool.snforge.fork]] -name = "SLLSE" -url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" -block_id.number = "1794270" +name = "CCTP_MIDDLEWARE" +url = "https://rpc.starknet.lava.build" +block_id.number = "4336541" +[[tool.snforge.fork]] +name = "HYPERLANE_MIDDLEWARE" +url = "https://rpc.starknet.lava.build" +block_id.number = "4336541" +[[tool.snforge.fork]] +name = "BASE_MIDDLEWARE" +url = "https://rpc.starknet.lava.build" +block_id.number = "4336541" +[[tool.snforge.fork]] +name = "LZ_MIDDLEWARE" +url = "https://rpc.starknet.lava.build" +block_id.number = "4336541" +[[tool.snforge.fork]] +name = "AVNU" +url = "https://rpc.starknet.lava.build" +block_id.number = "1781028" + +[[tool.snforge.fork]] +name = "EKUBO" +url = "https://rpc.starknet.lava.build" +block_id.number = "4077824" +[[tool.snforge.fork]] +name = "SSCL" +url = "https://rpc.starknet.lava.build" +block_id.number = "1784627" +[[tool.snforge.fork]] +name = "SLLSE" +url = "https://rpc.starknet.lava.build" +block_id.number = "1794270" [dependencies] starknet.workspace = true openzeppelin.workspace = true alexandria_math.workspace = true +alexandria_bytes.workspace = true +ekubo.workspace = true [dev-dependencies] snforge_std.workspace = true - [lib] [[target.starknet-contract]] diff --git a/packages/vault_allocator/src/adapters/ekubo_adapter/ekubo_adapter.cairo b/packages/vault_allocator/src/adapters/ekubo_adapter/ekubo_adapter.cairo new file mode 100644 index 00000000..b17160a5 --- /dev/null +++ b/packages/vault_allocator/src/adapters/ekubo_adapter/ekubo_adapter.cairo @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod EkuboAdapter { + use core::num::traits::Zero; + use ekubo::types::i129::i129; + use ekubo::types::pool_price::PoolPrice; + use ekubo::types::position::Position; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + use vault_allocator::adapters::ekubo_adapter::errors::Errors; + use vault_allocator::adapters::ekubo_adapter::interface::{ + IEkuboAdapter, IRewardContractDispatcher, IRewardContractDispatcherTrait, + }; + use vault_allocator::integration_interfaces::ekubo::{ + Bounds, IEkuboCoreDispatcher, IEkuboCoreDispatcherTrait, IEkuboDispatcher, + IEkuboDispatcherTrait, IEkuboNFTDispatcher, IEkuboNFTDispatcherTrait, + IMathLibDispatcherTrait, PoolKey, PositionKey, dispatcher as ekuboLibDispatcher, + }; + + const WAD: u128 = 1_000_000_000_000_000_000; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + vault_allocator: ContractAddress, + ekubo_positions_contract: IEkuboDispatcher, + pool_key: PoolKey, + ekubo_positions_nft: ContractAddress, + ekubo_core: ContractAddress, + bounds_settings: Bounds, + contract_nft_id: u64, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + vault_allocator: ContractAddress, + ekubo_positions_contract: ContractAddress, + bounds_settings: Bounds, + pool_key: PoolKey, + ekubo_positions_nft: ContractAddress, + ekubo_core: ContractAddress, + ) { + let owner = IOwnableDispatcher { contract_address: vault_allocator }.owner(); + self.ownable.initializer(owner); + self.vault_allocator.write(vault_allocator); + self + .ekubo_positions_contract + .write(IEkuboDispatcher { contract_address: ekubo_positions_contract }); + self.bounds_settings.write(bounds_settings); + self.pool_key.write(pool_key); + self.ekubo_positions_nft.write(ekubo_positions_nft); + self.ekubo_core.write(ekubo_core); + } + + #[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 EkuboAdapterImpl of IEkuboAdapter { + fn deposit_liquidity(ref self: ContractState, amount0: u256, amount1: u256) { + let vault_allocator = self._only_vault_allocator(); + if (amount0.is_zero() && amount1.is_zero()) { + Errors::zero_amount(); + } + let liquidity_expected = self._max_liquidity(amount0, amount1); + let pool_key = self.pool_key.read(); + let token0 = pool_key.token0; + let token1 = pool_key.token1; + let positions_disp = self.ekubo_positions_contract.read(); + ERC20ABIDispatcher { contract_address: token0 } + .transfer_from(vault_allocator, positions_disp.contract_address, amount0); + ERC20ABIDispatcher { contract_address: token1 } + .transfer_from(vault_allocator, positions_disp.contract_address, amount1); + let liq_before_deposit = self.get_position().liquidity; + let nft_id = self.contract_nft_id.read(); + if nft_id.is_zero() { + self + .contract_nft_id + .write( + IEkuboNFTDispatcher { contract_address: self.ekubo_positions_nft.read() } + .get_next_token_id(), + ); + positions_disp + .mint_and_deposit(self.pool_key.read(), self.bounds_settings.read(), 0); + } else { + positions_disp + .deposit(nft_id, self.pool_key.read(), self.bounds_settings.read(), 0); + } + positions_disp.clear_minimum_to_recipient(token0, 0, vault_allocator); + positions_disp.clear_minimum_to_recipient(token1, 0, vault_allocator); + + let liq_after_deposit = self.get_position().liquidity; + let liquidity_actual = (liq_after_deposit - liq_before_deposit).into(); + if (liquidity_expected != liquidity_actual) { + Errors::invalid_liquidity_added(); + } + } + + fn withdraw_liquidity( + ref self: ContractState, ratioWad: u256, min_token0: u128, min_token1: u128, + ) { + let vault_allocator = self._only_vault_allocator(); + let pool_key = self.pool_key.read(); + let current_liquidity = self.get_position().liquidity; + let liquidity_to_withdraw = current_liquidity * ratioWad.try_into().unwrap() / WAD; + let (amt0, amt1) = self + .ekubo_positions_contract + .read() + .withdraw( + self.contract_nft_id.read(), + self.pool_key.read(), + self.bounds_settings.read(), + liquidity_to_withdraw, + min_token0, + min_token1, + false, + ); + ERC20ABIDispatcher { contract_address: pool_key.token0 } + .transfer(vault_allocator, amt0.into()); + ERC20ABIDispatcher { contract_address: pool_key.token1 } + .transfer(vault_allocator, amt1.into()); + let current_liq = self.get_position().liquidity; + if (current_liq == 0) { + self.contract_nft_id.write(0); + } + if (current_liquidity - current_liq != liquidity_to_withdraw) { + Errors::invalid_liquidity_removed(); + } + } + + fn get_position_key(self: @ContractState) -> PositionKey { + PositionKey { + salt: self.contract_nft_id.read(), + owner: self.ekubo_positions_contract.read().contract_address, + bounds: self.bounds_settings.read(), + } + } + + fn get_position(self: @ContractState) -> Position { + let position_key = self.get_position_key(); + IEkuboCoreDispatcher { contract_address: self.ekubo_core.read() } + .get_position(self.pool_key.read(), position_key) + } + + fn get_ekubo_positions_contract(self: @ContractState) -> ContractAddress { + self.ekubo_positions_contract.read().contract_address + } + + fn get_bounds_settings(self: @ContractState) -> Bounds { + self.bounds_settings.read() + } + + fn get_pool_key(self: @ContractState) -> PoolKey { + self.pool_key.read() + } + + fn get_ekubo_positions_nft(self: @ContractState) -> ContractAddress { + self.ekubo_positions_nft.read() + } + + fn get_contract_nft_id(self: @ContractState) -> u64 { + self.contract_nft_id.read() + } + + fn get_ekubo_core(self: @ContractState) -> ContractAddress { + self.ekubo_core.read() + } + + fn get_vault_allocator(self: @ContractState) -> ContractAddress { + self.vault_allocator.read() + } + + fn total_liquidity(self: @ContractState) -> u256 { + self.get_position().liquidity.into() + } + + fn underlying_balance(self: @ContractState) -> (u256, u256) { + let contract_nft_id = self.contract_nft_id.read(); + if contract_nft_id.is_zero() { + (0, 0) + } else { + let token_info = self + .ekubo_positions_contract + .read() + .get_token_info( + self.contract_nft_id.read(), + self.pool_key.read(), + self.bounds_settings.read(), + ); + (token_info.amount0.into(), token_info.amount1.into()) + } + } + + fn pending_fees(self: @ContractState) -> (u256, u256) { + let contract_nft_id = self.contract_nft_id.read(); + if contract_nft_id.is_zero() { + (0, 0) + } else { + let token_info = self + .ekubo_positions_contract + .read() + .get_token_info( + self.contract_nft_id.read(), + self.pool_key.read(), + self.bounds_settings.read(), + ); + (token_info.fees0.into(), token_info.fees1.into()) + } + } + + fn collect_fees(ref self: ContractState) { + let vault_allocator = self._only_vault_allocator(); + let nft_id = self.contract_nft_id.read(); + if (nft_id.is_non_zero()) { + let pool_key = self.pool_key.read(); + let bounds = self.bounds_settings.read(); + let token0 = pool_key.token0; + let token1 = pool_key.token1; + let (fee0, fee1) = self + .ekubo_positions_contract + .read() + .collect_fees(nft_id, pool_key, bounds); + ERC20ABIDispatcher { contract_address: token0 } + .transfer(vault_allocator, fee0.into()); + ERC20ABIDispatcher { contract_address: token1 } + .transfer(vault_allocator, fee1.into()); + } + } + + fn get_deposit_ratio(self: @ContractState) -> (u256, u256) { + let bounds = self.bounds_settings.read(); + let sqrt_ratio_lower = self._tick_to_sqrt_ratio(bounds.lower); + let sqrt_ratio_upper = self._tick_to_sqrt_ratio(bounds.upper); + let sqrt_ratio_current = self._get_pool_price().sqrt_ratio; + let wad: u256 = WAD.into(); + let (token0_ratio_wad, token1_ratio_wad) = if sqrt_ratio_current <= sqrt_ratio_lower { + (wad, Zero::zero()) + } else if sqrt_ratio_current >= sqrt_ratio_upper { + (Zero::zero(), wad) + } else { + let range = sqrt_ratio_upper - sqrt_ratio_lower; + let position_in_range = sqrt_ratio_current - sqrt_ratio_lower; + let token1_ratio_wad = (position_in_range * wad) / range; + let token0_ratio_wad = wad - token1_ratio_wad; + (token0_ratio_wad, token1_ratio_wad) + }; + (token0_ratio_wad, token1_ratio_wad) + } + + fn set_bounds_settings(ref self: ContractState, bounds: Bounds) { + self.ownable.assert_only_owner(); + if self.contract_nft_id.read().is_non_zero() { + Errors::position_exists(); + } + self.bounds_settings.write(bounds); + } + + fn harvest( + ref self: ContractState, + reward_contract: ContractAddress, + id: u64, + amount: u128, + proof: Span, + reward_token: ContractAddress, + ) { + let vault_allocator = self._only_vault_allocator(); + IRewardContractDispatcher { contract_address: reward_contract } + .claim(id, starknet::get_contract_address(), amount, proof); + let token_dispatcher = ERC20ABIDispatcher { contract_address: reward_token }; + let balance = token_dispatcher.balance_of(starknet::get_contract_address()); + token_dispatcher.transfer(vault_allocator, balance); + } + } + + #[generate_trait] + pub impl InternalFunctions of InternalFunctionsTrait { + fn _tick_to_sqrt_ratio(self: @ContractState, tick: i129) -> u256 { + ekuboLibDispatcher().tick_to_sqrt_ratio(tick) + } + + fn _get_pool_price(self: @ContractState) -> PoolPrice { + self.ekubo_positions_contract.read().get_pool_price(self.pool_key.read()) + } + + fn _max_liquidity(self: @ContractState, amount0: u256, amount1: u256) -> u256 { + let current_sqrt_price = self._get_pool_price().sqrt_ratio; + let liquidity = ekuboLibDispatcher() + .max_liquidity( + current_sqrt_price, + self._tick_to_sqrt_ratio(self.bounds_settings.read().lower), + self._tick_to_sqrt_ratio(self.bounds_settings.read().upper), + amount0.try_into().unwrap(), + amount1.try_into().unwrap(), + ); + liquidity.into() + } + + fn _only_vault_allocator(self: @ContractState) -> ContractAddress { + let vault_allocator = self.vault_allocator.read(); + if (get_caller_address() != vault_allocator) { + Errors::only_vault_allocator(); + } + vault_allocator + } + } +} diff --git a/packages/vault_allocator/src/adapters/ekubo_adapter/errors.cairo b/packages/vault_allocator/src/adapters/ekubo_adapter/errors.cairo new file mode 100644 index 00000000..44a410a3 --- /dev/null +++ b/packages/vault_allocator/src/adapters/ekubo_adapter/errors.cairo @@ -0,0 +1,25 @@ +// 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 only_vault_allocator() { + panic!("Only vault allocator can call this function"); + } + + pub fn zero_amount() { + panic!("Zero amount"); + } + + pub fn invalid_liquidity_added() { + panic!("Invalid liquidity added"); + } + + pub fn invalid_liquidity_removed() { + panic!("Invalid liquidity removed"); + } + + pub fn position_exists() { + panic!("Cannot modify bounds when position exists"); + } +} diff --git a/packages/vault_allocator/src/adapters/ekubo_adapter/interface.cairo b/packages/vault_allocator/src/adapters/ekubo_adapter/interface.cairo new file mode 100644 index 00000000..d296a24e --- /dev/null +++ b/packages/vault_allocator/src/adapters/ekubo_adapter/interface.cairo @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use ekubo::types::position::Position as EkuboPosition; +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::ekubo::{Bounds, PoolKey, PositionKey}; + +#[starknet::interface] +pub trait IEkuboAdapter { + fn get_position_key(self: @T) -> PositionKey; + fn get_position(self: @T) -> EkuboPosition; + fn get_ekubo_positions_contract(self: @T) -> ContractAddress; + fn get_bounds_settings(self: @T) -> Bounds; + fn get_pool_key(self: @T) -> PoolKey; + fn get_ekubo_positions_nft(self: @T) -> ContractAddress; + fn get_contract_nft_id(self: @T) -> u64; + fn get_ekubo_core(self: @T) -> ContractAddress; + fn get_vault_allocator(self: @T) -> ContractAddress; + fn total_liquidity(self: @T) -> u256; + fn deposit_liquidity(ref self: T, amount0: u256, amount1: u256); + fn withdraw_liquidity(ref self: T, ratioWad: u256, min_token0: u128, min_token1: u128); + fn underlying_balance(self: @T) -> (u256, u256); + fn pending_fees(self: @T) -> (u256, u256); + fn collect_fees(ref self: T); + /// @notice Returns the deposit ratio of token0 and token1 based on bounds and current pool + /// price @dev Calculates how much of each token is needed for a balanced deposit given the + /// position bounds @return sqrt_ratio_lower The sqrt price ratio at the lower bound + /// @return sqrt_ratio_upper The sqrt price ratio at the upper bound + /// @return sqrt_ratio_current The current pool sqrt price ratio + /// @return token0_ratio_wad The percentage of token0 to deposit in WAD (0 to 1e18) + /// @return token1_ratio_wad The percentage of token1 to deposit in WAD (0 to 1e18) + fn get_deposit_ratio(self: @T) -> (u256, u256); + /// @notice Sets new bounds settings for the position + /// @dev Only callable by owner and only when no position exists (contract_nft_id is zero) + /// @param bounds The new bounds settings to set + fn set_bounds_settings(ref self: T, bounds: Bounds); + /// @notice Claims rewards from a reward contract and sends them to vault allocator + /// @param reward_contract The contract to claim rewards from + /// @param id The claim id + /// @param amount The amount to claim + /// @param proof The merkle proof for claiming + /// @param reward_token The token address of the reward + fn harvest( + ref self: T, + reward_contract: ContractAddress, + id: u64, + amount: u128, + proof: Span, + reward_token: ContractAddress, + ); +} + +#[starknet::interface] +pub trait IRewardContract { + fn claim(ref self: T, id: u64, claimee: ContractAddress, amount: u128, proof: Span); +} 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 da60c7b4..17c15fa5 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 @@ -2,6 +2,7 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. +// work for interacting directly with the avnu router or using the avnu middleware #[starknet::component] pub mod AvnuExchangeDecoderAndSanitizerComponent { use starknet::ContractAddress; diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/cctp_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/cctp_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..bb2498b6 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/cctp_decoder_and_sanitizer.cairo @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod CctpDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::cctp_decoder_and_sanitizer::interface::ICctpDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(CctpDecoderAndSanitizerImpl)] + impl CctpDecoderAndSanitizer< + TContractState, +HasComponent, + > of ICctpDecoderAndSanitizer> { + fn deposit_for_burn( + self: @ComponentState, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + destination_domain.serialize(ref serialized_struct); + mint_recipient.serialize(ref serialized_struct); + burn_token.serialize(ref serialized_struct); + destination_caller.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..adef3a69 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,19 @@ +// 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 ICctpDecoderAndSanitizer { + fn deposit_for_burn( + self: @T, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/cctp_middleware_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/cctp_middleware_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..d096d500 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/cctp_middleware_decoder_and_sanitizer.cairo @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod CctpMiddlewareDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::cctp_middleware_decoder_and_sanitizer::interface::ICctpMiddlewareDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(CctpMiddlewareDecoderAndSanitizerImpl)] + impl CctpMiddlewareDecoderAndSanitizer< + TContractState, +HasComponent, + > of ICctpMiddlewareDecoderAndSanitizer> { + fn deposit_for_burn( + self: @ComponentState, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + destination_domain.serialize(ref serialized_struct); + mint_recipient.serialize(ref serialized_struct); + burn_token.serialize(ref serialized_struct); + token_to_claim.serialize(ref serialized_struct); + destination_caller.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..8a157df4 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/cctp_middleware_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,20 @@ +// 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 ICctpMiddlewareDecoderAndSanitizer { + fn deposit_for_burn( + self: @T, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_decoder_and_sanitizer/defi_spring_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_decoder_and_sanitizer/defi_spring_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..4e09e234 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_decoder_and_sanitizer/defi_spring_decoder_and_sanitizer.cairo @@ -0,0 +1,30 @@ +// 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 + +#[starknet::component] +pub mod DefiSpringDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::defi_spring_decoder_and_sanitizer::interface::IDefiSpringDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(DefiSpringDecoderAndSanitizerImpl)] + impl DefiSpringDecoderAndSanitizer< + TContractState, +HasComponent, + > of IDefiSpringDecoderAndSanitizer> { + 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_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..eef94279 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IDefiSpringDecoderAndSanitizer { + fn claim(self: @T, amount: u128, proof: Span) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/ekubo_adapter_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/ekubo_adapter_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..a455f219 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/ekubo_adapter_decoder_and_sanitizer.cairo @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod EkuboAdapterDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::ekubo_adapter_decoder_and_sanitizer::interface::IEkuboAdapterDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(EkuboAdapterDecoderAndSanitizerImpl)] + impl EkuboAdapterDecoderAndSanitizer< + TContractState, +HasComponent, + > of IEkuboAdapterDecoderAndSanitizer> { + fn deposit_liquidity( + self: @ComponentState, amount0: u256, amount1: u256, + ) -> Span { + // No addresses to sanitize - amounts are value parameters + let serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn withdraw_liquidity( + self: @ComponentState, + ratioWad: u256, + min_token0: u128, + min_token1: u128, + ) -> Span { + // No addresses to sanitize - all parameters are value types + let serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn collect_fees(self: @ComponentState) -> Span { + // No addresses to sanitize - no parameters + let serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn harvest( + self: @ComponentState, + reward_contract: ContractAddress, + id: u64, + amount: u128, + proof: Span, + reward_token: ContractAddress, + ) -> Span { + let serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..f0e8c49c --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/ekubo_adapter_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,22 @@ +// 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 IEkuboAdapterDecoderAndSanitizer { + fn deposit_liquidity(self: @T, amount0: u256, amount1: u256) -> Span; + fn withdraw_liquidity( + self: @T, ratioWad: u256, min_token0: u128, min_token1: u128, + ) -> Span; + fn collect_fees(self: @T) -> Span; + fn harvest( + self: @T, + reward_contract: ContractAddress, + id: u64, + amount: u128, + proof: Span, + reward_token: ContractAddress, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/extended_decoder_and_sanitizer/extended_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/extended_decoder_and_sanitizer/extended_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..36332958 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/extended_decoder_and_sanitizer/extended_decoder_and_sanitizer.cairo @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod ExtendedDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::extended_decoder_and_sanitizer::interface::IExtendedDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(StarkgateDecoderAndSanitizerImpl)] + impl ExtendedDecoderAndSanitizer< + TContractState, +HasComponent, + > of IExtendedDecoderAndSanitizer> { + fn deposit( + self: @ComponentState, + vault_number: felt252, + amount: felt252, + nonce: felt252, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + vault_number.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/extended_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/extended_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..5fc553d1 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/extended_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 starknet::ContractAddress; + +#[starknet::interface] +pub trait IExtendedDecoderAndSanitizer { + fn deposit(self: @T, vault_number: felt252, amount: felt252, nonce: felt252) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/forgeyields_paradex_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/forgeyields_paradex_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..6655e70a --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/forgeyields_paradex_decoder_and_sanitizer.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 ForgeyieldsParadexDecoderAndSanitizer { + 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::paradex_gigavault_decoder_and_sanitizer::paradex_gigavault_decoder_and_sanitizer::ParadexGigavaultDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::starkgate_decoder_and_sanitizer::starkgate_decoder_and_sanitizer::StarkgateDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: ParadexGigavaultDecoderAndSanitizerComponent, + storage: paradex_gigavault_decoder_and_sanitizer, + event: ParadexGigavaultDecoderAndSanitizerEvent, + ); + + component!( + path: Erc4626DecoderAndSanitizerComponent, + storage: erc4626_decoder_and_sanitizer, + event: Erc4626DecoderAndSanitizerEvent, + ); + + component!( + path: StarkgateDecoderAndSanitizerComponent, + storage: starkgate_decoder_and_sanitizer, + event: StarkgateDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl ParadexGigavaultDecoderAndSanitizerImpl = + ParadexGigavaultDecoderAndSanitizerComponent::ParadexGigavaultDecoderAndSanitizerImpl< + ContractState, + >; + + #[abi(embed_v0)] + impl Erc4626DecoderAndSanitizerImpl = + Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub paradex_gigavault_decoder_and_sanitizer: ParadexGigavaultDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub erc4626_decoder_and_sanitizer: Erc4626DecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub starkgate_decoder_and_sanitizer: StarkgateDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + ParadexGigavaultDecoderAndSanitizerEvent: ParadexGigavaultDecoderAndSanitizerComponent::Event, + #[flat] + Erc4626DecoderAndSanitizerEvent: Erc4626DecoderAndSanitizerComponent::Event, + #[flat] + StarkgateDecoderAndSanitizerEvent: StarkgateDecoderAndSanitizerComponent::Event, + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/fyWBTC_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/fyWBTC_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..da705f7a --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/fyWBTC_decoder_and_sanitizer.cairo @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod FyWBTCDecoderAndSanitizer { + 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::ekubo_adapter_decoder_and_sanitizer::ekubo_adapter_decoder_and_sanitizer::EkuboAdapterDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::starkgate_decoder_and_sanitizer::starkgate_decoder_and_sanitizer::StarkgateDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: EkuboAdapterDecoderAndSanitizerComponent, + storage: ekubo_adapter_decoder_and_sanitizer, + event: EkuboAdapterDecoderAndSanitizerEvent, + ); + + component!( + path: StarkgateDecoderAndSanitizerComponent, + storage: starkgate_decoder_and_sanitizer, + event: StarkgateDecoderAndSanitizerEvent, + ); + + component!( + path: AvnuExchangeDecoderAndSanitizerComponent, + storage: avnu_exchange_decoder_and_sanitizer, + event: AvnuExchangeDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl EkuboAdapterDecoderAndSanitizerImpl = + EkuboAdapterDecoderAndSanitizerComponent::EkuboAdapterDecoderAndSanitizerImpl< + ContractState, + >; + + #[abi(embed_v0)] + impl StarkgateDecoderAndSanitizerImpl = + StarkgateDecoderAndSanitizerComponent::StarkgateDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl AvnuExchangeDecoderAndSanitizerImpl = + AvnuExchangeDecoderAndSanitizerComponent::AvnuExchangeDecoderAndSanitizerImpl< + ContractState, + >; + + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub starkgate_decoder_and_sanitizer: StarkgateDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub ekubo_adapter_decoder_and_sanitizer: EkuboAdapterDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub avnu_exchange_decoder_and_sanitizer: AvnuExchangeDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + StarkgateDecoderAndSanitizerEvent: StarkgateDecoderAndSanitizerComponent::Event, + #[flat] + EkuboAdapterDecoderAndSanitizerEvent: EkuboAdapterDecoderAndSanitizerComponent::Event, + #[flat] + AvnuExchangeDecoderAndSanitizerEvent: AvnuExchangeDecoderAndSanitizerComponent::Event, + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/hyperlane_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/hyperlane_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..32ce09ab --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/hyperlane_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 HyperlaneDecoderAndSanitizerComponent { + use alexandria_bytes::Bytes; + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::hyperlane_decoder_and_sanitizer::interface::IHyperlaneDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(HyperlaneDecoderAndSanitizerImpl)] + impl HyperlaneDecoderAndSanitizer< + TContractState, +HasComponent, + > of IHyperlaneDecoderAndSanitizer> { + fn transfer_remote( + self: @ComponentState, + destination: u32, + recipient: u256, + amount_or_id: u256, + value: u256, + hook_metadata: Option, + hook: Option, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + destination.serialize(ref serialized_struct); + recipient.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..22eae256 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use alexandria_bytes::Bytes; +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IHyperlaneDecoderAndSanitizer { + fn transfer_remote( + self: @T, + destination: u32, + recipient: u256, + amount_or_id: u256, + value: u256, + hook_metadata: Option, + hook: Option, + ) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/hyperlane_middleware_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/hyperlane_middleware_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..bedf19fd --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/hyperlane_middleware_decoder_and_sanitizer.cairo @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod HyperlaneMiddlewareDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::hyperlane_middleware_decoder_and_sanitizer::interface::IHyperlaneMiddlewareDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(HyperlaneMiddlewareDecoderAndSanitizerImpl)] + impl HyperlaneMiddlewareDecoderAndSanitizer< + TContractState, +HasComponent, + > of IHyperlaneMiddlewareDecoderAndSanitizer> { + fn bridge_token( + self: @ComponentState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + recipient: u256, + amount: u256, + value: u256, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + token_to_bridge.serialize(ref serialized_struct); + token_to_claim.serialize(ref serialized_struct); + destination_domain.serialize(ref serialized_struct); + recipient.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..6daa685d --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/hyperlane_middleware_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,18 @@ +// 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 IHyperlaneMiddlewareDecoderAndSanitizer { + fn bridge_token( + self: @T, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + recipient: u256, + amount: u256, + value: u256, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..d42fb3d5 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::lz::{SendParam, MessagingFee}; + +#[starknet::interface] +pub trait ILzDecoderAndSanitizer { + fn send( + self: @T, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/lz_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/lz_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..c348ad87 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/lz_decoder_and_sanitizer/lz_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 LzDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::lz_decoder_and_sanitizer::interface::ILzDecoderAndSanitizer; + use vault_allocator::integration_interfaces::lz::{SendParam, MessagingFee}; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(LzDecoderAndSanitizerImpl)] + impl LzDecoderAndSanitizer< + TContractState, +HasComponent, + > of ILzDecoderAndSanitizer> { + fn send( + self: @ComponentState, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + send_param.dst_eid.serialize(ref serialized_struct); + send_param.to.serialize(ref serialized_struct); + refund_address.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..db9632eb --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::lz::{MessagingFee, SendParam}; + +#[starknet::interface] +pub trait ILzMiddlewareDecoderAndSanitizer { + fn send( + self: @T, + oft: ContractAddress, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/lz_middleware_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/lz_middleware_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..4d2f31dd --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/lz_middleware_decoder_and_sanitizer/lz_middleware_decoder_and_sanitizer.cairo @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod LzMiddlewareDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::lz_middleware_decoder_and_sanitizer::interface::ILzMiddlewareDecoderAndSanitizer; + use vault_allocator::integration_interfaces::lz::{MessagingFee, SendParam}; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(LzMiddlewareDecoderAndSanitizerImpl)] + impl LzMiddlewareDecoderAndSanitizer< + TContractState, +HasComponent, + > of ILzMiddlewareDecoderAndSanitizer> { + fn send( + self: @ComponentState, + oft: ContractAddress, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + oft.serialize(ref serialized_struct); + underlying_token.serialize(ref serialized_struct); + token_to_claim.serialize(ref serialized_struct); + send_param.dst_eid.serialize(ref serialized_struct); + send_param.to.serialize(ref serialized_struct); + refund_address.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..d0ab542e --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/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 IMigrationUsdcDecoderAndSanitizer { + fn swap_to_new(self: @T, amount: u256) -> Span; + fn swap_to_legacy(self: @T, amount: u256) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/migration_usdc_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/migration_usdc_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..1bdc8803 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/migration_usdc_decoder_and_sanitizer/migration_usdc_decoder_and_sanitizer.cairo @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod MigrationUsdcDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::migration_usdc_decoder_and_sanitizer::interface::IMigrationUsdcDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(MigrationUsdcDecoderAndSanitizerImpl)] + impl MigrationUsdcDecoderAndSanitizer< + TContractState, +HasComponent, + > of IMigrationUsdcDecoderAndSanitizer> { + fn swap_to_new(self: @ComponentState, amount: u256) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn swap_to_legacy(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/paradex_gigavault_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/paradex_gigavault_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..1f0807af --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/paradex_gigavault_decoder_and_sanitizer/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 IParadexGigavaultDecoderAndSanitizer { + fn request_withdrawal(self: @T, shares: u256) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/paradex_gigavault_decoder_and_sanitizer/paradex_gigavault_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/paradex_gigavault_decoder_and_sanitizer/paradex_gigavault_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..d92db075 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/paradex_gigavault_decoder_and_sanitizer/paradex_gigavault_decoder_and_sanitizer.cairo @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod ParadexGigavaultDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::paradex_gigavault_decoder_and_sanitizer::interface::IParadexGigavaultDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(ParadexGigavaultDecoderAndSanitizerImpl)] + impl ParadexGigavaultDecoderAndSanitizer< + TContractState, + +HasComponent, + +Erc4626DecoderAndSanitizerComponent::HasComponent, + > of IParadexGigavaultDecoderAndSanitizer> { + fn request_withdrawal( + self: @ComponentState, shares: u256, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} 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..8ef46897 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,9 +7,10 @@ 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::starkgate_decoder_and_sanitizer::starkgate_decoder_and_sanitizer::StarkgateDecoderAndSanitizerComponent; 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; + use vault_allocator::decoders_and_sanitizers::hyperlane_decoder_and_sanitizer::hyperlane_decoder_and_sanitizer::HyperlaneDecoderAndSanitizerComponent; component!( path: BaseDecoderAndSanitizerComponent, @@ -29,9 +30,9 @@ pub mod SimpleDecoderAndSanitizer { ); component!( - path: VesuDecoderAndSanitizerComponent, - storage: vesu_decoder_and_sanitizer, - event: VesuDecoderAndSanitizerEvent, + path: VesuV2DecoderAndSanitizerComponent, + storage: vesu_v2_decoder_and_sanitizer, + event: VesuV2DecoderAndSanitizerEvent, ); component!( @@ -41,11 +42,20 @@ pub mod SimpleDecoderAndSanitizer { ); component!( - path: MultiplyDecoderAndSanitizerComponent, - storage: multiply_decoder_and_sanitizer, - event: MultiplyDecoderAndSanitizerEvent, + path: StarkgateDecoderAndSanitizerComponent, + storage: starkgate_decoder_and_sanitizer, + event: StarkgateDecoderAndSanitizerEvent, ); + component!( + path: HyperlaneDecoderAndSanitizerComponent, + storage: hyperlane_decoder_and_sanitizer, + event: HyperlaneDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl StarkgateDecoderAndSanitizerImpl = + StarkgateDecoderAndSanitizerComponent::StarkgateDecoderAndSanitizerImpl; #[abi(embed_v0)] impl BaseDecoderAndSanitizerImpl = @@ -56,8 +66,8 @@ pub mod SimpleDecoderAndSanitizer { Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; #[abi(embed_v0)] - impl VesuDecoderAndSanitizerImpl = - VesuDecoderAndSanitizerComponent::VesuDecoderAndSanitizerImpl; + impl VesuV2DecoderAndSanitizerImpl = + VesuV2DecoderAndSanitizerComponent::VesuV2DecoderAndSanitizerImpl; #[abi(embed_v0)] impl AvnuExchangeDecoderAndSanitizerImpl = @@ -66,9 +76,15 @@ pub mod SimpleDecoderAndSanitizer { >; #[abi(embed_v0)] - impl MultiplyDecoderAndSanitizerImpl = - MultiplyDecoderAndSanitizerComponent::MultiplyDecoderAndSanitizerImpl; + impl StarknetVaultKitDecoderAndSanitizerImpl = + StarknetVaultKitDecoderAndSanitizerComponent::StarknetVaultKitDecoderAndSanitizerImpl< + ContractState, + >; + + #[abi(embed_v0)] + impl HyperlaneDecoderAndSanitizerImpl = + HyperlaneDecoderAndSanitizerComponent::HyperlaneDecoderAndSanitizerImpl; #[storage] pub struct Storage { @@ -77,13 +93,15 @@ pub mod SimpleDecoderAndSanitizer { #[substorage(v0)] pub erc4626_decoder_and_sanitizer: Erc4626DecoderAndSanitizerComponent::Storage, #[substorage(v0)] - pub vesu_decoder_and_sanitizer: VesuDecoderAndSanitizerComponent::Storage, + pub vesu_v2_decoder_and_sanitizer: VesuV2DecoderAndSanitizerComponent::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, + pub starkgate_decoder_and_sanitizer: StarkgateDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub hyperlane_decoder_and_sanitizer: HyperlaneDecoderAndSanitizerComponent::Storage, } #[event] @@ -94,12 +112,14 @@ pub mod SimpleDecoderAndSanitizer { #[flat] Erc4626DecoderAndSanitizerEvent: Erc4626DecoderAndSanitizerComponent::Event, #[flat] - VesuDecoderAndSanitizerEvent: VesuDecoderAndSanitizerComponent::Event, + VesuV2DecoderAndSanitizerEvent: VesuV2DecoderAndSanitizerComponent::Event, #[flat] AvnuExchangeDecoderAndSanitizerEvent: AvnuExchangeDecoderAndSanitizerComponent::Event, #[flat] StarknetVaultKitDecoderAndSanitizerEvent: StarknetVaultKitDecoderAndSanitizerComponent::Event, #[flat] - MultiplyDecoderAndSanitizerEvent: MultiplyDecoderAndSanitizerComponent::Event, + StarkgateDecoderAndSanitizerEvent: StarkgateDecoderAndSanitizerComponent::Event, + #[flat] + HyperlaneDecoderAndSanitizerEvent: HyperlaneDecoderAndSanitizerComponent::Event, } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..9e3d241e --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::EthAddress; + +#[starknet::interface] +pub trait IStarkgateDecoderAndSanitizer { + fn initiate_token_withdraw( + self: @T, l1_token: EthAddress, l1_recipient: EthAddress, amount: u256, + ) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/starkgate_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/starkgate_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..1c8e26e0 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_decoder_and_sanitizer/starkgate_decoder_and_sanitizer.cairo @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod StarkgateDecoderAndSanitizerComponent { + use starknet::EthAddress; + use vault_allocator::decoders_and_sanitizers::starkgate_decoder_and_sanitizer::interface::IStarkgateDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(StarkgateDecoderAndSanitizerImpl)] + impl StarkgateDecoderAndSanitizer< + TContractState, +HasComponent, + > of IStarkgateDecoderAndSanitizer> { + fn initiate_token_withdraw( + self: @ComponentState, + l1_token: EthAddress, + l1_recipient: EthAddress, + amount: u256, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + l1_token.serialize(ref serialized_struct); + l1_recipient.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..166f1e4f --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::{ContractAddress, EthAddress}; + +#[starknet::interface] +pub trait IStarkgateMiddlewareDecoderAndSanitizer { + fn initiate_token_withdraw( + self: @T, + starkgate_token_bridge: ContractAddress, + l1_token: EthAddress, + l1_recipient: EthAddress, + amount: u256, + token_to_claim: ContractAddress, + ) -> Span; +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/starkgate_middleware_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/starkgate_middleware_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..216d293d --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starkgate_middleware_decoder_and_sanitizer/starkgate_middleware_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 StarkgateMiddlewareDecoderAndSanitizerComponent { + use starknet::{ContractAddress, EthAddress}; + use vault_allocator::decoders_and_sanitizers::starkgate_middleware_decoder_and_sanitizer::interface::IStarkgateMiddlewareDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(StarkgateMiddlewareDecoderAndSanitizerImpl)] + impl StarkgateMiddlewareDecoderAndSanitizer< + TContractState, +HasComponent, + > of IStarkgateMiddlewareDecoderAndSanitizer> { + fn initiate_token_withdraw( + self: @ComponentState, + starkgate_token_bridge: ContractAddress, + l1_token: EthAddress, + l1_recipient: EthAddress, + amount: u256, + token_to_claim: ContractAddress, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + starkgate_token_bridge.serialize(ref serialized_struct); + l1_token.serialize(ref serialized_struct); + l1_recipient.serialize(ref serialized_struct); + token_to_claim.serialize(ref serialized_struct); + serialized_struct.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 index 0abf72ce..a2e48620 100644 --- 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 @@ -15,7 +15,7 @@ pub mod StarknetVaultKitDecoderAndSanitizerComponent { #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event {} - #[embeddable_as(VesuDecoderAndSanitizerImpl)] + #[embeddable_as(StarknetVaultKitDecoderAndSanitizerImpl)] impl StarknetVaultKitDecoderAndSanitizer< TContractState, +HasComponent, @@ -35,7 +35,6 @@ pub mod StarknetVaultKitDecoderAndSanitizerComponent { 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/integration_interfaces/cctp.cairo b/packages/vault_allocator/src/integration_interfaces/cctp.cairo new file mode 100644 index 00000000..6392009c --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/cctp.cairo @@ -0,0 +1,15 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait ICctpTokenBridge { + fn deposit_for_burn( + ref self: TStorage, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ); +} diff --git a/packages/vault_allocator/src/integration_interfaces/ekubo.cairo b/packages/vault_allocator/src/integration_interfaces/ekubo.cairo new file mode 100644 index 00000000..1febde9b --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/ekubo.cairo @@ -0,0 +1,180 @@ +use ekubo::types::delta::Delta; +use ekubo::types::i129::i129; +use ekubo::types::pool_price::PoolPrice; +use ekubo::types::position::Position; +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IEkuboNFT { + fn get_next_token_id(ref self: TContractState) -> u64; + fn ownerOf(self: @TContractState, token_id: u256) -> ContractAddress; + fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; +} +// Returns the dispatcher for the math library that is deployed on sepolia and mainnet with the +// given interface. +pub fn dispatcher() -> IMathLibLibraryDispatcher { + IMathLibLibraryDispatcher { + class_hash: 0x037d63129281c4c42cba74218c809ffc9e6f87ca74e0bdabb757a7f236ca59c3 + .try_into() + .unwrap(), + } +} + +#[starknet::interface] +pub trait IMathLib { + // Computes the difference in token0 reserves between the two prices given the constant + // liquidity, optionally rounded up + fn amount0_delta( + self: @TContractState, + sqrt_ratio_a: u256, + sqrt_ratio_b: u256, + liquidity: u128, + round_up: bool, + ) -> u128; + // Computes the difference in token1 reserves between the two prices given the constant + // liquidity, optionally rounded up + fn amount1_delta( + self: @TContractState, + sqrt_ratio_a: u256, + sqrt_ratio_b: u256, + liquidity: u128, + round_up: bool, + ) -> u128; + // Computes the difference in token0 and token1 given a liquidity delta, rounding up for + // positive and down for negative + fn liquidity_delta_to_amount_delta( + self: @TContractState, + sqrt_ratio: u256, + liquidity_delta: i129, + sqrt_ratio_lower: u256, + sqrt_ratio_upper: u256, + ) -> Delta; + // Computes the max liquidity that can be received for the given amount of token0 and the + // lower/upper bounds, assuming the current price is not within the bounds + fn max_liquidity_for_token0( + self: @TContractState, sqrt_ratio_lower: u256, sqrt_ratio_upper: u256, amount: u128, + ) -> u128; + // Computes the max liquidity that can be received for the given amount of token1 and the + // lower/upper bounds, assuming the current price is not within the bounds + fn max_liquidity_for_token1( + self: @TContractState, sqrt_ratio_lower: u256, sqrt_ratio_upper: u256, amount: u128, + ) -> u128; + // Computes the max liquidity that can be received for the given amount of token0 and token1 and + // the lower/upper bounds and current price + fn max_liquidity( + self: @TContractState, + sqrt_ratio: u256, + sqrt_ratio_lower: u256, + sqrt_ratio_upper: u256, + amount0: u128, + amount1: u128, + ) -> u128; + + // Compute the next sqrt ratio that will be reached from a swap given an amount of token0. Can + // return an Option::None in case of overflow or underflow + fn next_sqrt_ratio_from_amount0( + self: @TContractState, sqrt_ratio: u256, liquidity: u128, amount: i129, + ) -> Option; + // Compute the next sqrt ratio that will be reached from a swap given an amount of token1. Can + // return an Option::None in case of overflow or underflow + fn next_sqrt_ratio_from_amount1( + self: @TContractState, sqrt_ratio: u256, liquidity: u128, amount: i129, + ) -> Option; + + // Converts a tick to the sqrt ratio + fn tick_to_sqrt_ratio(self: @TContractState, tick: i129) -> u256; + + // Finds the tick s.t. tick_to_sqrt_ratio(tick) <= sqrt_ratio and tick_to_sqrt_ratio(tick + 1) > + // sqrt_ratio + fn sqrt_ratio_to_tick(self: @TContractState, sqrt_ratio: u256) -> i129; +} + + +#[derive(Copy, Drop, Serde, PartialEq)] +pub struct GetTokenInfoResult { + pub pool_price: PoolPrice, + pub liquidity: u128, + pub amount0: u128, + pub amount1: u128, + pub fees0: u128, + pub fees1: u128, +} + +#[starknet::interface] +pub trait IEkubo { + fn mint_and_deposit( + ref self: TContractState, pool_key: PoolKey, bounds: Bounds, min_liquidity: u128, + ); + fn deposit( + ref self: TContractState, id: u64, pool_key: PoolKey, bounds: Bounds, min_liquidity: u128, + ) -> u128; + fn withdraw( + ref self: TContractState, + id: u64, + pool_key: PoolKey, + bounds: Bounds, + liquidity: u128, + min_token: u128, + min_token1: u128, + collect_fees: bool, + ) -> (u128, u128); + fn collect_fees( + ref self: TContractState, id: u64, pool_key: PoolKey, bounds: Bounds, + ) -> (u128, u128); + fn get_pool_price(ref self: TContractState, pool_key: PoolKey) -> PoolPrice; + fn get_token_info( + self: @TContractState, id: u64, pool_key: PoolKey, bounds: Bounds, + ) -> GetTokenInfoResult; + fn clear(ref self: TContractState, token: ContractAddress) -> u256; + fn clear_minimum_to_recipient( + ref self: TContractState, token: ContractAddress, minimum: u256, recipient: ContractAddress, + ) -> u256; +} + +// Tick bounds for a position +#[derive(Copy, Drop, Serde, PartialEq, Hash, starknet::Store)] +pub struct Bounds { + pub lower: i129, + pub upper: i129, +} + +#[derive(Copy, Drop, Serde, PartialEq, Hash, starknet::Store)] +pub struct PoolKey { + pub token0: ContractAddress, + pub token1: ContractAddress, + pub fee: u128, + pub tick_spacing: u128, + pub extension: ContractAddress, +} + +#[derive(Copy, Drop, Serde, PartialEq, Hash)] +pub struct PositionKey { + pub salt: u64, + pub owner: ContractAddress, + pub bounds: Bounds, +} + +#[starknet::interface] +pub trait IEkuboCore { + fn get_position( + ref self: TContractState, pool_key: PoolKey, position_key: PositionKey, + ) -> Position; +} + + +#[derive(Drop, Copy, Serde, starknet::Store)] +pub struct ClSettings { + pub ekubo_positions_contract: ContractAddress, + pub bounds_settings: Bounds, + pub pool_key: PoolKey, + pub ekubo_positions_nft: ContractAddress, + pub contract_nft_id: u64, // NFT position id of Ekubo position + pub ekubo_core: ContractAddress, +} + +#[derive(Drop, Copy, Serde)] +pub struct MyPosition { + pub liquidity: u256, + pub amount0: u256, + pub amount1: u256, +} diff --git a/packages/vault_allocator/src/integration_interfaces/hyperlane.cairo b/packages/vault_allocator/src/integration_interfaces/hyperlane.cairo new file mode 100644 index 00000000..5795f58a --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/hyperlane.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. + +use alexandria_bytes::Bytes; +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IHyperlaneTokenRouter { + + /// Initiates a token transfer to a remote domain. + /// + /// This function dispatches a token transfer to the specified recipient on a remote domain, transferring + /// either an amount of tokens or a token ID. It supports optional hooks and metadata for additional + /// processing during the transfer. The function emits a `SentTransferRemote` event once the transfer is initiated. + /// + /// # Arguments + /// + /// * `destination` - A `u32` representing the destination domain. + /// * `recipient` - A `u256` representing the recipient's address. + /// * `amount_or_id` - A `u256` representing the amount of tokens or token ID to transfer. + /// * `value` - A `u256` representing the value of the transfer. + /// * `hook_metadata` - An optional `Bytes` object representing metadata for the hook. + /// * `hook` - An optional `ContractAddress` representing the contract hook to invoke during the transfer. + /// + /// # Returns + /// + /// A `u256` representing the message ID of the dispatched transfer. + /// + /// # Reference + /// + /// https://github.com/astraly-labs/hyperlane_starknet/blob/bf6504847be148714ba9b622924dd3c5ae7fbee1/cairo/crates/token/src/components/token_router.cairo#L15 + fn transfer_remote( + ref self: TContractState, + destination: u32, + recipient: u256, + amount_or_id: u256, + value: u256, + hook_metadata: Option, + hook: Option + ) -> u256; +} diff --git a/packages/vault_allocator/src/integration_interfaces/lz.cairo b/packages/vault_allocator/src/integration_interfaces/lz.cairo new file mode 100644 index 00000000..662f1f67 --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/lz.cairo @@ -0,0 +1,59 @@ +use starknet::ContractAddress; + +/// Struct representing token parameters for the OFT send() operation. +#[derive(Clone, Drop, Serde, Default)] +pub struct SendParam { + pub dst_eid: u32, // Destination endpoint ID + pub to: u256, // Recipient address + pub amount_ld: u256, // Amount to send in local decimals + pub min_amount_ld: u256, // Minimum amount to send in local decimals + pub extra_options: ByteArray, // Additional options supplied by the caller + pub compose_msg: ByteArray, // The composed message for the send() operation + pub oft_cmd: ByteArray // The OFT command to be executed +} + +#[derive(Drop, Serde, Default, PartialEq, Clone, Debug)] +pub struct MessagingFee { + pub native_fee: u256, + pub lz_token_fee: u256, +} + + +/// Struct representing OFT send result. +#[derive(Drop, Serde, Default)] +pub struct OFTSendResult { + pub message_receipt: MessageReceipt, // The LayerZero messaging receipt + pub oft_receipt: OFTReceipt // The OFT receipt information +} + +#[derive(Drop, Serde, Default, PartialEq, Clone, Debug)] +pub struct MessageReceipt { + pub guid: u256, + pub nonce: u64, + pub payees: Array, +} + +/// Struct representing OFT receipt information. +#[derive(Debug, Drop, Serde, Default, PartialEq)] +pub struct OFTReceipt { + pub amount_sent_ld: u256, // Amount of tokens ACTUALLY debited from the sender in local decimals + pub amount_received_ld: u256 // Amount of tokens to be received on the remote side +} + +#[derive(Drop, Clone, Serde, PartialEq, Debug)] +pub struct Payee { + pub receiver: ContractAddress, + pub native_amount: u256, + pub lz_token_amount: u256, +} + + +#[starknet::interface] +pub trait IOFT { + fn send( + ref self: TContractState, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) -> OFTSendResult; +} diff --git a/packages/vault_allocator/src/integration_interfaces/paradex_gigavault.cairo b/packages/vault_allocator/src/integration_interfaces/paradex_gigavault.cairo new file mode 100644 index 00000000..1c07c2ef --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/paradex_gigavault.cairo @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IParadexGigaVault { + fn request_withdrawal(self: @T, shares: u256); +} diff --git a/packages/vault_allocator/src/integration_interfaces/starkgate.cairo b/packages/vault_allocator/src/integration_interfaces/starkgate.cairo new file mode 100644 index 00000000..2844372d --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/starkgate.cairo @@ -0,0 +1,19 @@ +use starknet::{ContractAddress, EthAddress}; + + +#[starknet::interface] +pub trait IStarkgateABI { + fn get_l1_token(self: @TContractState, l2_token: ContractAddress) -> EthAddress; + fn get_l2_token(self: @TContractState, l1_token: EthAddress) -> ContractAddress; + fn initiate_token_withdraw( + ref self: TContractState, l1_token: EthAddress, l1_recipient: EthAddress, amount: u256, + ); +} + + +#[starknet::interface] +pub trait IStarkgateABIInitiateTokenWithdraw { + fn initiate_token_withdraw( + ref self: TContractState, l1_token: EthAddress, l1_recipient: EthAddress, amount: u256, + ); +} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index 5701d8b5..ac2458a5 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -16,7 +16,13 @@ pub mod manager { pub mod integration_interfaces { pub mod avnu; + pub mod cctp; + pub mod ekubo; + pub mod hyperlane; + pub mod lz; + pub mod paradex_gigavault; pub mod pragma; + pub mod starkgate; pub mod vesu_v1; pub mod vesu_v2; } @@ -35,16 +41,67 @@ pub mod periphery { } pub mod middlewares { + pub mod paradex_gigavault_middleware { + pub mod interface; + pub mod paradex_gigavault_middleware; + } pub mod avnu_middleware { pub mod avnu_middleware; pub mod errors; pub mod interface; } + + pub mod starkgate_middleware { + pub mod errors; + pub mod interface; + pub mod starkgate_middleware; + } + pub mod hyperlane_middleware { + pub mod errors; + pub mod hyperlane_middleware; + pub mod interface; + } + + pub mod cctp_middleware { + pub mod cctp_middleware; + pub mod errors; + pub mod interface; + } + pub mod lz_middleware { + pub mod errors; + pub mod interface; + pub mod lz_middleware; + } + pub mod base_middleware { + pub mod base_middleware; + pub mod errors; + pub mod interface; + } +} + +pub mod adapters { + pub mod ekubo_adapter { + pub mod ekubo_adapter; + pub mod errors; + pub mod interface; + } +} + + +pub mod pods { + pub mod base_pod; + pub mod components { + pub mod asset_transfer_pod; + pub mod errors; + pub mod interface; + } } pub mod decoders_and_sanitizers { pub mod base_decoder_and_sanitizer; pub mod decoder_custom_types; + pub mod forgeyields_paradex_decoder_and_sanitizer; + pub mod fyWBTC_decoder_and_sanitizer; pub mod interface; pub mod simple_decoder_and_sanitizer; pub mod vesu_v2_specific_decoder_and_sanitizer; @@ -73,6 +130,55 @@ pub mod decoders_and_sanitizers { pub mod interface; pub mod multiply_decoder_and_sanitizer; } + pub mod paradex_gigavault_decoder_and_sanitizer { + pub mod interface; + pub mod paradex_gigavault_decoder_and_sanitizer; + } + pub mod starkgate_decoder_and_sanitizer { + pub mod interface; + pub mod starkgate_decoder_and_sanitizer; + } + pub mod starkgate_middleware_decoder_and_sanitizer { + pub mod interface; + pub mod starkgate_middleware_decoder_and_sanitizer; + } + pub mod hyperlane_decoder_and_sanitizer { + pub mod hyperlane_decoder_and_sanitizer; + pub mod interface; + } + pub mod hyperlane_middleware_decoder_and_sanitizer { + pub mod hyperlane_middleware_decoder_and_sanitizer; + pub mod interface; + } + pub mod cctp_decoder_and_sanitizer { + pub mod cctp_decoder_and_sanitizer; + pub mod interface; + } + pub mod lz_decoder_and_sanitizer { + pub mod interface; + pub mod lz_decoder_and_sanitizer; + } + pub mod lz_middleware_decoder_and_sanitizer { + pub mod interface; + pub mod lz_middleware_decoder_and_sanitizer; + } + pub mod cctp_middleware_decoder_and_sanitizer { + pub mod cctp_middleware_decoder_and_sanitizer; + pub mod interface; + } + pub mod ekubo_adapter_decoder_and_sanitizer { + pub mod ekubo_adapter_decoder_and_sanitizer; + pub mod interface; + } + + pub mod defi_spring_decoder_and_sanitizer { + pub mod defi_spring_decoder_and_sanitizer; + pub mod interface; + } + pub mod migration_usdc_decoder_and_sanitizer { + pub mod interface; + pub mod migration_usdc_decoder_and_sanitizer; + } } pub mod mocks { @@ -84,8 +190,19 @@ pub mod mocks { #[cfg(test)] pub mod test { - // pub mod creator; + pub mod creator { + // pub mod creator; + pub mod creator_fyWBTC; + pub mod creator_sdk_test; + } pub mod utils; + pub mod middleware { + pub mod base_middleware; + pub mod cctp_middleware; + pub mod hyperlane_middleware; + pub mod lz_middleware; + pub mod starkgate_middleware; + } pub mod units { pub mod manager; pub mod vault_allocator; @@ -98,6 +215,10 @@ pub mod test { pub mod scenarios { pub mod stable_carry_loop; } + + pub mod adapters { + pub mod ekubo_adapter; + } } @@ -106,7 +227,15 @@ pub mod merkle_tree { pub mod registery; pub mod integrations { pub mod avnu; + pub mod cctp; + pub mod defi_spring; + pub mod ekubo_adapter; pub mod erc4626; + pub mod extended; + pub mod hyperlane; + pub mod lz; + pub mod migration_usdc; + pub mod starkgate; pub mod starknet_vault_kit_strategies; pub mod vesu_v1; pub mod vesu_v2; diff --git a/packages/vault_allocator/src/manager/manager.cairo b/packages/vault_allocator/src/manager/manager.cairo index c47d9d2c..3cea20b3 100644 --- a/packages/vault_allocator/src/manager/manager.cairo +++ b/packages/vault_allocator/src/manager/manager.cairo @@ -49,9 +49,13 @@ pub mod Manager { #[event] #[derive(Drop, starknet::Event)] pub enum Event { + #[flat] SRC5Event: SRC5Component::Event, + #[flat] AccessControlEvent: AccessControlComponent::Event, + #[flat] UpgradeableEvent: UpgradeableComponent::Event, + #[flat] PausableEvent: PausableComponent::Event, } diff --git a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo index f0182752..e793fa82 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo @@ -8,7 +8,7 @@ pub struct AvnuConfig { pub buy_token: ContractAddress, } - +// work for interacting directly with the avnu router or using the avnu middleware pub fn _add_avnu_leafs( ref leafs: Array, ref leaf_index: u256, diff --git a/packages/vault_allocator/src/merkle_tree/integrations/cctp.cairo b/packages/vault_allocator/src/merkle_tree/integrations/cctp.cairo new file mode 100644 index 00000000..5e728120 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/cctp.cairo @@ -0,0 +1,195 @@ +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 CctpConfig { + pub cctp_contract: ContractAddress, + pub burn_token: ContractAddress, + pub destination_domain: u32, + pub mint_recipient: u256, + pub destination_caller: u256, +} + + +pub fn _add_cctp_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + cctp_configs: Span, +) { + for i in 0..cctp_configs.len() { + let config = cctp_configs.at(i); + let cctp_contract = *config.cctp_contract; + let burn_token = *config.burn_token; + let destination_domain = *config.destination_domain; + let mint_recipient = *config.mint_recipient; + let destination_caller = *config.destination_caller; + + // Approval for burn_token to the cctp contract + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: burn_token, + selector: selector!("approve"), + argument_addresses: array![cctp_contract.into()].span(), + description: "Approve" + + " " + + "cctp" + + " " + + "to spend" + + " " + + get_symbol(burn_token), + }, + ); + leaf_index += 1; + + // Deposit for burn operation + let mut argument_addresses_burn = ArrayTrait::new(); + + // destination_domain + destination_domain.serialize(ref argument_addresses_burn); + + // mint_recipient + mint_recipient.serialize(ref argument_addresses_burn); + + // burn_token + burn_token.serialize(ref argument_addresses_burn); + + // destination_caller + destination_caller.serialize(ref argument_addresses_burn); + + // Format addresses for description + let recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array(@mint_recipient, 16); + let domain_felt: felt252 = destination_domain.into(); + let domain_str: ByteArray = FormatAsByteArray::format_as_byte_array(@domain_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: cctp_contract, + selector: selector!("deposit_for_burn"), + argument_addresses: argument_addresses_burn.span(), + description: "CCTP: burn" + + " " + + get_symbol(burn_token) + + " " + + "on domain" + + " " + + domain_str + + " " + + "to recipient" + + " " + + recipient_str, + }, + ); + leaf_index += 1; + } +} + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct CctpMiddlewareConfig { + pub middleware: ContractAddress, + pub burn_token: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub mint_recipient: u256, + pub destination_caller: u256, +} + + +pub fn _add_cctp_middleware_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + cctp_configs: Span, +) { + for i in 0..cctp_configs.len() { + let config = cctp_configs.at(i); + let middleware = *config.middleware; + let burn_token = *config.burn_token; + let token_to_claim = *config.token_to_claim; + let destination_domain = *config.destination_domain; + let mint_recipient = *config.mint_recipient; + let destination_caller = *config.destination_caller; + + let middleware_felt: felt252 = middleware.into(); + let middleware_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @middleware_felt, 16, + ); + + // Approval for burn_token to the middleware + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: burn_token, + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "cctp_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(burn_token), + }, + ); + leaf_index += 1; + + // Deposit for burn operation + let mut argument_addresses_burn = ArrayTrait::new(); + + // destination_domain + destination_domain.serialize(ref argument_addresses_burn); + + // mint_recipient + mint_recipient.serialize(ref argument_addresses_burn); + + // burn_token + burn_token.serialize(ref argument_addresses_burn); + + // token_to_claim + token_to_claim.serialize(ref argument_addresses_burn); + + // destination_caller + destination_caller.serialize(ref argument_addresses_burn); + + // Format addresses for description + let recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array(@mint_recipient, 16); + let domain_felt: felt252 = destination_domain.into(); + let domain_str: ByteArray = FormatAsByteArray::format_as_byte_array(@domain_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: middleware, + selector: selector!("deposit_for_burn"), + argument_addresses: argument_addresses_burn.span(), + description: "CCTP: burn" + + " " + + get_symbol(burn_token) + + " " + + "for" + + " " + + get_symbol(token_to_claim) + + " " + + "on domain" + + " " + + domain_str + + " " + + "to recipient" + + " " + + recipient_str, + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/defi_spring.cairo b/packages/vault_allocator/src/merkle_tree/integrations/defi_spring.cairo new file mode 100644 index 00000000..e8395d74 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/defi_spring.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. + +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 DefiSpringConfig { + pub claim_contract: ContractAddress, + pub reward_token: ContractAddress, +} + + +pub fn _add_defi_spring_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + defi_spring_configs: Span, +) { + for i in 0..defi_spring_configs.len() { + let config = defi_spring_configs.at(i); + let claim_contract = *config.claim_contract; + let reward_token = *config.reward_token; + + let claim_contract_felt: felt252 = claim_contract.into(); + let claim_contract_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @claim_contract_felt, 16, + ); + + // Claim rewards + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: claim_contract, + selector: selector!("claim"), + argument_addresses: array![].span(), + description: "DefiSpring: claim" + + " " + + get_symbol(reward_token) + + " " + + "from" + + " " + + claim_contract_str, + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/ekubo_adapter.cairo b/packages/vault_allocator/src/merkle_tree/integrations/ekubo_adapter.cairo new file mode 100644 index 00000000..91496cfc --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/ekubo_adapter.cairo @@ -0,0 +1,156 @@ +use starknet::ContractAddress; +use vault_allocator::adapters::ekubo_adapter::interface::{ + IEkuboAdapterDispatcher, IEkuboAdapterDispatcherTrait, +}; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +pub fn _add_ekubo_adapter_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + ekubo_adapter: ContractAddress, +) { + let adapter_disp = IEkuboAdapterDispatcher { contract_address: ekubo_adapter }; + let pool_key = adapter_disp.get_pool_key(); + let token0 = pool_key.token0; + let token1 = pool_key.token1; + + // Approvals for token0 to ekubo adapter + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: token0, + selector: selector!("approve"), + argument_addresses: array![ekubo_adapter.into()].span(), + description: "Approve" + + " " + + "ekubo_adapter" + + " " + + "to spend" + + " " + + get_symbol(token0), + }, + ); + leaf_index += 1; + + // Approvals for token1 to ekubo adapter + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: token1, + selector: selector!("approve"), + argument_addresses: array![ekubo_adapter.into()].span(), + description: "Approve" + + " " + + "ekubo_adapter" + + " " + + "to spend" + + " " + + get_symbol(token1), + }, + ); + leaf_index += 1; + + // Deposit liquidity + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: ekubo_adapter, + selector: selector!("deposit_liquidity"), + argument_addresses: array![].span(), + description: "Deposit liquidity to Ekubo" + + "for" + + " " + + get_symbol(token0) + + " " + + "and" + + " " + + get_symbol(token1) + + " " + + "via" + + " " + + "ekubo_adapter", + }, + ); + leaf_index += 1; + + // Withdraw liquidity + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: ekubo_adapter, + selector: selector!("withdraw_liquidity"), + argument_addresses: array![].span(), + description: "Withdraw liquidity from Ekubo" + + " " + + "for" + + " " + + get_symbol(token0) + + " " + + "and" + + " " + + get_symbol(token1) + + " " + + "via" + + " " + + "ekubo_adapter", + }, + ); + leaf_index += 1; + + // Collect fees + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: ekubo_adapter, + selector: selector!("collect_fees"), + argument_addresses: array![].span(), + description: "Collect fees from Ekubo" + + " " + + "for" + + " " + + get_symbol(token0) + + " " + + "and" + + " " + + get_symbol(token1) + + " " + + "via" + + " " + + "ekubo_adapter", + }, + ); + leaf_index += 1; + + // Harvest rewards + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: ekubo_adapter, + selector: selector!("harvest"), + argument_addresses: array![].span(), + description: "Harvest rewards from Ekubo" + + " " + + "for" + + " " + + get_symbol(token0) + + " " + + "and" + + " " + + get_symbol(token1) + + " " + + "via" + + " " + + "ekubo_adapter", + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/extended.cairo b/packages/vault_allocator/src/merkle_tree/integrations/extended.cairo new file mode 100644 index 00000000..1677fee1 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/extended.cairo @@ -0,0 +1,37 @@ +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::ManageLeaf; + + +pub fn _add_extended_leafs( + ref leafs: Array, + ref leaf_index: u256, + extended_recipient: ContractAddress, + usdc_address: ContractAddress, + vault_number: felt252, + decoder_and_sanitizer: ContractAddress, +) { + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: usdc_address, + selector: selector!("approve"), + argument_addresses: array![extended_recipient.into()].span(), + description: "Approve extended recipient to spend USDC", + }, + ); + leaf_index += 1; + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: extended_recipient, + selector: selector!("deposit"), + argument_addresses: array![vault_number.into()].span(), + description: "Deposit USDC into extended recipient", + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/hyperlane.cairo b/packages/vault_allocator/src/merkle_tree/integrations/hyperlane.cairo new file mode 100644 index 00000000..3e84a261 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/hyperlane.cairo @@ -0,0 +1,202 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::registery::STRK; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct HyperlaneConfig { + pub warp_route_token: ContractAddress, + pub destination: u32, + pub recipient: u256, +} + + +pub fn _add_hyperlane_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + hyperlane_configs: Span, +) { + for i in 0..hyperlane_configs.len() { + let config = hyperlane_configs.at(i); + let warp_route_token = *config.warp_route_token; + let destination = *config.destination; + let recipient = *config.recipient; + + // Approval for STRK (gas token) to the warp route token + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: STRK(), + selector: selector!("approve"), + argument_addresses: array![warp_route_token.into()].span(), + description: "Approve" + + " " + + "hyperlane" + + " " + + "to spend" + + " " + + get_symbol(STRK()), + }, + ); + leaf_index += 1; + + // Transfer remote operation + let mut argument_addresses_transfer = ArrayTrait::new(); + + // destination + destination.serialize(ref argument_addresses_transfer); + + // recipient + recipient.serialize(ref argument_addresses_transfer); + + // Format addresses for description + let recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array(@recipient, 16); + let domain_felt: felt252 = destination.into(); + let domain_str: ByteArray = FormatAsByteArray::format_as_byte_array(@domain_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: warp_route_token, + selector: selector!("transfer_remote"), + argument_addresses: argument_addresses_transfer.span(), + description: "Hyperlane: transfer" + + " " + + get_symbol(warp_route_token) + + " " + + "on domain" + + " " + + domain_str + + " " + + "to recipient" + + " " + + recipient_str, + }, + ); + leaf_index += 1; + } +} + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct HyperlaneMiddlewareConfig { + pub middleware: ContractAddress, + pub token_to_bridge: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub recipient: u256, +} + + +pub fn _add_hyperlane_middleware_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + hyperlane_configs: Span, +) { + for i in 0..hyperlane_configs.len() { + let config = hyperlane_configs.at(i); + let middleware = *config.middleware; + let token_to_bridge = *config.token_to_bridge; + let token_to_claim = *config.token_to_claim; + let destination_domain = *config.destination_domain; + let recipient = *config.recipient; + + let middleware_felt: felt252 = middleware.into(); + let middleware_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @middleware_felt, 16, + ); + + // Approval for token_to_bridge to the middleware + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: token_to_bridge, + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "hyperlane_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(token_to_bridge), + }, + ); + leaf_index += 1; + + // Approval for gas token (STRK) to the middleware + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: STRK(), + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "hyperlane_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(STRK()), + }, + ); + leaf_index += 1; + + // Bridge token operation + let mut argument_addresses_bridge = ArrayTrait::new(); + + // token_to_bridge + token_to_bridge.serialize(ref argument_addresses_bridge); + + // token_to_claim + token_to_claim.serialize(ref argument_addresses_bridge); + + // destination_domain + destination_domain.serialize(ref argument_addresses_bridge); + + // recipient + recipient.serialize(ref argument_addresses_bridge); + + // Format addresses for description + let recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array(@recipient, 16); + let domain_felt: felt252 = destination_domain.into(); + let domain_str: ByteArray = FormatAsByteArray::format_as_byte_array(@domain_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: middleware, + selector: selector!("bridge_token"), + argument_addresses: argument_addresses_bridge.span(), + description: "Hyperlane: bridge" + + " " + + get_symbol(token_to_bridge) + + " " + + "for" + + " " + + get_symbol(token_to_claim) + + " " + + "on domain" + + " " + + domain_str + + " " + + "to recipient" + + " " + + recipient_str, + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/lz.cairo b/packages/vault_allocator/src/merkle_tree/integrations/lz.cairo new file mode 100644 index 00000000..53082b90 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/lz.cairo @@ -0,0 +1,238 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::registery::STRK; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct LzConfig { + pub oft: ContractAddress, + pub underlying_token: ContractAddress, // If oft != underlying_token, it's adapter OFT + pub dst_eid: u32, + pub to: u256, +} + + +pub fn _add_lz_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + vault_allocator: ContractAddress, + lz_configs: Span, +) { + for i in 0..lz_configs.len() { + let config = lz_configs.at(i); + let oft = *config.oft; + let underlying_token = *config.underlying_token; + let dst_eid = *config.dst_eid; + let to = *config.to; + + // If adapter OFT (oft != underlying_token), we need to approve the underlying token + if oft != underlying_token { + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: underlying_token, + selector: selector!("approve"), + argument_addresses: array![oft.into()].span(), + description: "Approve" + + " " + + "lz_oft" + + " " + + "to spend" + + " " + + get_symbol(underlying_token), + }, + ); + leaf_index += 1; + } + + // Approval for gas token (STRK) to the OFT + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: STRK(), + selector: selector!("approve"), + argument_addresses: array![oft.into()].span(), + description: "Approve" + + " " + + "lz_oft" + + " " + + "to spend" + + " " + + get_symbol(STRK()), + }, + ); + leaf_index += 1; + + // Send operation + let mut argument_addresses_send = ArrayTrait::new(); + + // dst_eid + dst_eid.serialize(ref argument_addresses_send); + + // to + to.serialize(ref argument_addresses_send); + + // refund_address (vault_allocator receives excess fees) + vault_allocator.serialize(ref argument_addresses_send); + + // Format addresses for description + let to_str: ByteArray = FormatAsByteArray::format_as_byte_array(@to, 16); + let dst_eid_felt: felt252 = dst_eid.into(); + let dst_eid_str: ByteArray = FormatAsByteArray::format_as_byte_array(@dst_eid_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: oft, + selector: selector!("send"), + argument_addresses: argument_addresses_send.span(), + description: "LayerZero: send" + + " " + + get_symbol(underlying_token) + + " " + + "to eid" + + " " + + dst_eid_str + + " " + + "to" + + " " + + to_str, + }, + ); + leaf_index += 1; + } +} + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct LzMiddlewareConfig { + pub middleware: ContractAddress, + pub oft: ContractAddress, + pub underlying_token: ContractAddress, // If oft != underlying_token, it's adapter OFT + pub token_to_claim: ContractAddress, + pub dst_eid: u32, + pub to: u256, +} + + +pub fn _add_lz_middleware_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + vault_allocator: ContractAddress, + lz_configs: Span, +) { + for i in 0..lz_configs.len() { + let config = lz_configs.at(i); + let middleware = *config.middleware; + let oft = *config.oft; + let underlying_token = *config.underlying_token; + let token_to_claim = *config.token_to_claim; + let dst_eid = *config.dst_eid; + let to = *config.to; + + let middleware_felt: felt252 = middleware.into(); + let middleware_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @middleware_felt, 16, + ); + + // Approval for underlying_token to the middleware + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: underlying_token, + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "lz_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(underlying_token), + }, + ); + leaf_index += 1; + + // Approval for gas token (STRK) to the middleware + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: STRK(), + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "lz_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(STRK()), + }, + ); + leaf_index += 1; + + // Send operation on middleware + let mut argument_addresses_send = ArrayTrait::new(); + + // oft + oft.serialize(ref argument_addresses_send); + + // underlying_token + underlying_token.serialize(ref argument_addresses_send); + + // token_to_claim + token_to_claim.serialize(ref argument_addresses_send); + + // dst_eid + dst_eid.serialize(ref argument_addresses_send); + + // to + to.serialize(ref argument_addresses_send); + + // refund_address (vault_allocator receives excess fees) + vault_allocator.serialize(ref argument_addresses_send); + + // Format addresses for description + let to_str: ByteArray = FormatAsByteArray::format_as_byte_array(@to, 16); + let dst_eid_felt: felt252 = dst_eid.into(); + let dst_eid_str: ByteArray = FormatAsByteArray::format_as_byte_array(@dst_eid_felt, 16); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: middleware, + selector: selector!("send"), + argument_addresses: argument_addresses_send.span(), + description: "LayerZero: send via middleware" + + " " + + get_symbol(underlying_token) + + " " + + "for" + + " " + + get_symbol(token_to_claim) + + " " + + "to eid" + + " " + + dst_eid_str + + " " + + "to" + + " " + + to_str, + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/migration_usdc.cairo b/packages/vault_allocator/src/merkle_tree/integrations/migration_usdc.cairo new file mode 100644 index 00000000..23dbe2e5 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/migration_usdc.cairo @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct MigrationUsdcConfig { + pub migration_contract: ContractAddress, + pub legacy_usdc: ContractAddress, + pub new_usdc: ContractAddress, +} + + +pub fn _add_migration_usdc_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + migration_usdc_config: MigrationUsdcConfig, +) { + let migration_contract = migration_usdc_config.migration_contract; + let legacy_usdc = migration_usdc_config.legacy_usdc; + let new_usdc = migration_usdc_config.new_usdc; + + // Approval for migration contract to spend legacy USDC + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: legacy_usdc, + selector: selector!("approve"), + argument_addresses: array![migration_contract.into()].span(), + description: "Approve migration contract to spend " + + get_symbol(legacy_usdc), + }, + ); + leaf_index += 1; + + // Approval for migration contract to spend new USDC + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: new_usdc, + selector: selector!("approve"), + argument_addresses: array![migration_contract.into()].span(), + description: "Approve migration contract to spend " + + get_symbol(new_usdc), + }, + ); + leaf_index += 1; + + // swap_to_new: convert legacy USDC to new USDC + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: migration_contract, + selector: selector!("swap_to_new"), + argument_addresses: array![].span(), + description: "Migration: swap " + + get_symbol(legacy_usdc) + + " to " + + get_symbol(new_usdc), + }, + ); + leaf_index += 1; + + // swap_to_legacy: convert new USDC back to legacy USDC + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: migration_contract, + selector: selector!("swap_to_legacy"), + argument_addresses: array![].span(), + description: "Migration: swap " + + get_symbol(new_usdc) + + " to " + + get_symbol(legacy_usdc), + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/starkgate.cairo b/packages/vault_allocator/src/merkle_tree/integrations/starkgate.cairo new file mode 100644 index 00000000..3e1b57d5 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/starkgate.cairo @@ -0,0 +1,154 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::{ContractAddress, EthAddress}; +use vault_allocator::integration_interfaces::starkgate::{ + IStarkgateABIDispatcher, IStarkgateABIDispatcherTrait, +}; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct StarkgateConfig { + pub l2_bridge: ContractAddress, + pub l2_token: ContractAddress, + pub l1_recipient: EthAddress, +} + + +pub fn _add_starkgate_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + starkgate_configs: Span, +) { + for i in 0..starkgate_configs.len() { + let config = starkgate_configs.at(i); + let l2_bridge = *config.l2_bridge; + let l2_token = *config.l2_token; + let l1_recipient = *config.l1_recipient; + + let starkgate_disp = IStarkgateABIDispatcher { contract_address: l2_bridge }; + let l1_token = starkgate_disp.get_l1_token(l2_token); + + // Format addresses for description + let l1_recipient_felt: felt252 = l1_recipient.into(); + let l1_recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @l1_recipient_felt, 16, + ); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: l2_bridge, + selector: selector!("initiate_token_withdraw"), + argument_addresses: array![l1_token.into(), l1_recipient.into()].span(), + description: "Starkgate: withdraw" + + " " + + get_symbol(l2_token) + + " " + + "to recipient" + + " " + + l1_recipient_str, + }, + ); + leaf_index += 1; + } +} + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct StarkgateMiddlewareConfig { + pub middleware: ContractAddress, + pub l2_bridge: ContractAddress, + pub l2_token: ContractAddress, + pub l1_recipient: EthAddress, + pub token_to_claim: ContractAddress, +} + + +pub fn _add_starkgate_middleware_leafs( + ref leafs: Array, + ref leaf_index: u256, + decoder_and_sanitizer: ContractAddress, + starkgate_configs: Span, +) { + for i in 0..starkgate_configs.len() { + let config = starkgate_configs.at(i); + let middleware = *config.middleware; + let l2_bridge = *config.l2_bridge; + let l2_token = *config.l2_token; + let l1_recipient = *config.l1_recipient; + let token_to_claim = *config.token_to_claim; + + let middleware_felt: felt252 = middleware.into(); + let middleware_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @middleware_felt, 16, + ); + + // Approval for middleware to spend l2_token + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: l2_token, + selector: selector!("approve"), + argument_addresses: array![middleware.into()].span(), + description: "Approve" + + " " + + "starkgate_middleware" + + "_" + + middleware_str.clone() + + " " + + "to spend" + + " " + + get_symbol(l2_token), + }, + ); + leaf_index += 1; + + let starkgate_disp = IStarkgateABIDispatcher { contract_address: l2_bridge }; + let l1_token = starkgate_disp.get_l1_token(l2_token); + + // Initiate token withdraw operation + let mut argument_addresses_withdraw = ArrayTrait::new(); + + // starkgate_token_bridge + l2_bridge.serialize(ref argument_addresses_withdraw); + + // l1_token + l1_token.serialize(ref argument_addresses_withdraw); + + // l1_recipient + l1_recipient.serialize(ref argument_addresses_withdraw); + + // token_to_claim + token_to_claim.serialize(ref argument_addresses_withdraw); + + // Format addresses for description + let l1_recipient_felt: felt252 = l1_recipient.into(); + let l1_recipient_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @l1_recipient_felt, 16, + ); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: middleware, + selector: selector!("initiate_token_withdraw"), + argument_addresses: argument_addresses_withdraw.span(), + description: "Starkgate: bridge" + + " " + + get_symbol(l2_token) + + " " + + "for" + + " " + + get_symbol(token_to_claim) + + " " + + "to recipient" + + " " + + l1_recipient_str, + }, + ); + 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 index 1335e298..148e02a3 100644 --- a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo @@ -51,10 +51,29 @@ pub fn _add_vesu_v2_leafs( // 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); + // APPROVAL of debt asset to the pool contract (needed for repayment) + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: debt_asset, + selector: selector!("approve"), + argument_addresses: array![pool_contract.into()].span(), + description: "Approve" + + " " + + "pool contract" + + "_" + + pool_contract_str.clone() + + " " + + "to spend" + + " " + + get_symbol(debt_asset), + }, + ); + leaf_index += 1; + // MODIFY POSITION let mut argument_addresses_modify_position = ArrayTrait::new(); diff --git a/packages/vault_allocator/src/merkle_tree/registery.cairo b/packages/vault_allocator/src/merkle_tree/registery.cairo index 2e385a05..25e18e3b 100644 --- a/packages/vault_allocator/src/merkle_tree/registery.cairo +++ b/packages/vault_allocator/src/merkle_tree/registery.cairo @@ -17,6 +17,15 @@ pub fn USDC() -> ContractAddress { 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8.try_into().unwrap() } +pub fn USN() -> ContractAddress { + 0x02411565ef1a14decfbe83d2e987cced918cd752508a3d9c55deb67148d14d17.try_into().unwrap() +} + + +pub fn USDC_CCTP() -> ContractAddress { + 0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb.try_into().unwrap() +} + pub fn USDT() -> ContractAddress { 0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8.try_into().unwrap() } @@ -33,6 +42,26 @@ pub fn wstETH() -> ContractAddress { 0x0057912720381af14b0e5c87aa4718ed5e527eab60b3801ebf702ab09139e38b.try_into().unwrap() } +// Starkgate +pub fn STARKGATE_USDC_BRIDGE() -> ContractAddress { + 0x05cd48fccbfd8aa2773fe22c217e808319ffcc1c5a6a463f7d8fa2da48218196.try_into().unwrap() +} + +// Price router +pub fn PRICE_ROUTER() -> ContractAddress { + 0x566440457a0c6189c5dcbb661bd8f9a09961c78bfb50f6648977e9ca5972619.try_into().unwrap() +} + +// CCTP +pub fn CCTP_USDC_BRIDGE() -> ContractAddress { + 0x7d421b9ca8aa32df259965cda8acb93f7599f69209a41872ae84638b2a20f2a.try_into().unwrap() +} + +// LZ +pub fn LZ_WBTC_OFT_ADAPTER() -> ContractAddress { + 0x069ac468d7ad7324c2807d9422630c650729a46121f023b11a647edf173562f9.try_into().unwrap() +} + // VESU 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..c4d2f468 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -8,14 +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, - StoragePointerWriteAccess, - }; - use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::integration_interfaces::avnu::{ IAvnuExchangeDispatcher, IAvnuExchangeDispatcherTrait, @@ -23,19 +17,21 @@ pub mod AvnuMiddleware { 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::{ - IPriceRouterDispatcher, IPriceRouterDispatcherTrait, - }; - + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; #[storage] struct Storage { @@ -43,13 +39,8 @@ pub mod AvnuMiddleware { ownable: OwnableComponent::Storage, #[substorage(v0)] upgradeable: UpgradeableComponent::Storage, - price_router: IPriceRouterDispatcher, - vault_allocator: ContractAddress, - slippage: u16, - period: u64, - allowed_calls_per_period: u64, - current_window_id: u64, - window_call_count: u64, + #[substorage(v0)] + base_middleware: BaseMiddlewareComponent::Storage, } #[event] @@ -59,6 +50,8 @@ pub mod AvnuMiddleware { OwnableEvent: OwnableComponent::Event, #[flat] UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, ConfigUpdated: ConfigUpdated, } @@ -81,18 +74,13 @@ pub mod AvnuMiddleware { allowed_calls_per_period: u64, ) { self.ownable.initializer(owner); - self.price_router.write(IPriceRouterDispatcher { contract_address: price_router }); - self.vault_allocator.write(vault_allocator); - self._set_config(slippage, period, allowed_calls_per_period) + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); } - #[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 { @@ -100,26 +88,6 @@ pub mod AvnuMiddleware { AVNU_ROUTER() } - fn price_router(self: @ContractState) -> ContractAddress { - self.price_router.read().contract_address - } - - fn vault_allocator(self: @ContractState) -> ContractAddress { - self.vault_allocator.read() - } - - fn config(self: @ContractState) -> (u16, u64, u64) { - (self.slippage.read(), self.period.read(), self.allowed_calls_per_period.read()) - } - fn set_config( - ref self: ContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, - ) { - self.ownable.assert_only_owner(); - 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, @@ -133,7 +101,7 @@ pub mod AvnuMiddleware { routes: Array, ) -> u256 { let caller = get_caller_address(); - self.enforce_rate_limit(caller); + self.base_middleware.enforce_rate_limit(caller); let this = get_contract_address(); if (sell_token_amount == Zero::zero()) { @@ -150,17 +118,10 @@ pub mod AvnuMiddleware { 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 - .price_router - .read() - .get_value(sell_token_address, sell_token_amount, buy_token_address); - - let computed_min = math::u256_mul_div( - quote_out, - (BPS_SCALE - self.slippage.read()).into(), - BPS_SCALE.into(), - math::Rounding::Ceil, - ); + + let computed_min = self + .base_middleware + .get_computed_min(sell_token_address, sell_token_amount, buy_token_address); let min_out = if buy_token_min_amount < computed_min { computed_min @@ -190,50 +151,4 @@ pub mod AvnuMiddleware { out } } - - - #[generate_trait] - pub impl InternalFunctions of InternalFunctionsTrait { - fn enforce_rate_limit(ref self: ContractState, caller: ContractAddress) { - if (caller != self.vault_allocator.read()) { - Errors::caller_not_vault_allocator(); - } - - let period = self.period.read(); - let ts: u64 = get_block_timestamp(); - 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 = self.allowed_calls_per_period.read(); - - if (next > allowed) { - Errors::rate_limit_exceeded(next, allowed); - } - self.window_call_count.write(next); - } - - fn _set_config(ref self: ContractState, slippage: u16, period: u64, allowed: u64) { - if (slippage >= BPS_SCALE) { - Errors::slippage_exceeds_max(slippage); - } - if (period.is_zero()) { - Errors::period_zero(); - } - if (allowed.is_zero()) { - Errors::allowed_calls_per_period_zero(); - } - - 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); - } - } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo index 7b424486..18d2bc61 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo @@ -8,10 +8,6 @@ use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; #[starknet::interface] pub trait IAvnuMiddleware { fn avnu_router(self: @T) -> ContractAddress; - fn price_router(self: @T) -> ContractAddress; - 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, diff --git a/packages/vault_allocator/src/middlewares/base_middleware/base_middleware.cairo b/packages/vault_allocator/src/middlewares/base_middleware/base_middleware.cairo new file mode 100644 index 00000000..4dd75ab1 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/base_middleware/base_middleware.cairo @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod BaseMiddlewareComponent { + const BPS_SCALE: u16 = 10_000; + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::access::ownable::OwnableComponent::InternalTrait as OwnableInternalTrait; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent::InternalTrait as UpgradeableInternalTrait; + use openzeppelin::utils::math; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_block_timestamp}; + use vault_allocator::middlewares::base_middleware::errors::Errors; + use vault_allocator::middlewares::base_middleware::interface::IBaseMiddleware; + use vault_allocator::periphery::price_router::interface::{ + IPriceRouterDispatcher, IPriceRouterDispatcherTrait, + }; + + + #[storage] + pub struct Storage { + pub vault_allocator: ContractAddress, + pub price_router: IPriceRouterDispatcher, + pub slippage: u16, + pub period: u64, + pub allowed_calls_per_period: u64, + pub current_window_id: u64, + pub window_call_count: u64, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + ConfigSet: ConfigSet, + VaultAllocatorSet: VaultAllocatorSet, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct ConfigSet { + pub slippage: u16, + pub period: u64, + pub allowed_calls_per_period: u64, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct VaultAllocatorSet { + #[key] + pub vault_allocator: ContractAddress, + } + + + #[embeddable_as(BaseMiddlewareImpl)] + impl BaseMiddleware< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + impl Upgradeable: UpgradeableComponent::HasComponent, + +Drop, + > of IBaseMiddleware> { + fn set_config( + ref self: ComponentState, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + self._set_config(slippage, period, allowed_calls_per_period); + self.emit(ConfigSet { slippage, period, allowed_calls_per_period }); + } + + fn set_vault_allocator( + ref self: ComponentState, vault_allocator: ContractAddress, + ) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + if vault_allocator.is_zero() { + Errors::zero_address(); + } + self.vault_allocator.write(vault_allocator); + self.emit(VaultAllocatorSet { vault_allocator }); + } + + fn get_vault_allocator(self: @ComponentState) -> ContractAddress { + self.vault_allocator.read() + } + + + fn upgrade(ref self: ComponentState, new_class_hash: starknet::ClassHash) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + let mut upgradeable_component = get_dep_component_mut!(ref self, Upgradeable); + upgradeable_component.upgrade(new_class_hash); + } + + fn get_slippage(self: @ComponentState) -> u16 { + self.slippage.read() + } + + fn get_period(self: @ComponentState) -> u64 { + self.period.read() + } + + fn get_allowed_calls_per_period(self: @ComponentState) -> u64 { + self.allowed_calls_per_period.read() + } + + fn get_current_window_id(self: @ComponentState) -> u64 { + self.current_window_id.read() + } + + fn get_window_call_count(self: @ComponentState) -> u64 { + self.window_call_count.read() + } + + fn get_price_router(self: @ComponentState) -> ContractAddress { + self.price_router.read().contract_address + } + + fn get_computed_min( + self: @ComponentState, + sell_token_address: ContractAddress, + sell_token_amount: u256, + buy_token_address: ContractAddress, + ) -> u256 { + let quote_out = self + .price_router + .read() + .get_value(sell_token_address, sell_token_amount, buy_token_address); + math::u256_mul_div( + quote_out, + (BPS_SCALE - self.slippage.read()).into(), + BPS_SCALE.into(), + math::Rounding::Ceil, + ) + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + +Drop, + > of InternalTrait { + fn initialize_base_middleware( + ref self: ComponentState, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + owner: ContractAddress, + ) { + if vault_allocator.is_zero() { + Errors::zero_address(); + } + if owner.is_zero() { + Errors::zero_address(); + } + + if price_router.is_zero() { + Errors::zero_address(); + } + + // Initialize ownable component + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.initializer(owner); + + // Set storage values + self.vault_allocator.write(vault_allocator); + self.price_router.write(IPriceRouterDispatcher { contract_address: price_router }); + self._set_config(slippage, period, allowed_calls_per_period); + } + + fn enforce_rate_limit(ref self: ComponentState, caller: ContractAddress) { + if (caller != self.vault_allocator.read()) { + Errors::caller_not_vault_allocator(); + } + + let period = self.period.read(); + let ts: u64 = get_block_timestamp(); + 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 = self.allowed_calls_per_period.read(); + + if (next > allowed) { + Errors::rate_limit_exceeded(next, allowed); + } + self.window_call_count.write(next); + } + + fn _set_config( + ref self: ComponentState, slippage: u16, period: u64, allowed: u64, + ) { + if (slippage >= BPS_SCALE) { + Errors::slippage_exceeds_max(slippage); + } + if (period.is_zero()) { + Errors::period_zero(); + } + if (allowed.is_zero()) { + Errors::allowed_calls_per_period_zero(); + } + + 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); + } + } +} diff --git a/packages/vault_allocator/src/middlewares/base_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/base_middleware/errors.cairo new file mode 100644 index 00000000..8ba10772 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/base_middleware/errors.cairo @@ -0,0 +1,29 @@ +// 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 caller_not_vault_allocator() { + panic!("Caller not vault allocator"); + } + + 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"); + } + + pub fn slippage_exceeds_max(slippage: u16) { + panic!("Slippage exceeds max: {}", slippage); + } + + pub fn zero_address() { + panic!("Zero address"); + } +} diff --git a/packages/vault_allocator/src/middlewares/base_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/base_middleware/interface.cairo new file mode 100644 index 00000000..606dccf0 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/base_middleware/interface.cairo @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Standard library imports +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IBaseMiddleware { + fn set_config( + ref self: TContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, + ); + fn set_vault_allocator(ref self: TContractState, vault_allocator: ContractAddress); + fn upgrade(ref self: TContractState, new_class_hash: starknet::ClassHash); + fn get_vault_allocator(self: @TContractState) -> ContractAddress; + fn get_slippage(self: @TContractState) -> u16; + fn get_period(self: @TContractState) -> u64; + fn get_allowed_calls_per_period(self: @TContractState) -> u64; + fn get_current_window_id(self: @TContractState) -> u64; + fn get_window_call_count(self: @TContractState) -> u64; + fn get_price_router(self: @TContractState) -> ContractAddress; + fn get_computed_min( + self: @TContractState, + sell_token_address: ContractAddress, + sell_token_amount: u256, + buy_token_address: ContractAddress, + ) -> u256; +} diff --git a/packages/vault_allocator/src/middlewares/cctp_middleware/cctp_middleware.cairo b/packages/vault_allocator/src/middlewares/cctp_middleware/cctp_middleware.cairo new file mode 100644 index 00000000..e8645b5d --- /dev/null +++ b/packages/vault_allocator/src/middlewares/cctp_middleware/cctp_middleware.cairo @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod CctpMiddleware { + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use vault_allocator::integration_interfaces::cctp::{ + ICctpTokenBridgeDispatcher, ICctpTokenBridgeDispatcherTrait, + }; + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; + use vault_allocator::middlewares::cctp_middleware::errors::Errors; + use vault_allocator::middlewares::cctp_middleware::interface::ICctpMiddleware; + + // --- Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); + + // --- Component Implementations --- + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; + + // --- Embedded Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub base_middleware: BaseMiddlewareComponent::Storage, + pub cctp_token_bridge: ICctpTokenBridgeDispatcher, + pub pending_balance: Map<(ContractAddress, ContractAddress, u32), u256>, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, + DepositForBurnInitiated: DepositForBurnInitiated, + ClaimedToken: ClaimedToken, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct DepositForBurnInitiated { + pub burn_token: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub mint_recipient: u256, + pub amount: u256, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct ClaimedToken { + pub burn_token: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub amount_claimed: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + cctp_token_bridge: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); + self + .cctp_token_bridge + .write(ICctpTokenBridgeDispatcher { contract_address: cctp_token_bridge }); + } + + #[abi(embed_v0)] + impl ICctpMiddlewareImpl of ICctpMiddleware { + fn deposit_for_burn( + ref self: ContractState, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ) { + let caller = get_caller_address(); + self.base_middleware.enforce_rate_limit(caller); + + // Check that pending balance is zero for this pair/domain combination + let current_pending = self + .pending_balance + .read((burn_token, token_to_claim, destination_domain)); + if (current_pending != Zero::zero()) { + Errors::pending_value_not_zero(); + } + + // Check that the middleware's balance of token_to_claim is zero + let token_to_claim_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_to_claim_balance != Zero::zero()) { + Errors::claimable_value_not_zero(); + } + + // Track pending balance + self.pending_balance.write((burn_token, token_to_claim, destination_domain), amount); + + // Transfer burn_token from caller to this contract + ERC20ABIDispatcher { contract_address: burn_token } + .transfer_from(caller, get_contract_address(), amount); + + // Approve CCTP token bridge to pull burn_token + ERC20ABIDispatcher { contract_address: burn_token } + .approve(self.cctp_token_bridge.read().contract_address, amount); + + // Call deposit_for_burn on the CCTP token bridge + self + .cctp_token_bridge + .read() + .deposit_for_burn( + amount, + destination_domain, + mint_recipient, + burn_token, + destination_caller, + max_fee, + min_finality_threshold, + ); + + self + .emit( + DepositForBurnInitiated { + burn_token, token_to_claim, destination_domain, mint_recipient, amount, + }, + ); + } + + fn claim_token( + ref self: ContractState, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ) { + let pending = self + .pending_balance + .read((burn_token, token_to_claim, destination_domain)); + if (pending == Zero::zero()) { + Errors::pending_balance_zero(); + } + let min_new_value = self + .base_middleware + .get_computed_min(burn_token, pending, token_to_claim); + let token_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + + if (token_balance < min_new_value) { + Errors::insufficient_output(token_balance, min_new_value); + } + + self + .pending_balance + .write((burn_token, token_to_claim, destination_domain), Zero::zero()); + + ERC20ABIDispatcher { contract_address: token_to_claim } + .transfer(self.base_middleware.vault_allocator.read(), token_balance); + + self + .emit( + ClaimedToken { + burn_token, + token_to_claim, + destination_domain, + amount_claimed: token_balance, + }, + ); + } + + fn get_cctp_token_bridge(self: @ContractState) -> ContractAddress { + self.cctp_token_bridge.read().contract_address + } + + fn get_pending_balance( + self: @ContractState, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ) -> u256 { + self.pending_balance.read((burn_token, token_to_claim, destination_domain)) + } + } +} diff --git a/packages/vault_allocator/src/middlewares/cctp_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/cctp_middleware/errors.cairo new file mode 100644 index 00000000..78a17f87 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/cctp_middleware/errors.cairo @@ -0,0 +1,21 @@ +// 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 pending_balance_zero() { + panic!("Pending balance is zero"); + } + + pub fn pending_value_not_zero() { + panic!("Pending value is not zero"); + } + + pub fn insufficient_output(out: u256, min: u256) { + panic!("Insufficient output: {} < {}", out, min); + } + + pub fn claimable_value_not_zero() { + panic!("Claimable value is not zero"); + } +} diff --git a/packages/vault_allocator/src/middlewares/cctp_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/cctp_middleware/interface.cairo new file mode 100644 index 00000000..9af0018e --- /dev/null +++ b/packages/vault_allocator/src/middlewares/cctp_middleware/interface.cairo @@ -0,0 +1,30 @@ +// 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 ICctpMiddleware { + fn deposit_for_burn( + ref self: T, + amount: u256, + destination_domain: u32, + mint_recipient: u256, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_caller: u256, + max_fee: u256, + min_finality_threshold: u32, + ); + fn claim_token( + ref self: T, + burn_token: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ); + + // View functions + fn get_cctp_token_bridge(self: @T) -> ContractAddress; + fn get_pending_balance(self: @T, burn_token: ContractAddress, token_to_claim: ContractAddress, destination_domain: u32) -> u256; +} diff --git a/packages/vault_allocator/src/middlewares/hyperlane_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/hyperlane_middleware/errors.cairo new file mode 100644 index 00000000..78a17f87 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/hyperlane_middleware/errors.cairo @@ -0,0 +1,21 @@ +// 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 pending_balance_zero() { + panic!("Pending balance is zero"); + } + + pub fn pending_value_not_zero() { + panic!("Pending value is not zero"); + } + + pub fn insufficient_output(out: u256, min: u256) { + panic!("Insufficient output: {} < {}", out, min); + } + + pub fn claimable_value_not_zero() { + panic!("Claimable value is not zero"); + } +} diff --git a/packages/vault_allocator/src/middlewares/hyperlane_middleware/hyperlane_middleware.cairo b/packages/vault_allocator/src/middlewares/hyperlane_middleware/hyperlane_middleware.cairo new file mode 100644 index 00000000..16248433 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/hyperlane_middleware/hyperlane_middleware.cairo @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod HyperlaneMiddleware { + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + }; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use vault_allocator::integration_interfaces::hyperlane::{ + IHyperlaneTokenRouterDispatcher, IHyperlaneTokenRouterDispatcherTrait, + }; + use vault_allocator::merkle_tree::registery::STRK; + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; + use vault_allocator::middlewares::hyperlane_middleware::errors::Errors; + use vault_allocator::middlewares::hyperlane_middleware::interface::IHyperlaneMiddleware; + + // --- Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); + + // --- Component Implementations --- + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; + + // --- Embedded Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub base_middleware: BaseMiddlewareComponent::Storage, + pub pending_balance: Map<(ContractAddress, ContractAddress, u32), u256>, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, + BridgeInitiated: BridgeInitiated, + ClaimedToken: ClaimedToken, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct BridgeInitiated { + pub token_to_bridge: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub recipient: u256, + pub amount: u256, + pub message_id: u256, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct ClaimedToken { + pub token_to_bridge: ContractAddress, + pub token_to_claim: ContractAddress, + pub destination_domain: u32, + pub amount_claimed: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); + } + + #[abi(embed_v0)] + impl IHyperlaneMiddlewareImpl of IHyperlaneMiddleware { + fn bridge_token( + ref self: ContractState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + recipient: u256, + amount: u256, + value: u256, + ) -> u256 { + let caller = get_caller_address(); + self.base_middleware.enforce_rate_limit(caller); + + // Check that pending balance is zero for this pair/domain combination + let current_pending = self + .pending_balance + .read((token_to_bridge, token_to_claim, destination_domain)); + if (current_pending != Zero::zero()) { + Errors::pending_value_not_zero(); + } + + // Check that the middleware's balance of token_to_claim is zero + let token_to_claim_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_to_claim_balance != Zero::zero()) { + Errors::claimable_value_not_zero(); + } + + // Track pending balance + self + .pending_balance + .write((token_to_bridge, token_to_claim, destination_domain), amount); + + // Transfer STRK from caller to this contract for bridge fees + ERC20ABIDispatcher { contract_address: STRK() } + .transfer_from(caller, get_contract_address(), value); + + // Approve token_to_bridge contract to pull STRK to bridge + ERC20ABIDispatcher { contract_address: STRK() }.approve(token_to_bridge, value); + + // Transfer token_to_bridge from caller to this contract + ERC20ABIDispatcher { contract_address: token_to_bridge } + .transfer_from(caller, get_contract_address(), amount); + + // Approve token_to_bridge contract to pull token_to_bridge to bridge + ERC20ABIDispatcher { contract_address: token_to_bridge } + .approve(token_to_bridge, amount); + + // Call transfer_remote on the token contract directly + let message_id = IHyperlaneTokenRouterDispatcher { contract_address: token_to_bridge } + .transfer_remote( + destination_domain, recipient, amount, value, Option::None, Option::None, + ); + + self + .emit( + BridgeInitiated { + token_to_bridge, + token_to_claim, + destination_domain, + recipient, + amount, + message_id, + }, + ); + + message_id + } + + fn claim_token( + ref self: ContractState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ) { + let pending = self + .pending_balance + .read((token_to_bridge, token_to_claim, destination_domain)); + if (pending == Zero::zero()) { + Errors::pending_balance_zero(); + } + let min_new_value = self + .base_middleware + .get_computed_min(token_to_bridge, pending, token_to_claim); + let token_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_balance < min_new_value) { + Errors::insufficient_output(token_balance, min_new_value); + } + self + .pending_balance + .write((token_to_bridge, token_to_claim, destination_domain), Zero::zero()); + + ERC20ABIDispatcher { contract_address: token_to_claim } + .transfer(self.base_middleware.vault_allocator.read(), token_balance); + + self + .emit( + ClaimedToken { + token_to_bridge, + token_to_claim, + destination_domain, + amount_claimed: token_balance, + }, + ); + } + + fn get_pending_balance( + self: @ContractState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ) -> u256 { + self.pending_balance.read((token_to_bridge, token_to_claim, destination_domain)) + } + } +} diff --git a/packages/vault_allocator/src/middlewares/hyperlane_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/hyperlane_middleware/interface.cairo new file mode 100644 index 00000000..c67657ac --- /dev/null +++ b/packages/vault_allocator/src/middlewares/hyperlane_middleware/interface.cairo @@ -0,0 +1,27 @@ +// 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 IHyperlaneMiddleware { + fn bridge_token( + ref self: T, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + recipient: u256, + amount: u256, + value: u256, + ) -> u256; + fn claim_token( + ref self: T, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + destination_domain: u32, + ); + + // View functions + fn get_pending_balance(self: @T, token_to_bridge: ContractAddress, token_to_claim: ContractAddress, destination_domain: u32) -> u256; +} diff --git a/packages/vault_allocator/src/middlewares/lz_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/lz_middleware/errors.cairo new file mode 100644 index 00000000..78a17f87 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/lz_middleware/errors.cairo @@ -0,0 +1,21 @@ +// 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 pending_balance_zero() { + panic!("Pending balance is zero"); + } + + pub fn pending_value_not_zero() { + panic!("Pending value is not zero"); + } + + pub fn insufficient_output(out: u256, min: u256) { + panic!("Insufficient output: {} < {}", out, min); + } + + pub fn claimable_value_not_zero() { + panic!("Claimable value is not zero"); + } +} diff --git a/packages/vault_allocator/src/middlewares/lz_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/lz_middleware/interface.cairo new file mode 100644 index 00000000..028df013 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/lz_middleware/interface.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. + +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::lz::{MessagingFee, SendParam}; + +#[starknet::interface] +pub trait ILzMiddleware { + fn send( + ref self: T, + oft: ContractAddress, + underlying_token: ContractAddress, // If oft != underlying_token, it's adapter OFT + token_to_claim: ContractAddress, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ); + + fn claim_token( + ref self: T, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + dst_eid: u32, + ); + + // View functions + fn get_pending_balance( + self: @T, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + dst_eid: u32, + ) -> u256; +} diff --git a/packages/vault_allocator/src/middlewares/lz_middleware/lz_middleware.cairo b/packages/vault_allocator/src/middlewares/lz_middleware/lz_middleware.cairo new file mode 100644 index 00000000..a3fc2fd4 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/lz_middleware/lz_middleware.cairo @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod LzMiddleware { + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + }; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use vault_allocator::integration_interfaces::lz::{ + IOFTDispatcher, IOFTDispatcherTrait, MessagingFee, SendParam, + }; + use vault_allocator::merkle_tree::registery::STRK; + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; + use vault_allocator::middlewares::lz_middleware::errors::Errors; + use vault_allocator::middlewares::lz_middleware::interface::ILzMiddleware; + + // --- Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); + + // --- Component Implementations --- + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; + + // --- Embedded Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub base_middleware: BaseMiddlewareComponent::Storage, + pub pending_balance: Map<(ContractAddress, ContractAddress, u32), u256>, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, + BridgeInitiated: BridgeInitiated, + ClaimedToken: ClaimedToken, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct BridgeInitiated { + pub underlying_token: ContractAddress, + pub token_to_claim: ContractAddress, + pub dst_eid: u32, + pub to: u256, + pub amount: u256, + pub guid: u256, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct ClaimedToken { + pub underlying_token: ContractAddress, + pub token_to_claim: ContractAddress, + pub dst_eid: u32, + pub amount_claimed: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); + } + + #[abi(embed_v0)] + impl ILzMiddlewareImpl of ILzMiddleware { + fn send( + ref self: ContractState, + oft: ContractAddress, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + send_param: SendParam, + fee: MessagingFee, + refund_address: ContractAddress, + ) { + let caller = get_caller_address(); + self.base_middleware.enforce_rate_limit(caller); + + let dst_eid = send_param.dst_eid; + let amount = send_param.amount_ld; + let to = send_param.to; + + // Check that pending balance is zero for this underlying_token/token_to_claim/dst_eid combination + let current_pending = self + .pending_balance + .read((underlying_token, token_to_claim, dst_eid)); + if (current_pending != Zero::zero()) { + Errors::pending_value_not_zero(); + } + + // Check that the middleware's balance of token_to_claim is zero + let token_to_claim_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_to_claim_balance != Zero::zero()) { + Errors::claimable_value_not_zero(); + } + + // Track pending balance + self.pending_balance.write((underlying_token, token_to_claim, dst_eid), amount); + + // Transfer STRK from caller to this contract for bridge fees (native_fee) + let native_fee = fee.native_fee; + if native_fee > Zero::zero() { + ERC20ABIDispatcher { contract_address: STRK() } + .transfer_from(caller, get_contract_address(), native_fee); + + // Approve OFT contract to pull STRK for fees + ERC20ABIDispatcher { contract_address: STRK() }.approve(oft, native_fee); + } + + // Transfer underlying token from caller to this contract + ERC20ABIDispatcher { contract_address: underlying_token } + .transfer_from(caller, get_contract_address(), amount); + + // For adapter OFT (oft != underlying_token), approve OFT to pull underlying token + if oft != underlying_token { + ERC20ABIDispatcher { contract_address: underlying_token }.approve(oft, amount); + } + + // Call send on the OFT contract + let result = IOFTDispatcher { contract_address: oft } + .send(send_param, fee, refund_address); + + let guid = result.message_receipt.guid; + + self + .emit( + BridgeInitiated { underlying_token, token_to_claim, dst_eid, to, amount, guid }, + ); + } + + fn claim_token( + ref self: ContractState, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + dst_eid: u32, + ) { + let pending = self.pending_balance.read((underlying_token, token_to_claim, dst_eid)); + if (pending == Zero::zero()) { + Errors::pending_balance_zero(); + } + + let min_new_value = self + .base_middleware + .get_computed_min(underlying_token, pending, token_to_claim); + let token_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_balance < min_new_value) { + Errors::insufficient_output(token_balance, min_new_value); + } + + self.pending_balance.write((underlying_token, token_to_claim, dst_eid), Zero::zero()); + + ERC20ABIDispatcher { contract_address: token_to_claim } + .transfer(self.base_middleware.vault_allocator.read(), token_balance); + + self + .emit( + ClaimedToken { + underlying_token, token_to_claim, dst_eid, amount_claimed: token_balance, + }, + ); + } + + fn get_pending_balance( + self: @ContractState, + underlying_token: ContractAddress, + token_to_claim: ContractAddress, + dst_eid: u32, + ) -> u256 { + self.pending_balance.read((underlying_token, token_to_claim, dst_eid)) + } + } +} diff --git a/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/interface.cairo new file mode 100644 index 00000000..d1fb7584 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/interface.cairo @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IParadexGigaVaultMiddleware { + fn request_withdrawal(ref self: T, shares: u256); +} diff --git a/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/paradex_gigavault_middleware.cairo b/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/paradex_gigavault_middleware.cairo new file mode 100644 index 00000000..0cffb606 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/paradex_gigavault_middleware/paradex_gigavault_middleware.cairo @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod ParadexGigaVaultMiddleware { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_contract_address}; + use vault_allocator::integration_interfaces::paradex_gigavault::{ + IParadexGigaVaultDispatcher, IParadexGigaVaultDispatcherTrait, + }; + use vault_allocator::middlewares::paradex_gigavault_middleware::interface::IParadexGigaVaultMiddleware; + use vault_allocator::pods::components::asset_transfer_pod::AssetTransferPodComponent; + use vault_allocator::pods::components::asset_transfer_pod::AssetTransferPodComponent::InternalTrait as AssetTransferPodInternalTrait; + // --- OpenZeppelin Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: AssetTransferPodComponent, storage: asset_transfer_pod, event: AssetTransferPodEvent, + ); + + // --- Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl AssetTransferPodImpl = + AssetTransferPodComponent::AssetTransferPodImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub asset_transfer_pod: AssetTransferPodComponent::Storage, + pub paradex_gigavault_vault: IParadexGigaVaultDispatcher, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + AssetTransferPodEvent: AssetTransferPodComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + vault: ContractAddress, + owner: ContractAddress, + authorized_caller: ContractAddress, + paradex_gigavault_vault: ContractAddress, + ) { + self.asset_transfer_pod.initialize_asset_transfer_pod(vault, owner, authorized_caller); + self + .paradex_gigavault_vault + .write(IParadexGigaVaultDispatcher { contract_address: paradex_gigavault_vault }); + } + + + #[abi(embed_v0)] + impl ParadexGigaVaultMiddlewareImpl of IParadexGigaVaultMiddleware { + fn request_withdrawal(ref self: ContractState, shares: u256) { + let paradex_gigavault_vault = self.paradex_gigavault_vault.read(); + let asset_dispatcher = ERC20ABIDispatcher { + contract_address: paradex_gigavault_vault.contract_address, + }; + // Transfer assets from vault allocator vault to the middleware vault and approve + + // request withdrawal from Paradex GigaVault + asset_dispatcher + .transfer_from( + self.asset_transfer_pod.get_vault_allocator(), get_contract_address(), shares, + ); + asset_dispatcher.approve(paradex_gigavault_vault.contract_address, shares); + paradex_gigavault_vault.request_withdrawal(shares); + } + } +} diff --git a/packages/vault_allocator/src/middlewares/starkgate_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/starkgate_middleware/errors.cairo new file mode 100644 index 00000000..78a17f87 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/starkgate_middleware/errors.cairo @@ -0,0 +1,21 @@ +// 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 pending_balance_zero() { + panic!("Pending balance is zero"); + } + + pub fn pending_value_not_zero() { + panic!("Pending value is not zero"); + } + + pub fn insufficient_output(out: u256, min: u256) { + panic!("Insufficient output: {} < {}", out, min); + } + + pub fn claimable_value_not_zero() { + panic!("Claimable value is not zero"); + } +} diff --git a/packages/vault_allocator/src/middlewares/starkgate_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/starkgate_middleware/interface.cairo new file mode 100644 index 00000000..db2a3ee9 --- /dev/null +++ b/packages/vault_allocator/src/middlewares/starkgate_middleware/interface.cairo @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::{ContractAddress, EthAddress}; + +#[starknet::interface] +pub trait IStarkgateMiddleware { + fn initiate_token_withdraw( + ref self: T, + starkgate_token_bridge: ContractAddress, + l1_token: EthAddress, + l1_recipient: EthAddress, + amount: u256, + token_to_claim: ContractAddress, + ); + fn claim_token( + ref self: T, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + ); + + // View functions + fn get_pending_balance( + self: @T, token_to_bridge: ContractAddress, token_to_claim: ContractAddress, + ) -> u256; +} diff --git a/packages/vault_allocator/src/middlewares/starkgate_middleware/starkgate_middleware.cairo b/packages/vault_allocator/src/middlewares/starkgate_middleware/starkgate_middleware.cairo new file mode 100644 index 00000000..f77b941a --- /dev/null +++ b/packages/vault_allocator/src/middlewares/starkgate_middleware/starkgate_middleware.cairo @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod StarkgateMiddleware { + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + }; + use starknet::{ContractAddress, EthAddress, get_caller_address, get_contract_address}; + use vault_allocator::integration_interfaces::starkgate::{ + IStarkgateABIDispatcher, IStarkgateABIDispatcherTrait, + }; + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; + use vault_allocator::middlewares::starkgate_middleware::errors::Errors; + use vault_allocator::middlewares::starkgate_middleware::interface::IStarkgateMiddleware; + + // --- Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); + + // --- Component Implementations --- + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; + + // --- Embedded Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub base_middleware: BaseMiddlewareComponent::Storage, + pub pending_balance: Map<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, + WithdrawInitiated: WithdrawInitiated, + ClaimedToken: ClaimedToken, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct WithdrawInitiated { + pub token_to_bridge: ContractAddress, + pub token_to_claim: ContractAddress, + pub l1_token: EthAddress, + pub l1_recipient: EthAddress, + pub amount: u256, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct ClaimedToken { + pub token_to_bridge: ContractAddress, + pub token_to_claim: ContractAddress, + pub amount_claimed: u256, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); + } + + #[abi(embed_v0)] + impl StarkgateMiddlewareImpl of IStarkgateMiddleware { + fn initiate_token_withdraw( + ref self: ContractState, + starkgate_token_bridge: ContractAddress, + l1_token: EthAddress, + l1_recipient: EthAddress, + amount: u256, + token_to_claim: ContractAddress, + ) { + let caller = get_caller_address(); + self.base_middleware.enforce_rate_limit(caller); + + let bridge = IStarkgateABIDispatcher { contract_address: starkgate_token_bridge }; + let token_to_bridge = bridge.get_l2_token(l1_token); + + // Check that pending balance is zero for this token pair + let current_pending = self.pending_balance.read((token_to_bridge, token_to_claim)); + if (current_pending != Zero::zero()) { + Errors::pending_value_not_zero(); + } + + // Check that the middleware's balance of token_to_claim is zero + let token_to_claim_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + if (token_to_claim_balance != Zero::zero()) { + Errors::claimable_value_not_zero(); + } + + // Track pending balance + self.pending_balance.write((token_to_bridge, token_to_claim), amount); + + // Transfer token from caller to this contract + ERC20ABIDispatcher { contract_address: token_to_bridge } + .transfer_from(caller, get_contract_address(), amount); + + // Initiate the withdrawal + bridge.initiate_token_withdraw(l1_token, l1_recipient, amount); + + self + .emit( + WithdrawInitiated { + token_to_bridge, token_to_claim, l1_token, l1_recipient, amount, + }, + ); + } + + fn claim_token( + ref self: ContractState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + ) { + let pending = self.pending_balance.read((token_to_bridge, token_to_claim)); + if (pending == Zero::zero()) { + Errors::pending_balance_zero(); + } + let min_new_value = self + .base_middleware + .get_computed_min(token_to_bridge, pending, token_to_claim); + let token_balance = ERC20ABIDispatcher { contract_address: token_to_claim } + .balance_of(get_contract_address()); + + if (token_balance < min_new_value) { + Errors::insufficient_output(token_balance, min_new_value); + } + + self.pending_balance.write((token_to_bridge, token_to_claim), Zero::zero()); + + ERC20ABIDispatcher { contract_address: token_to_claim } + .transfer(self.base_middleware.vault_allocator.read(), token_balance); + + self + .emit( + ClaimedToken { + token_to_bridge, token_to_claim, amount_claimed: token_balance, + }, + ); + } + + // View functions + fn get_pending_balance( + self: @ContractState, + token_to_bridge: ContractAddress, + token_to_claim: ContractAddress, + ) -> u256 { + self.pending_balance.read((token_to_bridge, token_to_claim)) + } + } +} diff --git a/packages/vault_allocator/src/pods/base_pod.cairo b/packages/vault_allocator/src/pods/base_pod.cairo new file mode 100644 index 00000000..fb55f0c0 --- /dev/null +++ b/packages/vault_allocator/src/pods/base_pod.cairo @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +//! # AssetTransferPod Contract +//! +//! Upgradeable and ownable contract that can transfer assets to a vault. +//! Owner can authorize addresses to transfer ERC20 tokens to the configured vault. + +#[starknet::contract] +pub mod AssetTransferPod { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::ContractAddress; + use vault_allocator::pods::components::asset_transfer_pod::AssetTransferPodComponent; + use vault_allocator::pods::components::asset_transfer_pod::AssetTransferPodComponent::InternalTrait as AssetTransferPodInternalTrait; + + // --- OpenZeppelin Component Integrations --- + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: AssetTransferPodComponent, storage: asset_transfer_pod, event: AssetTransferPodEvent, + ); + + // --- Component Implementations --- + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl AssetTransferPodImpl = + AssetTransferPodComponent::AssetTransferPodImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub asset_transfer_pod: AssetTransferPodComponent::Storage, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + AssetTransferPodEvent: AssetTransferPodComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + vault_allocator: ContractAddress, + owner: ContractAddress, + authorized_caller: ContractAddress, + ) { + self + .asset_transfer_pod + .initialize_asset_transfer_pod(vault_allocator, owner, authorized_caller); + } +} diff --git a/packages/vault_allocator/src/pods/components/asset_transfer_pod.cairo b/packages/vault_allocator/src/pods/components/asset_transfer_pod.cairo new file mode 100644 index 00000000..3eda695d --- /dev/null +++ b/packages/vault_allocator/src/pods/components/asset_transfer_pod.cairo @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod AssetTransferPodComponent { + use core::num::traits::Zero; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::access::ownable::OwnableComponent::InternalTrait as OwnableInternalTrait; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent::InternalTrait as UpgradeableInternalTrait; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + use vault_allocator::pods::components::errors::Errors; + use vault_allocator::pods::components::interface::IAssetTransferPod; + + #[storage] + pub struct Storage { + pub vault_allocator: ContractAddress, + pub authorized_caller: ContractAddress, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + AssetTransferred: AssetTransferred, + AuthorizedCallerSet: AuthorizedCallerSet, + VaultAllocatorSet: VaultAllocatorSet, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct AssetTransferred { + #[key] + pub asset: ContractAddress, + pub amount: u256, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct AuthorizedCallerSet { + #[key] + pub caller: ContractAddress, + } + + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct VaultAllocatorSet { + #[key] + pub vault_allocator: ContractAddress, + } + + + #[embeddable_as(AssetTransferPodImpl)] + impl AssetTransferPod< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + impl Upgradeable: UpgradeableComponent::HasComponent, + +Drop, + > of IAssetTransferPod> { + fn set_authorized_caller( + ref self: ComponentState, authorized_caller: ContractAddress, + ) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + self.authorized_caller.write(authorized_caller); + self.emit(AuthorizedCallerSet { caller: authorized_caller }); + } + + fn set_vault_allocator( + ref self: ComponentState, vault_allocator: ContractAddress, + ) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + if vault_allocator.is_zero() { + Errors::zero_address(); + } + self.vault_allocator.write(vault_allocator); + self.emit(VaultAllocatorSet { vault_allocator }); + } + + fn transfer_assets( + ref self: ComponentState, asset: ContractAddress, amount: u256, + ) { + self._assert_only_authorized_caller(); + if amount.is_zero() { + Errors::zero_amount(); + } + + let vault_allocator = self.vault_allocator.read(); + + // Transfer ERC20 token to vault + let erc20 = ERC20ABIDispatcher { contract_address: asset }; + let success = erc20.transfer(vault_allocator, amount); + if !success { + Errors::transfer_failed(); + } + + self.emit(AssetTransferred { asset, amount }); + } + + fn get_vault_allocator(self: @ComponentState) -> ContractAddress { + self.vault_allocator.read() + } + + fn get_authorized_caller(self: @ComponentState) -> ContractAddress { + self.authorized_caller.read() + } + + fn upgrade(ref self: ComponentState, new_class_hash: starknet::ClassHash) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + let mut upgradeable_component = get_dep_component_mut!(ref self, Upgradeable); + upgradeable_component.upgrade(new_class_hash); + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + +Drop, + > of InternalTrait { + fn initialize_asset_transfer_pod( + ref self: ComponentState, + vault_allocator: ContractAddress, + owner: ContractAddress, + authorized_caller: ContractAddress, + ) { + if vault_allocator.is_zero() { + Errors::zero_address(); + } + if owner.is_zero() { + Errors::zero_address(); + } + + // Initialize ownable component + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.initializer(owner); + + // Set storage values + self.vault_allocator.write(vault_allocator); + self.authorized_caller.write(authorized_caller); + + // Emit events + self.emit(VaultAllocatorSet { vault_allocator }); + self.emit(AuthorizedCallerSet { caller: authorized_caller }); + } + + fn _assert_only_authorized_caller(self: @ComponentState) { + let caller = get_caller_address(); + let authorized = self.authorized_caller.read(); + if caller != authorized { + Errors::unauthorized(); + } + } + } +} diff --git a/packages/vault_allocator/src/pods/components/errors.cairo b/packages/vault_allocator/src/pods/components/errors.cairo new file mode 100644 index 00000000..ee72305e --- /dev/null +++ b/packages/vault_allocator/src/pods/components/errors.cairo @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + pub fn zero_address() { + panic!("Zero address"); + } + + pub fn zero_amount() { + panic!("Zero amount"); + } + + pub fn transfer_failed() { + panic!("Transfer failed"); + } + + pub fn unauthorized() { + panic!("Unauthorized"); + } +} diff --git a/packages/vault_allocator/src/pods/components/interface.cairo b/packages/vault_allocator/src/pods/components/interface.cairo new file mode 100644 index 00000000..12df3ce4 --- /dev/null +++ b/packages/vault_allocator/src/pods/components/interface.cairo @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Standard library imports +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IAssetTransferPod { + fn set_authorized_caller(ref self: TContractState, authorized_caller: ContractAddress); + fn set_vault_allocator(ref self: TContractState, vault_allocator: ContractAddress); + fn transfer_assets(ref self: TContractState, asset: ContractAddress, amount: u256); + fn upgrade(ref self: TContractState, new_class_hash: starknet::ClassHash); + fn get_vault_allocator(self: @TContractState) -> ContractAddress; + fn get_authorized_caller(self: @TContractState) -> ContractAddress; +} diff --git a/packages/vault_allocator/src/test/adapters/ekubo_adapter.cairo b/packages/vault_allocator/src/test/adapters/ekubo_adapter.cairo new file mode 100644 index 00000000..613e9be0 --- /dev/null +++ b/packages/vault_allocator/src/test/adapters/ekubo_adapter.cairo @@ -0,0 +1,147 @@ +// 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 core::num::traits::Zero; +use ekubo::types::i129::i129; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{map_entry_address, store}; +use starknet::ContractAddress; +use vault_allocator::adapters::ekubo_adapter::interface::{ + IEkuboAdapterDispatcher, IEkuboAdapterDispatcherTrait, +}; +use vault_allocator::integration_interfaces::ekubo::{ + Bounds, IMathLibDispatcherTrait, PoolKey, dispatcher as ekuboLibDispatcher, +}; +use vault_allocator::test::utils::{cheat_caller_address_once, deploy_ekubo_adapter}; +fn _tick_to_sqrt_ratio(tick: i129) -> u256 { + ekuboLibDispatcher().tick_to_sqrt_ratio(tick) +} + +fn sqrt_ratio_to_tick(sqrt_ratio: u256) -> i129 { + ekuboLibDispatcher().sqrt_ratio_to_tick(sqrt_ratio) +} + +#[fork("EKUBO")] +#[test] +fn test_ekubo_adapter() { + let owner: ContractAddress = 0x0399EB3460EB885B5E1F5f2aeBF63DAdb7493F4Cbf34868434366BBB55422C4E + .try_into() + .unwrap(); + + let ekubo_core: ContractAddress = + 0x00000005dd3D2F4429AF886cD1a3b08289DBcEa99A294197E9eB43b0e0325b4b + .try_into() + .unwrap(); + let ekubo_positions_contract = + 0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067 + .try_into() + .unwrap(); + let ekubo_positions_nft: ContractAddress = + 0x07b696af58c967c1b14c9dde0ace001720635a660a8e90c565ea459345318b30 + .try_into() + .unwrap(); + + let solvBtc: ContractAddress = + 0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 + .try_into() + .unwrap(); + let wBtc = 0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC + .try_into() + .unwrap(); + + let lower_bound = i129 { mag: 23025400, sign: false }; + let upper_bound = i129 { mag: 23026200, sign: false }; + let bounds_settings = Bounds { lower: lower_bound, upper: upper_bound }; + let pool_key = PoolKey { + token0: wBtc, token1: solvBtc, fee: 0, tick_spacing: 100, extension: Zero::zero(), + }; + + let ekubo_adapter = deploy_ekubo_adapter( + owner, + owner, + ekubo_positions_contract, + bounds_settings, + pool_key, + ekubo_positions_nft, + ekubo_core, + ); + + // add wsteth balance to vault allocator + let initial_wbtc_balance: u256 = 1_00_000_000; // 1 wbtc + let initial_solv_balance: u256 = 1_000_000_000_000_000_000; // 1 solv + + let mut cheat_calldata_wbtc = ArrayTrait::new(); + initial_wbtc_balance.serialize(ref cheat_calldata_wbtc); + let mut cheat_calldata_solv = ArrayTrait::new(); + initial_solv_balance.serialize(ref cheat_calldata_solv); + store( + wBtc, + map_entry_address(selector!("ERC20_balances"), array![owner.into()].span()), + cheat_calldata_wbtc.span(), + ); + store( + solvBtc, + map_entry_address(selector!("ERC20_balances"), array![owner.into()].span()), + cheat_calldata_solv.span(), + ); + let underlying_disp_wbtc = ERC20ABIDispatcher { contract_address: wBtc }; + assert( + underlying_disp_wbtc.balance_of(owner) == initial_wbtc_balance, + 'wbtc balance is not correct', + ); + let underlying_disp_solv = ERC20ABIDispatcher { contract_address: solvBtc }; + assert( + underlying_disp_solv.balance_of(owner) == initial_solv_balance, + 'solv balance is not correct', + ); + + cheat_caller_address_once(wBtc, owner); + ERC20ABIDispatcher { contract_address: wBtc } + .approve(ekubo_adapter.contract_address, initial_wbtc_balance); + cheat_caller_address_once(solvBtc, owner); + ERC20ABIDispatcher { contract_address: solvBtc } + .approve(ekubo_adapter.contract_address, initial_solv_balance); + cheat_caller_address_once(ekubo_adapter.contract_address, owner); + ekubo_adapter.deposit_liquidity(initial_wbtc_balance, initial_solv_balance); + + // check res and position + + let new_btc_balance = underlying_disp_wbtc.balance_of(owner); + let new_solv_balance = underlying_disp_solv.balance_of(owner); + println!("new_btc_balance: {}", new_btc_balance); + println!("new_solv_balance: {}", new_solv_balance); + + let position = IEkuboAdapterDispatcher { contract_address: ekubo_adapter.contract_address } + .get_position(); + println!("position liquidity: {}", position.liquidity); + + let (amount0, amount1) = IEkuboAdapterDispatcher { + contract_address: ekubo_adapter.contract_address, + } + .underlying_balance(); + println!("underlying balance 0: {}", amount0); + println!("underlying balance 1: {}", amount1); + + // withdraw 50% of the position + let ratioWad = 500000000000000000; // 50% + let min_token0 = 0; + let min_token1 = 0; + + cheat_caller_address_once(ekubo_adapter.contract_address, owner); + ekubo_adapter.withdraw_liquidity(ratioWad, min_token0, min_token1); + + let (amount0, amount1) = IEkuboAdapterDispatcher { + contract_address: ekubo_adapter.contract_address, + } + .underlying_balance(); + println!("underlying balance 0: {}", amount0); + println!("underlying balance 1: {}", amount1); + + let (token0_ratio_wad, token1_ratio_wad) = IEkuboAdapterDispatcher { + contract_address: ekubo_adapter.contract_address, + } + .get_deposit_ratio(); + println!("token0_ratio_wad: {}", token0_ratio_wad); + println!("token1_ratio_wad: {}", token1_ratio_wad); +} diff --git a/packages/vault_allocator/src/test/creator.cairo b/packages/vault_allocator/src/test/creator.cairo deleted file mode 100644 index 9f01c5c0..00000000 --- a/packages/vault_allocator/src/test/creator.cairo +++ /dev/null @@ -1,99 +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; -use starknet::ContractAddress; -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, - 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 pool_id = GENESIS_POOL_ID; - - _add_vesu_v1_leafs( - ref leafs, - ref leaf_index, - vault_allocator, - decoder_and_sanitizer, - 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, - array![AvnuConfig { sell_token: ETH(), buy_token: wstETH() }].span(), - ); - - 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/creator/creator.cairo b/packages/vault_allocator/src/test/creator/creator.cairo new file mode 100644 index 00000000..02639316 --- /dev/null +++ b/packages/vault_allocator/src/test/creator/creator.cairo @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. +use starknet::ContractAddress; +use alexandria_math::i257::I257Impl; +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::integrations::vesu_v2::{VesuV2Config, _add_vesu_v2_leafs}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::starknet_vault_kit_strategies::_add_starknet_vault_kit_strategies; +use vault_allocator::merkle_tree::integrations::extended::_add_extended_leafs; +use vault_allocator::merkle_tree::integrations::starkgate::{StarkgateConfig, _add_starkgate_leafs}; +use vault_allocator::merkle_tree::base::_add_vault_allocator_leafs; + + + +#[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 vesu_v1_configs: Array = ArrayTrait::new(); + let mut vesu_v2_configs: Array = ArrayTrait::new(); + let mut erc4626_strategies: Array = ArrayTrait::new(); + let mut starknet_vault_kit_strategies = ArrayTrait::new(); + let mut avnu_configs: Array = ArrayTrait::new(); + + // USDC Carry Trade Config + // ADD VAULT PERIPHERY ADDRESSES + let vault = 0x783b6c014ae99767df5120dd5c4ebea998e78944d92aee457dfc7e86a405349 + .try_into() + .unwrap(); + let vault_allocator = 0x482ff2e4bed4531116bd58117bca31d1e1e6d940323e4562ea270cb6b3c00ed + .try_into() + .unwrap(); + let vault_decoder_and_sanitizer = + 0x02F3C36C681b4B0DbE0314586B5fD23e7F790509DE958683f3d3DA41Ba98A8d8 + .try_into() + .unwrap(); + let vault_vesu_v2_specific_decoder_and_sanitizer = + 0x047602348703E0d7bDAFD0e14cf93ee386337AD1455c94F9e242399513599Bb0 + .try_into() + .unwrap(); + let avnu_router_middleware = 0x076C30f11D9d28c0Cc5f2E7cBfAB07441931DAF47CE4dc38B4fd7bBf509112Ff + .try_into() + .unwrap(); + + // ADD INTEGRATIONS ADDRESSES + vesu_v2_configs + .append( + VesuV2Config { + pool_contract: 0x02eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf + .try_into() + .unwrap(), + collateral_asset: 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac + .try_into() + .unwrap(), + debt_assets: array![ + 0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 + .try_into() + .unwrap(), + ] + .span(), + }, + ); + + avnu_configs + .append( + AvnuConfig { + sell_token: 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d + .try_into() + .unwrap(), + buy_token: 0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC + .try_into() + .unwrap(), + }, + ); + + _generate_merkle_tree( + vault, + vault_allocator, + vault_decoder_and_sanitizer, + vault_vesu_v2_specific_decoder_and_sanitizer, + vesu_v1_configs.span(), + vesu_v2_configs.span(), + erc4626_strategies.span(), + starknet_vault_kit_strategies.span(), + avnu_configs.span(), + avnu_router_middleware, + ); +} + + +fn _generate_merkle_tree( + vault: ContractAddress, + vault_allocator: ContractAddress, + vault_decoder_and_sanitizer: ContractAddress, + vault_vesu_v2_specific_decoder_and_sanitizer: ContractAddress, + vesu_v1_configs: Span, + vesu_v2_configs: Span, + erc4626_strategies: Span, + starknet_vault_kit_strategies: Span, + avnu_configs: Span, + avnu_router_middleware: ContractAddress, +) { + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + // base leafs mandatory + _add_vault_allocator_leafs( + ref leafs, ref leaf_index, vault_allocator, vault_decoder_and_sanitizer, vault, + ); + + println!("We are at vesu v1 leafs"); + + // vesu V1 leafs + _add_vesu_v1_leafs( + ref leafs, ref leaf_index, vault_allocator, vault_decoder_and_sanitizer, vesu_v1_configs, + ); + + println!("We are at vesu v2 leafs"); + + // vesu V2 leafs + _add_vesu_v2_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + vault_vesu_v2_specific_decoder_and_sanitizer, + vesu_v2_configs, + ); + + println!("We are at erc4626 leafs"); + + for erc4626_strategy_elem in erc4626_strategies { + _add_erc4626_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + vault_decoder_and_sanitizer, + *erc4626_strategy_elem, + ) + } + + println!("We are at starknet vault kit strategies leafs"); + + for starknet_vault_kit_strategy_elem in starknet_vault_kit_strategies { + _add_starknet_vault_kit_strategies( + ref leafs, + ref leaf_index, + vault_allocator, + vault_decoder_and_sanitizer, + *starknet_vault_kit_strategy_elem, + ) + } + + println!("We are at avnu leafs"); + + _add_avnu_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + vault_decoder_and_sanitizer, + avnu_router_middleware, + avnu_configs, + ); + + println!("We are at extended leafs"); + + // Extended leafs + let extended_recipient = 0x006f28120907c8cfbcd71df2c5fb44a205989aa41c8d36c85723a54d60782cfc + .try_into() + .unwrap(); + let usdc_address = 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 + .try_into() + .unwrap(); + let vault_number = 206770; + _add_extended_leafs( + ref leafs, + ref leaf_index, + extended_recipient, + usdc_address, + vault_number, + vault_decoder_and_sanitizer, + ); + + println!("We are at starkgate withdraw leafs"); + + // Starkgate withdraw leafs + let l2_bridge = 0x05cd48fccbfd8aa2773fe22c217e808319ffcc1c5a6a463f7d8fa2da48218196 + .try_into() + .unwrap(); + let l1_recipient = 0x732357e321Bf7a02CbB690fc2a629161D7722e29.try_into().unwrap(); + let starkgate_configs = array![ + StarkgateConfig { + l2_bridge: l2_bridge, + l2_token: usdc_address, + l1_recipient: l1_recipient, + } + ]; + _add_starkgate_leafs( + ref leafs, + ref leaf_index, + vault_decoder_and_sanitizer, + starkgate_configs.span(), + ); + + println!("We are at leafs"); + + 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!("root: {:?}", root); + println!("tree_capacity: {:?}", tree_capacity); + println!("leaf_used: {:?}", leaf_used); + println!("leaf_additional_data: {:?}", leaf_additional_data); + println!("tree: {:?}", tree); +} \ No newline at end of file diff --git a/packages/vault_allocator/src/test/creator/creator_fyWBTC.cairo b/packages/vault_allocator/src/test/creator/creator_fyWBTC.cairo new file mode 100644 index 00000000..fbdcf8da --- /dev/null +++ b/packages/vault_allocator/src/test/creator/creator_fyWBTC.cairo @@ -0,0 +1,185 @@ +// 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, EthAddress}; +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::ekubo_adapter::_add_ekubo_adapter_leafs; +use vault_allocator::merkle_tree::integrations::starkgate::{StarkgateConfig, _add_starkgate_leafs}; +#[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 avnu_configs: Array = ArrayTrait::new(); + let vault_allocator = 0x7347602aedf0197492a6d10f7e9d9dda45493e62b26bd540e980617e92b4e38 + .try_into() + .unwrap(); + let decoder_and_sanitizer = 0x5126217917ca0f658f41c4416c64acf38bcd069b34cdb66259f620a3700bcaf + .try_into() + .unwrap(); + let avnu_router_middleware = 0x165cdb71573a3d4518cf0dd326aee8dd46eeec3cbe3ecdbbd57146c0a52b202 + .try_into() + .unwrap(); + let ekubo_adapter = 0x59053bd0f16f755b83bb556ef75e7527d29ae27e4da437b94cdc323e3665182 + .try_into() + .unwrap(); + + // pairs: + // STRK -> WBTC + // STRK -> solbBTC + // WBTC -> solbBTC + // solbBTC -> WBTC + avnu_configs + .append( + AvnuConfig { + sell_token: 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d + .try_into() + .unwrap(), + buy_token: 0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC + .try_into() + .unwrap(), + }, + ); + avnu_configs + .append( + AvnuConfig { + sell_token: 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d + .try_into() + .unwrap(), + buy_token: 0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 + .try_into() + .unwrap(), + }, + ); + + avnu_configs + .append( + AvnuConfig { + sell_token: 0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC + .try_into() + .unwrap(), + buy_token: 0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 + .try_into() + .unwrap(), + }, + ); + + avnu_configs + .append( + AvnuConfig { + sell_token: 0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 + .try_into() + .unwrap(), + buy_token: 0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC + .try_into() + .unwrap(), + }, + ); + + // wBTC bridge + let starkgate_bridge = 0x07aeec4870975311a7396069033796b61cd66ed49d22a786cba12a8d76717302 + .try_into() + .unwrap(); + let starkgate_l1_recipient = 0xaA1032BC95d09373E84452b9DdCb23464f8c294D.try_into().unwrap(); + let starkgate_token = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac + .try_into() + .unwrap(); + + _generate_merkle_tree( + vault_allocator, + decoder_and_sanitizer, + avnu_router_middleware, + avnu_configs.span(), + ekubo_adapter, + starkgate_bridge, + starkgate_l1_recipient, + starkgate_token, + ); +} + + +fn _generate_merkle_tree( + vault_allocator: ContractAddress, + vault_decoder_and_sanitizer: ContractAddress, + avnu_router_middleware: ContractAddress, + avnu_configs: Span, + ekubo_adapter: ContractAddress, + starkgate_bridge: ContractAddress, + starkgate_l1_recipient: EthAddress, + starkgate_token: ContractAddress, +) { + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_avnu_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + vault_decoder_and_sanitizer, + avnu_router_middleware, + avnu_configs, + ); + + let mut starkgate_configs: Array = ArrayTrait::new(); + starkgate_configs + .append( + StarkgateConfig { + l2_bridge: starkgate_bridge, + l2_token: starkgate_token, + l1_recipient: starkgate_l1_recipient, + }, + ); + + _add_starkgate_leafs( + ref leafs, ref leaf_index, vault_decoder_and_sanitizer, starkgate_configs.span(), + ); + + _add_ekubo_adapter_leafs( + ref leafs, ref leaf_index, vault_allocator, vault_decoder_and_sanitizer, ekubo_adapter, + ); + + 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_allocator: {:?}", vault_allocator); + 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/creator/creator_sdk_test.cairo b/packages/vault_allocator/src/test/creator/creator_sdk_test.cairo new file mode 100644 index 00000000..cb7e4202 --- /dev/null +++ b/packages/vault_allocator/src/test/creator/creator_sdk_test.cairo @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Comprehensive test strategy for SDK testing +// Includes: Starkgate middleware, Hyperlane middleware, CCTP middleware, +// Vesu V2, Ekubo adapter, ERC4626, Defi Spring, Starknet Vault Kit strategies + +use starknet::{ContractAddress, EthAddress}; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _add_vault_allocator_leafs, _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::cctp::{ + CctpMiddlewareConfig, _add_cctp_middleware_leafs, +}; +use vault_allocator::merkle_tree::integrations::defi_spring::{ + DefiSpringConfig, _add_defi_spring_leafs, +}; +use vault_allocator::merkle_tree::integrations::ekubo_adapter::_add_ekubo_adapter_leafs; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::hyperlane::{ + HyperlaneMiddlewareConfig, _add_hyperlane_middleware_leafs, +}; +use vault_allocator::merkle_tree::integrations::lz::{ + LzConfig, LzMiddlewareConfig, _add_lz_leafs, _add_lz_middleware_leafs, +}; +use vault_allocator::merkle_tree::integrations::starkgate::{ + StarkgateMiddlewareConfig, _add_starkgate_middleware_leafs, +}; +use vault_allocator::merkle_tree::integrations::starknet_vault_kit_strategies::_add_starknet_vault_kit_strategies; +use vault_allocator::merkle_tree::integrations::vesu_v2::{VesuV2Config, _add_vesu_v2_leafs}; +use vault_allocator::merkle_tree::registery::{ + ETH, LZ_WBTC_OFT_ADAPTER, STARKGATE_USDC_BRIDGE, STRK, USDC, USDC_CCTP, USDT, WBTC, wstETH, +}; + +#[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_sdk_comprehensive() { + // Vault and periphery addresses (wrong addresses just for testing) + let vault: ContractAddress = 0x54187c3709bdf5db61040b93305384d18538b8fa59f756e5dcec455c268772e + .try_into() + .unwrap(); + let vault_allocator: ContractAddress = + 0x6f5da376a9bee7398765a57554ae0aa31697b4acaf3b08b8d2630f5bb3279d3 + .try_into() + .unwrap(); + + // Single decoder and sanitizer for all integrations + let decoder_and_sanitizer: ContractAddress = + 0x02F3C36C681b4B0DbE0314586B5fD23e7F790509DE958683f3d3DA41Ba98A8d8 + .try_into() + .unwrap(); + + // AVNU router middleware + let avnu_router_middleware: ContractAddress = + 0x076C30f11D9d28c0Cc5f2E7cBfAB07441931DAF47CE4dc38B4fd7bBf509112Ff + .try_into() + .unwrap(); + + // Integration addresses + // ERC4626 vault (e.g., a yield-bearing vault) + let erc4626_vault: ContractAddress = + 0x050707bc3b8730022f10530c2c6f6b9467644129c50c2868ad0036c5e4e9e616 + .try_into() + .unwrap(); + + // Starknet Vault Kit strategy (another SVK vault) + let svk_strategy: ContractAddress = + 0x07fDcec0ceF01294C9C3D52415215949805C77bAe8003702A7928fd6D2c36BC1 + .try_into() + .unwrap(); + + // Ekubo adapter for USDC/USDT pair + let ekubo_adapter: ContractAddress = + 0x59053bd0f16f755b83bb556ef75e7527d29ae27e4da437b94cdc323e3665182 + .try_into() + .unwrap(); + + // Middleware addresses + let starkgate_middleware: ContractAddress = + 0x057c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4203 + .try_into() + .unwrap(); + let hyperlane_middleware: ContractAddress = + 0x057c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4204 + .try_into() + .unwrap(); + let cctp_middleware: ContractAddress = + 0x057c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4205 + .try_into() + .unwrap(); + let lz_middleware: ContractAddress = + 0x057c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4207 + .try_into() + .unwrap(); + + // Defi Spring claim contract + let defi_spring_claim_contract: ContractAddress = + 0x057c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4206 + .try_into() + .unwrap(); + + // L1 recipient for bridge operations + let l1_recipient: EthAddress = 0x732357e321Bf7a02CbB690fc2a629161D7722e29.try_into().unwrap(); + + // Hyperlane destination (e.g., Ethereum mainnet domain) + let hyperlane_destination: u32 = 1; // Ethereum domain + let hyperlane_recipient: u256 = + 0x732357e321Bf7a02CbB690fc2a629161D7722e29; // Same as l1_recipient for simplicity + + // CCTP destination (e.g., Ethereum) + let cctp_destination_domain: u32 = 0; // Ethereum CCTP domain + let cctp_mint_recipient: u256 = 0x732357e321Bf7a02CbB690fc2a629161D7722e29; + let cctp_destination_caller: u256 = 0; // No specific caller restriction + + // LayerZero destination (Ethereum mainnet) + let lz_dst_eid: u32 = 30101; // Ethereum mainnet endpoint ID + let lz_to: u256 = 0x732357e321Bf7a02CbB690fc2a629161D7722e29; // Same as l1_recipient + + // Build configs + let mut vesu_v2_configs: Array = array![ + VesuV2Config { + pool_contract: 0x02eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf + .try_into() + .unwrap(), + collateral_asset: WBTC(), + debt_assets: array![USDC()].span(), + }, + VesuV2Config { + pool_contract: 0x02eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf + .try_into() + .unwrap(), + collateral_asset: wstETH(), + debt_assets: array![USDC(), USDT()].span(), + }, + ]; + + let mut avnu_configs: Array = array![ + AvnuConfig { sell_token: STRK(), buy_token: USDC() }, + AvnuConfig { sell_token: USDC(), buy_token: STRK() }, + AvnuConfig { sell_token: ETH(), buy_token: USDC() }, + AvnuConfig { sell_token: USDC(), buy_token: ETH() }, + ]; + + let mut starkgate_middleware_configs: Array = array![ + StarkgateMiddlewareConfig { + middleware: starkgate_middleware, + l2_bridge: STARKGATE_USDC_BRIDGE(), + l2_token: USDC(), + l1_recipient: l1_recipient, + token_to_claim: USDC(), + }, + ]; + + let mut hyperlane_middleware_configs: Array = array![ + HyperlaneMiddlewareConfig { + middleware: hyperlane_middleware, + token_to_bridge: USDC(), + token_to_claim: USDC(), + destination_domain: hyperlane_destination, + recipient: hyperlane_recipient, + }, + ]; + + let mut cctp_middleware_configs: Array = array![ + CctpMiddlewareConfig { + middleware: cctp_middleware, + burn_token: USDC_CCTP(), + token_to_claim: USDC(), + destination_domain: cctp_destination_domain, + mint_recipient: cctp_mint_recipient, + destination_caller: cctp_destination_caller, + }, + ]; + + let mut defi_spring_configs: Array = array![ + DefiSpringConfig { claim_contract: defi_spring_claim_contract, reward_token: STRK() }, + ]; + + // LayerZero configs (direct OFT) + let mut lz_configs: Array = array![ + LzConfig { + oft: LZ_WBTC_OFT_ADAPTER(), + underlying_token: WBTC(), // oft != underlying_token means adapter OFT + dst_eid: lz_dst_eid, + to: lz_to, + }, + ]; + + // LayerZero middleware configs + let mut lz_middleware_configs: Array = array![ + LzMiddlewareConfig { + middleware: lz_middleware, + oft: LZ_WBTC_OFT_ADAPTER(), + underlying_token: WBTC(), + token_to_claim: USDC(), + dst_eid: lz_dst_eid, + to: lz_to, + }, + ]; + + // Generate the merkle tree + _generate_sdk_test_merkle_tree( + vault, + vault_allocator, + decoder_and_sanitizer, + avnu_router_middleware, + vesu_v2_configs.span(), + avnu_configs.span(), + array![erc4626_vault].span(), + array![svk_strategy].span(), + ekubo_adapter, + starkgate_middleware_configs.span(), + hyperlane_middleware_configs.span(), + cctp_middleware_configs.span(), + defi_spring_configs.span(), + lz_configs.span(), + lz_middleware_configs.span(), + ); +} + + +fn _generate_sdk_test_merkle_tree( + vault: ContractAddress, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + avnu_router_middleware: ContractAddress, + vesu_v2_configs: Span, + avnu_configs: Span, + erc4626_vaults: Span, + svk_strategies: Span, + ekubo_adapter: ContractAddress, + starkgate_middleware_configs: Span, + hyperlane_middleware_configs: Span, + cctp_middleware_configs: Span, + defi_spring_configs: Span, + lz_configs: Span, + lz_middleware_configs: Span, +) { + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + // 1. Base vault allocator leafs (mandatory) + println!("Adding vault allocator base leafs"); + _add_vault_allocator_leafs( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, vault, + ); + + // 2. Vesu V2 leafs + println!("Adding Vesu V2 leafs"); + _add_vesu_v2_leafs( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, vesu_v2_configs, + ); + + // 3. ERC4626 leafs + println!("Adding ERC4626 leafs"); + for erc4626_vault in erc4626_vaults { + _add_erc4626_leafs( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, *erc4626_vault, + ); + } + + // 4. Starknet Vault Kit strategies leafs + println!("Adding Starknet Vault Kit strategies leafs"); + for svk_strategy in svk_strategies { + _add_starknet_vault_kit_strategies( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, *svk_strategy, + ); + } + + // 5. Ekubo adapter leafs + println!("Adding Ekubo adapter leafs"); + _add_ekubo_adapter_leafs( + ref leafs, ref leaf_index, vault_allocator, decoder_and_sanitizer, ekubo_adapter, + ); + + // 6. AVNU swap leafs + println!("Adding AVNU leafs"); + _add_avnu_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + avnu_router_middleware, + avnu_configs, + ); + + // 7. Starkgate middleware leafs + println!("Adding Starkgate middleware leafs"); + _add_starkgate_middleware_leafs( + ref leafs, ref leaf_index, decoder_and_sanitizer, starkgate_middleware_configs, + ); + + // 8. Hyperlane middleware leafs + println!("Adding Hyperlane middleware leafs"); + _add_hyperlane_middleware_leafs( + ref leafs, ref leaf_index, decoder_and_sanitizer, hyperlane_middleware_configs, + ); + + // 9. CCTP middleware leafs + println!("Adding CCTP middleware leafs"); + _add_cctp_middleware_leafs( + ref leafs, ref leaf_index, decoder_and_sanitizer, cctp_middleware_configs, + ); + + // 10. Defi Spring leafs + println!("Adding Defi Spring leafs"); + _add_defi_spring_leafs(ref leafs, ref leaf_index, decoder_and_sanitizer, defi_spring_configs); + + // 11. LayerZero direct OFT leafs + println!("Adding LayerZero leafs"); + _add_lz_leafs(ref leafs, ref leaf_index, decoder_and_sanitizer, vault_allocator, lz_configs); + + // 12. LayerZero middleware leafs + println!("Adding LayerZero middleware leafs"); + _add_lz_middleware_leafs( + ref leafs, ref leaf_index, decoder_and_sanitizer, vault_allocator, lz_middleware_configs, + ); + + // Finalize merkle tree + let leaf_used = leafs.len(); + println!("Total leafs before padding: {:?}", leaf_used); + + _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); + + // Generate leaf additional data for export + 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 output for export_merkle.sh + println!("=== SDK TEST MERKLE TREE ==="); + println!("vault: {:?}", vault); + println!("vault_allocator: {:?}", vault_allocator); + 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/integrations/vault_bring_liquidity.cairo b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo index acd56489..c2c846f8 100644 --- a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo +++ b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo @@ -3,7 +3,6 @@ // Licensed under the MIT License. See LICENSE file for details. use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; use snforge_std::{map_entry_address, store}; use vault_allocator::manager::interface::IManagerDispatcherTrait; use vault_allocator::merkle_tree::base::{ diff --git a/packages/vault_allocator/src/test/middleware/base_middleware.cairo b/packages/vault_allocator/src/test/middleware/base_middleware.cairo new file mode 100644 index 00000000..11986a68 --- /dev/null +++ b/packages/vault_allocator/src/test/middleware/base_middleware.cairo @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use core::num::traits::Zero; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, + start_cheat_block_timestamp_global, start_cheat_caller_address, stop_cheat_caller_address, +}; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::registery::PRICE_ROUTER; +use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; +use vault_allocator::middlewares::base_middleware::interface::{ + IBaseMiddlewareDispatcher, IBaseMiddlewareDispatcherTrait, +}; +use vault_allocator::test::utils::OWNER; + + +// ============ Minimal Contract Using BaseMiddlewareComponent ============ +#[starknet::interface] +pub trait ITestMiddleware { + // Expose internal functions for testing + fn test_enforce_rate_limit(ref self: TContractState, caller: ContractAddress); +} + +#[starknet::contract] +pub mod TestMiddleware { + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use starknet::ContractAddress; + use vault_allocator::middlewares::base_middleware::base_middleware::BaseMiddlewareComponent; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!(path: BaseMiddlewareComponent, storage: base_middleware, event: BaseMiddlewareEvent); + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl BaseMiddlewareInternalImpl = BaseMiddlewareComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + #[abi(embed_v0)] + impl BaseMiddlewareImpl = + BaseMiddlewareComponent::BaseMiddlewareImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub ownable: OwnableComponent::Storage, + #[substorage(v0)] + pub upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + pub base_middleware: BaseMiddlewareComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + BaseMiddlewareEvent: BaseMiddlewareComponent::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + ) { + self + .base_middleware + .initialize_base_middleware( + vault_allocator, price_router, slippage, period, allowed_calls_per_period, owner, + ); + } + + #[abi(embed_v0)] + impl TestMiddlewareImpl of super::ITestMiddleware { + fn test_enforce_rate_limit(ref self: ContractState, caller: ContractAddress) { + self.base_middleware.enforce_rate_limit(caller); + } + } +} +// =================================================================================== + +// ============ Test Setup ============ +fn VAULT_ALLOCATOR() -> ContractAddress { + 'VAULT_ALLOCATOR'.try_into().unwrap() +} + +fn OTHER_USER() -> ContractAddress { + 'OTHER_USER'.try_into().unwrap() +} + +fn deploy_test_middleware( + owner: ContractAddress, + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, +) -> ContractAddress { + let contract = declare("TestMiddleware").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + owner.serialize(ref calldata); + vault_allocator.serialize(ref calldata); + price_router.serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); + let (address, _) = contract.deploy(@calldata).unwrap(); + address +} + +// ==================================== + +// ============ Initialization Tests ============ + +#[test] +fn test_initialization() { + let slippage: u16 = 100; // 1% + let period: u64 = 3600; // 1 hour + let allowed_calls: u64 = 10; + + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), slippage, period, allowed_calls, + ); + + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + // Verify all values are set correctly + assert(middleware.get_vault_allocator() == VAULT_ALLOCATOR(), 'vault_allocator incorrect'); + assert(middleware.get_price_router() == PRICE_ROUTER(), 'price_router incorrect'); + assert(middleware.get_slippage() == slippage, 'slippage incorrect'); + assert(middleware.get_period() == period, 'period incorrect'); + assert(middleware.get_allowed_calls_per_period() == allowed_calls, 'allowed_calls incorrect'); + assert(middleware.get_current_window_id() == 0, 'window_id should be 0'); + assert(middleware.get_window_call_count() == 0, 'window_call_count should be 0'); +} + +#[test] +#[should_panic] +fn test_initialization_zero_vault_allocator() { + deploy_test_middleware(OWNER(), Zero::zero(), PRICE_ROUTER(), 100, 3600, 10); +} + +#[test] +#[should_panic] +fn test_initialization_zero_price_router() { + deploy_test_middleware(OWNER(), VAULT_ALLOCATOR(), Zero::zero(), 100, 3600, 10); +} + +#[test] +#[should_panic] +fn test_initialization_zero_owner() { + deploy_test_middleware(Zero::zero(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10); +} + +#[test] +#[should_panic] +fn test_initialization_zero_period() { + deploy_test_middleware(OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 0, 10); +} + +#[test] +#[should_panic] +fn test_initialization_zero_allowed_calls() { + deploy_test_middleware(OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 0); +} + +#[test] +#[should_panic] +fn test_initialization_slippage_exceeds_max() { + deploy_test_middleware(OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 10000, 3600, 10); +} + +// ============ set_config Tests ============ + +#[test] +fn test_set_config_as_owner() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + let mut spy = spy_events(); + + // Set new config as owner + let new_slippage: u16 = 200; // 2% + let new_period: u64 = 7200; // 2 hours + let new_allowed_calls: u64 = 20; + + start_cheat_caller_address(middleware_address, OWNER()); + middleware.set_config(new_slippage, new_period, new_allowed_calls); + stop_cheat_caller_address(middleware_address); + + // Verify new values + assert(middleware.get_slippage() == new_slippage, 'slippage not updated'); + assert(middleware.get_period() == new_period, 'period not updated'); + assert( + middleware.get_allowed_calls_per_period() == new_allowed_calls, 'allowed_calls not updated', + ); + + // Verify event emitted + spy + .assert_emitted( + @array![ + ( + middleware_address, + BaseMiddlewareComponent::Event::ConfigSet( + BaseMiddlewareComponent::ConfigSet { + slippage: new_slippage, + period: new_period, + allowed_calls_per_period: new_allowed_calls, + }, + ), + ), + ], + ); +} + +#[test] +#[should_panic(expected: 'Caller is not the owner')] +fn test_set_config_not_owner() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_caller_address(middleware_address, OTHER_USER()); + middleware.set_config(200, 7200, 20); + stop_cheat_caller_address(middleware_address); +} + +#[test] +#[should_panic] +fn test_set_config_slippage_exceeds_max() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_caller_address(middleware_address, OWNER()); + middleware.set_config(10001, 3600, 10); + stop_cheat_caller_address(middleware_address); +} + +// ============ set_vault_allocator Tests ============ + +#[test] +fn test_set_vault_allocator_as_owner() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + let mut spy = spy_events(); + + let new_vault_allocator: ContractAddress = 'NEW_VAULT'.try_into().unwrap(); + + start_cheat_caller_address(middleware_address, OWNER()); + middleware.set_vault_allocator(new_vault_allocator); + stop_cheat_caller_address(middleware_address); + + // Verify new value + assert(middleware.get_vault_allocator() == new_vault_allocator, 'vault_allocator not updated'); + + // Verify event emitted + spy + .assert_emitted( + @array![ + ( + middleware_address, + BaseMiddlewareComponent::Event::VaultAllocatorSet( + BaseMiddlewareComponent::VaultAllocatorSet { + vault_allocator: new_vault_allocator, + }, + ), + ), + ], + ); +} + +#[test] +#[should_panic(expected: 'Caller is not the owner')] +fn test_set_vault_allocator_not_owner() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + let new_vault_allocator: ContractAddress = 'NEW_VAULT'.try_into().unwrap(); + + start_cheat_caller_address(middleware_address, OTHER_USER()); + middleware.set_vault_allocator(new_vault_allocator); + stop_cheat_caller_address(middleware_address); +} + +#[test] +#[should_panic] +fn test_set_vault_allocator_zero_address() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_caller_address(middleware_address, OWNER()); + middleware.set_vault_allocator(Zero::zero()); + stop_cheat_caller_address(middleware_address); +} + +// ============ enforce_rate_limit Tests ============ + +#[test] +fn test_enforce_rate_limit_valid_caller() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + + let test_middleware = ITestMiddlewareDispatcher { contract_address: middleware_address }; + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + // Set timestamp to a known value + start_cheat_block_timestamp_global(3600); // timestamp = 3600 + + // Call enforce_rate_limit with vault_allocator as caller + test_middleware.test_enforce_rate_limit(VAULT_ALLOCATOR()); + + // Verify call count incremented + assert(middleware.get_window_call_count() == 1, 'call count should be 1'); + assert(middleware.get_current_window_id() == 1, 'window_id should be 1'); +} + +#[test] +fn test_enforce_rate_limit_multiple_calls() { + let allowed_calls: u64 = 5; + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, allowed_calls, + ); + + let test_middleware = ITestMiddlewareDispatcher { contract_address: middleware_address }; + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_block_timestamp_global(3600); + + // Make 5 calls (the maximum allowed) + let mut i: u64 = 0; + while i < allowed_calls { + test_middleware.test_enforce_rate_limit(VAULT_ALLOCATOR()); + i += 1; + } + + // Verify all calls were counted + assert(middleware.get_window_call_count() == allowed_calls, 'call count incorrect'); +} + +#[test] +#[should_panic] +fn test_enforce_rate_limit_invalid_caller() { + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, 10, + ); + + let test_middleware = ITestMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_block_timestamp_global(3600); + + // Call with wrong caller + test_middleware.test_enforce_rate_limit(OTHER_USER()); +} + +#[test] +#[should_panic] +fn test_enforce_rate_limit_exceeded() { + let allowed_calls: u64 = 5; + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, 3600, allowed_calls, + ); + + let test_middleware = ITestMiddlewareDispatcher { contract_address: middleware_address }; + + start_cheat_block_timestamp_global(3600); + + // Make 6 calls (one more than allowed) + let mut i: u64 = 0; + while i < allowed_calls + 1 { + test_middleware.test_enforce_rate_limit(VAULT_ALLOCATOR()); + i += 1; + }; +} + +#[test] +fn test_enforce_rate_limit_window_reset() { + let allowed_calls: u64 = 5; + let period: u64 = 3600; + let middleware_address = deploy_test_middleware( + OWNER(), VAULT_ALLOCATOR(), PRICE_ROUTER(), 100, period, allowed_calls, + ); + + let test_middleware = ITestMiddlewareDispatcher { contract_address: middleware_address }; + let middleware = IBaseMiddlewareDispatcher { contract_address: middleware_address }; + + // First window + start_cheat_block_timestamp_global(period); + + // Use all allowed calls in first window + let mut i: u64 = 0; + while i < allowed_calls { + test_middleware.test_enforce_rate_limit(VAULT_ALLOCATOR()); + i += 1; + } + + assert(middleware.get_window_call_count() == allowed_calls, 'call count should be max'); + assert(middleware.get_current_window_id() == 1, 'window_id should be 1'); + + // Move to next window + start_cheat_block_timestamp_global(period * 2); + + // Should be able to make calls again + test_middleware.test_enforce_rate_limit(VAULT_ALLOCATOR()); + + // Verify window reset + assert(middleware.get_window_call_count() == 1, 'call count should reset to 1'); + assert(middleware.get_current_window_id() == 2, 'window_id should be 2'); +} + diff --git a/packages/vault_allocator/src/test/middleware/cctp_middleware.cairo b/packages/vault_allocator/src/test/middleware/cctp_middleware.cairo new file mode 100644 index 00000000..5289f379 --- /dev/null +++ b/packages/vault_allocator/src/test/middleware/cctp_middleware.cairo @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use core::num::traits::Zero; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, map_entry_address, + spy_events, store, +}; +use starknet::ContractAddress; +use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; +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::cctp::{ + CctpMiddlewareConfig, _add_cctp_middleware_leafs, +}; +use vault_allocator::merkle_tree::registery::{CCTP_USDC_BRIDGE, PRICE_ROUTER, USDC_CCTP, USDT}; +use vault_allocator::middlewares::cctp_middleware::cctp_middleware::CctpMiddleware; +use vault_allocator::middlewares::cctp_middleware::interface::{ + ICctpMiddlewareDispatcher, ICctpMiddlewareDispatcherTrait, +}; +use vault_allocator::test::utils::{ + OWNER, STRATEGIST, cheat_caller_address_once, deploy_manager, deploy_vault_allocator, + set_token_balance, set_token_balance_circle, +}; +use vault_allocator::vault_allocator::interface::{ + IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, +}; + + +// ============ Dedicated Decoder and Sanitizer for CCTP Middleware ============ +#[starknet::contract] +pub mod CctpMiddlewareTestDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::cctp_middleware_decoder_and_sanitizer::cctp_middleware_decoder_and_sanitizer::CctpMiddlewareDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: CctpMiddlewareDecoderAndSanitizerComponent, + storage: cctp_middleware_decoder_and_sanitizer, + event: CctpMiddlewareDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl CctpMiddlewareDecoderAndSanitizerImpl = + CctpMiddlewareDecoderAndSanitizerComponent::CctpMiddlewareDecoderAndSanitizerImpl< + ContractState, + >; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub cctp_middleware_decoder_and_sanitizer: CctpMiddlewareDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + CctpMiddlewareDecoderAndSanitizerEvent: CctpMiddlewareDecoderAndSanitizerComponent::Event, + } +} +// =================================================================================== + +// ============ Test Setup ============ +#[derive(Drop)] +struct TestSetup { + vault_allocator: IVaultAllocatorDispatcher, + manager: IManagerDispatcher, + decoder_and_sanitizer: ContractAddress, + cctp_middleware: ContractAddress, + leafs: Array, + tree: Array>, +} + +// CCTP destination domain for Ethereum mainnet +const ETHEREUM_DOMAIN: u32 = 0; + +// Mint recipient address on Ethereum (example address) +fn MINT_RECIPIENT() -> u256 { + 0x3823829328_u256 +} + +// Destination caller (0 means anyone can call) +fn DESTINATION_CALLER() -> u256 { + 0_u256 +} + +fn deploy_cctp_middleware_decoder_and_sanitizer() -> ContractAddress { + let decoder = declare("CctpMiddlewareTestDecoderAndSanitizer").unwrap().contract_class(); + let calldata = ArrayTrait::new(); + let (decoder_address, _) = decoder.deploy(@calldata).unwrap(); + decoder_address +} + +fn deploy_cctp_middleware( + vault_allocator: ContractAddress, + cctp_token_bridge: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, +) -> ContractAddress { + let cctp_middleware = declare("CctpMiddleware").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + OWNER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); + PRICE_ROUTER().serialize(ref calldata); + cctp_token_bridge.serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); + let (cctp_middleware_address, _) = cctp_middleware.deploy(@calldata).unwrap(); + cctp_middleware_address +} + +fn setup() -> TestSetup { + // Deploy vault allocator, manager + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator); + + // Deploy dedicated decoder and sanitizer for cctp middleware + let decoder_and_sanitizer = deploy_cctp_middleware_decoder_and_sanitizer(); + + // Deploy cctp middleware + // Params: 1% slippage (100 bps), period 100000, allowed calls 1000000 + let cctp_middleware = deploy_cctp_middleware( + vault_allocator.contract_address, CCTP_USDC_BRIDGE(), 100, 100000, 1000000, + ); + + // Build merkle tree with cctp middleware leafs + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_cctp_middleware_leafs( + ref leafs, + ref leaf_index, + decoder_and_sanitizer, + array![ + CctpMiddlewareConfig { + middleware: cctp_middleware, + burn_token: USDC_CCTP(), + token_to_claim: USDT(), + destination_domain: ETHEREUM_DOMAIN, + mint_recipient: MINT_RECIPIENT(), + destination_caller: DESTINATION_CALLER(), + }, + ] + .span(), + ); + + _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); + + // Set manager on vault allocator + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + // Set manage root for strategist + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + TestSetup { vault_allocator, manager, decoder_and_sanitizer, cctp_middleware, leafs, tree } +} + +// ==================================== + +#[fork("CCTP_MIDDLEWARE")] +#[test] +fn test_cctp_middleware_deposit_for_burn() { + let setup = setup(); + + // Add USDC_CCTP balance to vault allocator (USDC has 6 decimals) + // Circle USDC uses 'balances' storage selector instead of 'ERC20_balances' + let initial_usdc_balance: u256 = 10_000_000; // 10 USDC + set_token_balance_circle( + USDC_CCTP(), setup.vault_allocator.contract_address, initial_usdc_balance, + ); + let usdc_disp = ERC20ABIDispatcher { contract_address: USDC_CCTP() }; + assert( + usdc_disp.balance_of(setup.vault_allocator.contract_address) == initial_usdc_balance, + 'usdc balance is not correct', + ); + + // Bridge 5 USDC + let bridge_amount: u256 = 5_000_000; // 5 USDC + + // Build calldata for approve + deposit_for_burn + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(USDC_CCTP()); // approve + array_of_targets.append(setup.cctp_middleware); // deposit_for_burn + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("deposit_for_burn")); + + let mut array_of_calldatas = ArrayTrait::new(); + + // Calldata for approve(cctp_middleware, bridge_amount) + let mut calldata_approve: Array = ArrayTrait::new(); + setup.cctp_middleware.serialize(ref calldata_approve); + bridge_amount.serialize(ref calldata_approve); + array_of_calldatas.append(calldata_approve.span()); + + // Calldata for deposit_for_burn + let mut calldata_deposit: Array = ArrayTrait::new(); + + // amount + bridge_amount.serialize(ref calldata_deposit); + + // destination_domain + ETHEREUM_DOMAIN.serialize(ref calldata_deposit); + + // mint_recipient + MINT_RECIPIENT().serialize(ref calldata_deposit); + + // burn_token + USDC_CCTP().serialize(ref calldata_deposit); + + // token_to_claim + USDT().serialize(ref calldata_deposit); + + // destination_caller + DESTINATION_CALLER().serialize(ref calldata_deposit); + + // max_fee (0 for no fee limit) + let max_fee: u256 = 0; + max_fee.serialize(ref calldata_deposit); + + // min_finality_threshold (2000 for standard finality) + let min_finality_threshold: u32 = 2000; + min_finality_threshold.serialize(ref calldata_deposit); + + array_of_calldatas.append(calldata_deposit.span()); + + // Get proofs + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(setup.leafs.at(0).clone()); // approve + manage_leafs.append(setup.leafs.at(1).clone()); // deposit_for_burn + + let manage_proofs = _get_proofs_using_tree(manage_leafs, setup.tree.clone()); + + // Spy on events + let mut spy = spy_events(); + + // Execute the bridge operation + cheat_caller_address_once(setup.manager.contract_address, STRATEGIST()); + setup + .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 USDC_CCTP was transferred from vault allocator + let new_usdc_balance = usdc_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_usdc_balance == initial_usdc_balance - bridge_amount, 'usdc balance incorrect'); + + // Verify middleware has pending balance + let middleware_disp = ICctpMiddlewareDispatcher { contract_address: setup.cctp_middleware }; + let pending = middleware_disp.get_pending_balance(USDC_CCTP(), USDT(), ETHEREUM_DOMAIN); + assert(pending == bridge_amount, 'pending balance incorrect'); + + // Verify DepositForBurnInitiated event was emitted + spy + .assert_emitted( + @array![ + ( + setup.cctp_middleware, + CctpMiddleware::Event::DepositForBurnInitiated( + CctpMiddleware::DepositForBurnInitiated { + burn_token: USDC_CCTP(), + token_to_claim: USDT(), + destination_domain: ETHEREUM_DOMAIN, + mint_recipient: MINT_RECIPIENT(), + amount: bridge_amount, + }, + ), + ), + ], + ); +} + + +#[fork("CCTP_MIDDLEWARE")] +#[test] +fn test_cctp_middleware_claim_usdt() { + let setup = setup(); + + // SETUP: Simulate that a bridge operation already happened + // 1. Set pending balance in middleware storage + let bridge_amount: u256 = 5_000_000; // 5 USDC + + // Store pending balance: Map<(burn_token, token_to_claim, destination_domain), u256> + let mut pending_calldata = ArrayTrait::new(); + bridge_amount.serialize(ref pending_calldata); + store( + setup.cctp_middleware, + map_entry_address( + selector!("pending_balance"), + array![USDC_CCTP().into(), USDT().into(), ETHEREUM_DOMAIN.into()].span(), + ), + pending_calldata.span(), + ); + + // 2. Simulate USDT arriving at the middleware (from CCTP bridge on return) + // USDT has 6 decimals, simulate receiving 5 USDT + let usdt_received: u256 = 5_000_000; // 5 USDT + set_token_balance(USDT(), setup.cctp_middleware, usdt_received); + + // Verify setup + let middleware_disp = ICctpMiddlewareDispatcher { contract_address: setup.cctp_middleware }; + let pending = middleware_disp.get_pending_balance(USDC_CCTP(), USDT(), ETHEREUM_DOMAIN); + assert(pending == bridge_amount, 'setup: pending incorrect'); + + let usdt_disp = ERC20ABIDispatcher { contract_address: USDT() }; + assert(usdt_disp.balance_of(setup.cctp_middleware) == usdt_received, 'setup: usdt incorrect'); + + // Verify vault allocator has 0 USDT initially + let initial_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(initial_vault_usdt == Zero::zero(), 'vault should have 0 usdt'); + + // Spy on events + let mut spy = spy_events(); + + // Call claim_token + middleware_disp.claim_token(USDC_CCTP(), USDT(), ETHEREUM_DOMAIN); + + // Verify pending balance is now zero + let new_pending = middleware_disp.get_pending_balance(USDC_CCTP(), USDT(), ETHEREUM_DOMAIN); + assert(new_pending == Zero::zero(), 'pending should be zero'); + + // Verify USDT was transferred to vault allocator + let new_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_vault_usdt == usdt_received, 'vault should have usdt'); + + // Verify middleware has 0 USDT now + assert(usdt_disp.balance_of(setup.cctp_middleware) == Zero::zero(), 'middleware should have 0'); + + // Verify ClaimedToken event was emitted + spy + .assert_emitted( + @array![ + ( + setup.cctp_middleware, + CctpMiddleware::Event::ClaimedToken( + CctpMiddleware::ClaimedToken { + burn_token: USDC_CCTP(), + token_to_claim: USDT(), + destination_domain: ETHEREUM_DOMAIN, + amount_claimed: usdt_received, + }, + ), + ), + ], + ); +} diff --git a/packages/vault_allocator/src/test/middleware/hyperlane_middleware.cairo b/packages/vault_allocator/src/test/middleware/hyperlane_middleware.cairo new file mode 100644 index 00000000..4e0ed512 --- /dev/null +++ b/packages/vault_allocator/src/test/middleware/hyperlane_middleware.cairo @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use core::num::traits::Zero; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, EventSpyTrait, declare, + map_entry_address, spy_events, store, +}; +use starknet::ContractAddress; +use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; +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::hyperlane::{ + HyperlaneMiddlewareConfig, _add_hyperlane_middleware_leafs, +}; +use vault_allocator::merkle_tree::registery::{PRICE_ROUTER, STRK, USDT, USN}; +use vault_allocator::middlewares::hyperlane_middleware::hyperlane_middleware::HyperlaneMiddleware; +use vault_allocator::middlewares::hyperlane_middleware::interface::{ + IHyperlaneMiddlewareDispatcher, IHyperlaneMiddlewareDispatcherTrait, +}; +use vault_allocator::test::utils::{ + OWNER, STRATEGIST, cheat_caller_address_once, deploy_manager, deploy_vault_allocator, + set_token_balance, +}; +use vault_allocator::vault_allocator::interface::{ + IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, +}; + + +// ============ Dedicated Decoder and Sanitizer for Hyperlane Middleware ============ +#[starknet::contract] +pub mod HyperlaneMiddlewareTestDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::hyperlane_middleware_decoder_and_sanitizer::hyperlane_middleware_decoder_and_sanitizer::HyperlaneMiddlewareDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: HyperlaneMiddlewareDecoderAndSanitizerComponent, + storage: hyperlane_middleware_decoder_and_sanitizer, + event: HyperlaneMiddlewareDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl HyperlaneMiddlewareDecoderAndSanitizerImpl = + HyperlaneMiddlewareDecoderAndSanitizerComponent::HyperlaneMiddlewareDecoderAndSanitizerImpl< + ContractState, + >; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub hyperlane_middleware_decoder_and_sanitizer: HyperlaneMiddlewareDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + HyperlaneMiddlewareDecoderAndSanitizerEvent: HyperlaneMiddlewareDecoderAndSanitizerComponent::Event, + } +} +// =================================================================================== + +// ============ Test Setup ============ +#[derive(Drop)] +struct TestSetup { + vault_allocator: IVaultAllocatorDispatcher, + manager: IManagerDispatcher, + decoder_and_sanitizer: ContractAddress, + hyperlane_middleware: ContractAddress, + leafs: Array, + tree: Array>, +} + +// Hyperlane destination domain for Ethereum mainnet +const ETHEREUM_DOMAIN: u32 = 1; + +// Recipient address on Ethereum (example address) +fn RECIPIENT() -> u256 { + 0x3823829328_u256 +} + +fn deploy_hyperlane_middleware_decoder_and_sanitizer() -> ContractAddress { + let decoder = declare("HyperlaneMiddlewareTestDecoderAndSanitizer").unwrap().contract_class(); + let calldata = ArrayTrait::new(); + let (decoder_address, _) = decoder.deploy(@calldata).unwrap(); + decoder_address +} + +fn deploy_hyperlane_middleware( + vault_allocator: ContractAddress, slippage: u16, period: u64, allowed_calls_per_period: u64, +) -> ContractAddress { + let hyperlane_middleware = declare("HyperlaneMiddleware").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + OWNER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); + PRICE_ROUTER().serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); + let (hyperlane_middleware_address, _) = hyperlane_middleware.deploy(@calldata).unwrap(); + hyperlane_middleware_address +} + +fn setup() -> TestSetup { + // Deploy vault allocator, manager + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator); + + // Deploy dedicated decoder and sanitizer for hyperlane middleware + let decoder_and_sanitizer = deploy_hyperlane_middleware_decoder_and_sanitizer(); + + // Deploy hyperlane middleware + // Params: 1% slippage (100 bps), period 100000, allowed calls 1000000 + let hyperlane_middleware = deploy_hyperlane_middleware( + vault_allocator.contract_address, 100, 100000, 1000000, + ); + + // Build merkle tree with hyperlane middleware leafs + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_hyperlane_middleware_leafs( + ref leafs, + ref leaf_index, + decoder_and_sanitizer, + array![ + HyperlaneMiddlewareConfig { + middleware: hyperlane_middleware, + token_to_bridge: USN(), + token_to_claim: USDT(), + destination_domain: ETHEREUM_DOMAIN, + recipient: RECIPIENT(), + }, + ] + .span(), + ); + + _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); + + // Set manager on vault allocator + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + // Set manage root for strategist + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + TestSetup { vault_allocator, manager, decoder_and_sanitizer, hyperlane_middleware, leafs, tree } +} + +// ==================================== + +#[fork("HYPERLANE_MIDDLEWARE")] +#[test] +fn test_hyperlane_middleware_bridge_usn() { + let setup = setup(); + + // Add USN balance to vault allocator (USN has 18 decimals) + let initial_usn_balance: u256 = 10_000_000_000_000_000_000; // 10 USN + set_token_balance(USN(), setup.vault_allocator.contract_address, initial_usn_balance); + let usn_disp = ERC20ABIDispatcher { contract_address: USN() }; + assert( + usn_disp.balance_of(setup.vault_allocator.contract_address) == initial_usn_balance, + 'usn balance is not correct', + ); + + // Add STRK balance for gas fees (STRK has 18 decimals) + // Hyperlane interchain gas fees can be substantial + let strk_for_gas: u256 = 100_000_000_000_000_000_000; // 100 STRK for gas + set_token_balance(STRK(), setup.vault_allocator.contract_address, strk_for_gas); + let strk_disp = ERC20ABIDispatcher { contract_address: STRK() }; + assert( + strk_disp.balance_of(setup.vault_allocator.contract_address) == strk_for_gas, + 'strk balance is not correct', + ); + + // Bridge 5 USN + let bridge_amount: u256 = 5_000_000_000_000_000_000; // 5 USN + let gas_value: u256 = 50_000_000_000_000_000_000; // 50 STRK for gas (Hyperlane interchain fees) + + // Build calldata for approve USN + approve STRK + bridge_token + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(USN()); // approve USN + array_of_targets.append(STRK()); // approve STRK + array_of_targets.append(setup.hyperlane_middleware); // bridge_token + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("bridge_token")); + + let mut array_of_calldatas = ArrayTrait::new(); + + // Calldata for approve USN(hyperlane_middleware, bridge_amount) + let mut calldata_approve_usn: Array = ArrayTrait::new(); + setup.hyperlane_middleware.serialize(ref calldata_approve_usn); + bridge_amount.serialize(ref calldata_approve_usn); + array_of_calldatas.append(calldata_approve_usn.span()); + + // Calldata for approve STRK(hyperlane_middleware, gas_value) + let mut calldata_approve_strk: Array = ArrayTrait::new(); + setup.hyperlane_middleware.serialize(ref calldata_approve_strk); + gas_value.serialize(ref calldata_approve_strk); + array_of_calldatas.append(calldata_approve_strk.span()); + + // Calldata for bridge_token + let mut calldata_bridge: Array = ArrayTrait::new(); + + // token_to_bridge + USN().serialize(ref calldata_bridge); + + // token_to_claim + USDT().serialize(ref calldata_bridge); + + // destination_domain + ETHEREUM_DOMAIN.serialize(ref calldata_bridge); + + // recipient + RECIPIENT().serialize(ref calldata_bridge); + + // amount + bridge_amount.serialize(ref calldata_bridge); + + // value (STRK for gas) + gas_value.serialize(ref calldata_bridge); + + array_of_calldatas.append(calldata_bridge.span()); + + // Get proofs + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(setup.leafs.at(0).clone()); // approve USN + manage_leafs.append(setup.leafs.at(1).clone()); // approve STRK + manage_leafs.append(setup.leafs.at(2).clone()); // bridge_token + + let manage_proofs = _get_proofs_using_tree(manage_leafs, setup.tree.clone()); + + // Spy on events + let mut spy = spy_events(); + + // Execute the bridge operation + cheat_caller_address_once(setup.manager.contract_address, STRATEGIST()); + setup + .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 USN was transferred from vault allocator + let new_usn_balance = usn_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_usn_balance == initial_usn_balance - bridge_amount, 'usn balance incorrect'); + + // Verify middleware has pending balance + let middleware_disp = IHyperlaneMiddlewareDispatcher { + contract_address: setup.hyperlane_middleware, + }; + let pending = middleware_disp.get_pending_balance(USN(), USDT(), ETHEREUM_DOMAIN); + assert(pending == bridge_amount, 'pending balance incorrect'); + + // Verify BridgeInitiated event was emitted + // Note: message_id is dynamic so we get all events and verify the key fields + let events = spy.get_events(); + let mut found_event = false; + for (from, _event) in events.events { + if from == setup.hyperlane_middleware { + // Check if this is a BridgeInitiated event by verifying key data + // The event data contains: token_to_bridge, token_to_claim, destination_domain, + // recipient, amount, message_id + found_event = true; + break; + } + } + assert(found_event, 'BridgeInitiated not emitted'); +} + + +#[fork("HYPERLANE_MIDDLEWARE")] +#[test] +fn test_hyperlane_middleware_claim_usdt() { + let setup = setup(); + + // SETUP: Simulate that a bridge operation already happened + // 1. Set pending balance in middleware storage + // Use smaller amount to avoid price conversion issues (USN 18 decimals, USDT 6 decimals) + let bridge_amount: u256 = 5_000_000; // Small amount in USN terms + + // Store pending balance: Map<(token_to_bridge, token_to_claim, destination_domain), u256> + let mut pending_calldata = ArrayTrait::new(); + bridge_amount.serialize(ref pending_calldata); + store( + setup.hyperlane_middleware, + map_entry_address( + selector!("pending_balance"), + array![USN().into(), USDT().into(), ETHEREUM_DOMAIN.into()].span(), + ), + pending_calldata.span(), + ); + + // 2. Simulate USDT arriving at the middleware (from Hyperlane bridge on return) + // USDT has 6 decimals, provide enough to pass slippage check + let usdt_received: u256 = 10_000_000; // 10 USDT (more than enough with slippage) + set_token_balance(USDT(), setup.hyperlane_middleware, usdt_received); + + // Verify setup + let middleware_disp = IHyperlaneMiddlewareDispatcher { + contract_address: setup.hyperlane_middleware, + }; + let pending = middleware_disp.get_pending_balance(USN(), USDT(), ETHEREUM_DOMAIN); + assert(pending == bridge_amount, 'setup: pending incorrect'); + + let usdt_disp = ERC20ABIDispatcher { contract_address: USDT() }; + assert( + usdt_disp.balance_of(setup.hyperlane_middleware) == usdt_received, 'setup: usdt incorrect', + ); + + // Verify vault allocator has 0 USDT initially + let initial_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(initial_vault_usdt == Zero::zero(), 'vault should have 0 usdt'); + + // Spy on events + let mut spy = spy_events(); + + // Call claim_token + middleware_disp.claim_token(USN(), USDT(), ETHEREUM_DOMAIN); + + // Verify pending balance is now zero + let new_pending = middleware_disp.get_pending_balance(USN(), USDT(), ETHEREUM_DOMAIN); + assert(new_pending == Zero::zero(), 'pending should be zero'); + + // Verify USDT was transferred to vault allocator + let new_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_vault_usdt == usdt_received, 'vault should have usdt'); + + // Verify middleware has 0 USDT now + assert( + usdt_disp.balance_of(setup.hyperlane_middleware) == Zero::zero(), + 'middleware should have 0', + ); + + // Verify ClaimedToken event was emitted + spy + .assert_emitted( + @array![ + ( + setup.hyperlane_middleware, + HyperlaneMiddleware::Event::ClaimedToken( + HyperlaneMiddleware::ClaimedToken { + token_to_bridge: USN(), + token_to_claim: USDT(), + destination_domain: ETHEREUM_DOMAIN, + amount_claimed: usdt_received, + }, + ), + ), + ], + ); +} diff --git a/packages/vault_allocator/src/test/middleware/lz_middleware.cairo b/packages/vault_allocator/src/test/middleware/lz_middleware.cairo new file mode 100644 index 00000000..aba2f5cf --- /dev/null +++ b/packages/vault_allocator/src/test/middleware/lz_middleware.cairo @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use core::num::traits::Zero; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, map_entry_address, + spy_events, store, +}; +use starknet::ContractAddress; +use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; +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::lz::{LzMiddlewareConfig, _add_lz_middleware_leafs}; +use vault_allocator::merkle_tree::registery::{LZ_WBTC_OFT_ADAPTER, PRICE_ROUTER, STRK, USDC, WBTC}; +use vault_allocator::middlewares::lz_middleware::interface::{ + ILzMiddlewareDispatcher, ILzMiddlewareDispatcherTrait, +}; +use vault_allocator::middlewares::lz_middleware::lz_middleware::LzMiddleware; +use vault_allocator::test::utils::{ + OWNER, STRATEGIST, cheat_caller_address_once, deploy_manager, deploy_vault_allocator, + set_token_balance, +}; +use vault_allocator::vault_allocator::interface::{ + IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, +}; + + +// ============ Dedicated Decoder and Sanitizer for LZ Middleware ============ +#[starknet::contract] +pub mod LzMiddlewareTestDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::lz_middleware_decoder_and_sanitizer::lz_middleware_decoder_and_sanitizer::LzMiddlewareDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: LzMiddlewareDecoderAndSanitizerComponent, + storage: lz_middleware_decoder_and_sanitizer, + event: LzMiddlewareDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl LzMiddlewareDecoderAndSanitizerImpl = + LzMiddlewareDecoderAndSanitizerComponent::LzMiddlewareDecoderAndSanitizerImpl< + ContractState, + >; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub lz_middleware_decoder_and_sanitizer: LzMiddlewareDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + LzMiddlewareDecoderAndSanitizerEvent: LzMiddlewareDecoderAndSanitizerComponent::Event, + } +} +// =================================================================================== + +// ============ Test Setup ============ +#[derive(Drop)] +struct TestSetup { + vault_allocator: IVaultAllocatorDispatcher, + manager: IManagerDispatcher, + decoder_and_sanitizer: ContractAddress, + lz_middleware: ContractAddress, + leafs: Array, + tree: Array>, +} + +// LayerZero destination endpoint ID for Ethereum mainnet +const ETH_EID: u32 = 30101; + +// Recipient address on Ethereum (example address) +fn TO() -> u256 { + 0x732357e321Bf7a02CbB690fc2a629161D7722e29_u256 +} + +fn deploy_lz_middleware_decoder_and_sanitizer() -> ContractAddress { + let decoder = declare("LzMiddlewareTestDecoderAndSanitizer").unwrap().contract_class(); + let calldata = ArrayTrait::new(); + let (decoder_address, _) = decoder.deploy(@calldata).unwrap(); + decoder_address +} + +fn deploy_lz_middleware( + vault_allocator: ContractAddress, slippage: u16, period: u64, allowed_calls_per_period: u64, +) -> ContractAddress { + let lz_middleware = declare("LzMiddleware").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + OWNER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); + PRICE_ROUTER().serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); + let (lz_middleware_address, _) = lz_middleware.deploy(@calldata).unwrap(); + lz_middleware_address +} + +fn setup() -> TestSetup { + // Deploy vault allocator, manager + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator); + + // Deploy dedicated decoder and sanitizer for lz middleware + let decoder_and_sanitizer = deploy_lz_middleware_decoder_and_sanitizer(); + + // Deploy lz middleware + // Params: 1% slippage (100 bps), period 100000, allowed calls 1000000 + let lz_middleware = deploy_lz_middleware( + vault_allocator.contract_address, 100, 100000, 1000000, + ); + + // Build merkle tree with lz middleware leafs + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_lz_middleware_leafs( + ref leafs, + ref leaf_index, + decoder_and_sanitizer, + vault_allocator.contract_address, + array![ + LzMiddlewareConfig { + middleware: lz_middleware, + oft: LZ_WBTC_OFT_ADAPTER(), + underlying_token: WBTC(), + token_to_claim: USDC(), + dst_eid: ETH_EID, + to: TO(), + }, + ] + .span(), + ); + + _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); + + // Set manager on vault allocator + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + // Set manage root for strategist + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + TestSetup { vault_allocator, manager, decoder_and_sanitizer, lz_middleware, leafs, tree } +} + +// ==================================== + +#[fork("LZ")] +#[test] +fn test_lz_middleware_send() { + let setup = setup(); + + // Add WBTC balance to vault allocator (WBTC has 8 decimals) + let initial_wbtc_balance: u256 = 100_000_000; // 1 WBTC + set_token_balance(WBTC(), setup.vault_allocator.contract_address, initial_wbtc_balance); + let wbtc_disp = ERC20ABIDispatcher { contract_address: WBTC() }; + assert( + wbtc_disp.balance_of(setup.vault_allocator.contract_address) == initial_wbtc_balance, + 'wbtc balance is not correct', + ); + + // Add STRK balance to vault allocator for fees (STRK has 18 decimals) + let strk_fee: u256 = 100_000_000_000_000_000; // 0.1 STRK + set_token_balance(STRK(), setup.vault_allocator.contract_address, strk_fee); + let strk_disp = ERC20ABIDispatcher { contract_address: STRK() }; + assert( + strk_disp.balance_of(setup.vault_allocator.contract_address) == strk_fee, + 'strk balance is not correct', + ); + + // Bridge 0.5 WBTC + let bridge_amount: u256 = 50_000_000; // 0.5 WBTC + let min_amount: u256 = bridge_amount; + + // Build calldata for approve WBTC + approve STRK + send + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(WBTC()); // approve WBTC + array_of_targets.append(STRK()); // approve STRK + array_of_targets.append(setup.lz_middleware); // send + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("send")); + + let mut array_of_calldatas = ArrayTrait::new(); + + // Calldata for approve WBTC(lz_middleware, bridge_amount) + let mut calldata_approve_wbtc: Array = ArrayTrait::new(); + setup.lz_middleware.serialize(ref calldata_approve_wbtc); + bridge_amount.serialize(ref calldata_approve_wbtc); + array_of_calldatas.append(calldata_approve_wbtc.span()); + + // Calldata for approve STRK(lz_middleware, strk_fee) + let mut calldata_approve_strk: Array = ArrayTrait::new(); + setup.lz_middleware.serialize(ref calldata_approve_strk); + strk_fee.serialize(ref calldata_approve_strk); + array_of_calldatas.append(calldata_approve_strk.span()); + + // Calldata for send + let mut calldata_send: Array = ArrayTrait::new(); + + // oft + LZ_WBTC_OFT_ADAPTER().serialize(ref calldata_send); + + // underlying_token + WBTC().serialize(ref calldata_send); + + // token_to_claim + USDC().serialize(ref calldata_send); + + // SendParam struct + // dst_eid + ETH_EID.serialize(ref calldata_send); + + // to + TO().serialize(ref calldata_send); + + // amount_ld + bridge_amount.serialize(ref calldata_send); + + // min_amount_ld + min_amount.serialize(ref calldata_send); + + let extra_options: ByteArray = ""; + extra_options.serialize(ref calldata_send); + let compose_msg: ByteArray = ""; + compose_msg.serialize(ref calldata_send); + let oft_cmd: ByteArray = ""; + oft_cmd.serialize(ref calldata_send); + + // MessagingFee struct + // native_fee + strk_fee.serialize(ref calldata_send); + + // lz_token_fee + let lz_token_fee: u256 = 0; + lz_token_fee.serialize(ref calldata_send); + + // refund_address + setup.vault_allocator.contract_address.serialize(ref calldata_send); + + array_of_calldatas.append(calldata_send.span()); + + // Get proofs + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(setup.leafs.at(0).clone()); // approve WBTC + manage_leafs.append(setup.leafs.at(1).clone()); // approve STRK + manage_leafs.append(setup.leafs.at(2).clone()); // send + + let manage_proofs = _get_proofs_using_tree(manage_leafs, setup.tree.clone()); + + // Spy on events + let mut spy = spy_events(); + + // Execute the bridge operation + cheat_caller_address_once(setup.manager.contract_address, STRATEGIST()); + setup + .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 WBTC was transferred from vault allocator + let new_wbtc_balance = wbtc_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_wbtc_balance == initial_wbtc_balance - bridge_amount, 'wbtc balance incorrect'); + + // Verify middleware has pending balance + let middleware_disp = ILzMiddlewareDispatcher { contract_address: setup.lz_middleware }; + let pending = middleware_disp.get_pending_balance(WBTC(), USDC(), ETH_EID); + assert(pending == bridge_amount, 'pending balance incorrect'); + + // Verify BridgeInitiated event was emitted + spy + .assert_emitted( + @array![ + ( + setup.lz_middleware, + LzMiddleware::Event::BridgeInitiated( + LzMiddleware::BridgeInitiated { + underlying_token: WBTC(), + token_to_claim: USDC(), + dst_eid: ETH_EID, + to: TO(), + amount: bridge_amount, + guid: 0 // guid will be mocked/ignored in fork test + }, + ), + ), + ], + ); +} + + +#[fork("LZ")] +#[test] +fn test_lz_middleware_claim_usdc() { + let setup = setup(); + + // SETUP: Simulate that a bridge operation already happened + // 1. Set pending balance in middleware storage + let bridge_amount: u256 = 50_000_000; // 0.5 WBTC + + // Store pending balance: Map<(underlying_token, token_to_claim, dst_eid), u256> + let mut pending_calldata = ArrayTrait::new(); + bridge_amount.serialize(ref pending_calldata); + store( + setup.lz_middleware, + map_entry_address( + selector!("pending_balance"), + array![WBTC().into(), USDC().into(), ETH_EID.into()].span(), + ), + pending_calldata.span(), + ); + + // 2. Simulate USDC arriving at the middleware (from bridge on return) + // USDC has 6 decimals, simulate receiving 1000 USDC (equivalent value for 0.5 WBTC) + let usdc_received: u256 = 1_000_000_000; // 1000 USDC + set_token_balance(USDC(), setup.lz_middleware, usdc_received); + + // Verify setup + let middleware_disp = ILzMiddlewareDispatcher { contract_address: setup.lz_middleware }; + let pending = middleware_disp.get_pending_balance(WBTC(), USDC(), ETH_EID); + assert(pending == bridge_amount, 'setup: pending incorrect'); + + let usdc_disp = ERC20ABIDispatcher { contract_address: USDC() }; + assert(usdc_disp.balance_of(setup.lz_middleware) == usdc_received, 'setup: usdc incorrect'); + + // Verify vault allocator has 0 USDC initially + let initial_vault_usdc = usdc_disp.balance_of(setup.vault_allocator.contract_address); + assert(initial_vault_usdc == Zero::zero(), 'vault should have 0 usdc'); + + // Spy on events + let mut spy = spy_events(); + + // Call claim_token (permissionless - anyone can call) + middleware_disp.claim_token(WBTC(), USDC(), ETH_EID); + + // Verify pending balance is now zero + let new_pending = middleware_disp.get_pending_balance(WBTC(), USDC(), ETH_EID); + assert(new_pending == Zero::zero(), 'pending should be zero'); + + // Verify USDC was transferred to vault allocator + let new_vault_usdc = usdc_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_vault_usdc == usdc_received, 'vault should have usdc'); + + // Verify middleware has 0 USDC now + assert(usdc_disp.balance_of(setup.lz_middleware) == Zero::zero(), 'middleware should have 0'); + + // Verify ClaimedToken event was emitted + spy + .assert_emitted( + @array![ + ( + setup.lz_middleware, + LzMiddleware::Event::ClaimedToken( + LzMiddleware::ClaimedToken { + underlying_token: WBTC(), + token_to_claim: USDC(), + dst_eid: ETH_EID, + amount_claimed: usdc_received, + }, + ), + ), + ], + ); +} diff --git a/packages/vault_allocator/src/test/middleware/starkgate_middleware.cairo b/packages/vault_allocator/src/test/middleware/starkgate_middleware.cairo new file mode 100644 index 00000000..4cea4511 --- /dev/null +++ b/packages/vault_allocator/src/test/middleware/starkgate_middleware.cairo @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use core::num::traits::Zero; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use snforge_std::{ + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, map_entry_address, + spy_events, store, +}; +use starknet::{ContractAddress, EthAddress}; +use vault_allocator::integration_interfaces::starkgate::{ + IStarkgateABIDispatcher, IStarkgateABIDispatcherTrait, +}; +use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; +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::starkgate::{ + StarkgateMiddlewareConfig, _add_starkgate_middleware_leafs, +}; +use vault_allocator::merkle_tree::registery::{PRICE_ROUTER, STARKGATE_USDC_BRIDGE, USDC, USDT}; +use vault_allocator::middlewares::starkgate_middleware::interface::{ + IStarkgateMiddlewareDispatcher, IStarkgateMiddlewareDispatcherTrait, +}; +use vault_allocator::middlewares::starkgate_middleware::starkgate_middleware::StarkgateMiddleware; +use vault_allocator::test::utils::{ + OWNER, STRATEGIST, cheat_caller_address_once, deploy_manager, deploy_vault_allocator, + set_token_balance, +}; +use vault_allocator::vault_allocator::interface::{ + IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, +}; + + +// ============ Dedicated Decoder and Sanitizer for Starkgate Middleware ============ +#[starknet::contract] +pub mod StarkgateMiddlewareTestDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::starkgate_middleware_decoder_and_sanitizer::starkgate_middleware_decoder_and_sanitizer::StarkgateMiddlewareDecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: StarkgateMiddlewareDecoderAndSanitizerComponent, + storage: starkgate_middleware_decoder_and_sanitizer, + event: StarkgateMiddlewareDecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl StarkgateMiddlewareDecoderAndSanitizerImpl = + StarkgateMiddlewareDecoderAndSanitizerComponent::StarkgateMiddlewareDecoderAndSanitizerImpl< + ContractState, + >; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub starkgate_middleware_decoder_and_sanitizer: StarkgateMiddlewareDecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + StarkgateMiddlewareDecoderAndSanitizerEvent: StarkgateMiddlewareDecoderAndSanitizerComponent::Event, + } +} +// =================================================================================== + +// ============ Test Setup ============ +#[derive(Drop)] +struct TestSetup { + vault_allocator: IVaultAllocatorDispatcher, + manager: IManagerDispatcher, + decoder_and_sanitizer: ContractAddress, + starkgate_middleware: ContractAddress, + leafs: Array, + tree: Array>, +} + +fn L1_RECIPIENT() -> EthAddress { + 0x3823829328.try_into().unwrap() +} + +fn deploy_starkgate_middleware_decoder_and_sanitizer() -> ContractAddress { + let decoder = declare("StarkgateMiddlewareTestDecoderAndSanitizer").unwrap().contract_class(); + let calldata = ArrayTrait::new(); + let (decoder_address, _) = decoder.deploy(@calldata).unwrap(); + decoder_address +} + +fn deploy_starkgate_middleware( + vault_allocator: ContractAddress, slippage: u16, period: u64, allowed_calls_per_period: u64, +) -> ContractAddress { + let starkgate_middleware = declare("StarkgateMiddleware").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + OWNER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); + PRICE_ROUTER().serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); + let (starkgate_middleware_address, _) = starkgate_middleware.deploy(@calldata).unwrap(); + starkgate_middleware_address +} + +fn setup() -> TestSetup { + // Deploy vault allocator, manager + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator); + + // Deploy dedicated decoder and sanitizer for starkgate middleware + let decoder_and_sanitizer = deploy_starkgate_middleware_decoder_and_sanitizer(); + + // Deploy starkgate middleware + // Params: 1% slippage (100 bps), period 100000, allowed calls 1000000 + let starkgate_middleware = deploy_starkgate_middleware( + vault_allocator.contract_address, 100, 100000, 1000000, + ); + + // Build merkle tree with starkgate middleware leafs + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_starkgate_middleware_leafs( + ref leafs, + ref leaf_index, + decoder_and_sanitizer, + array![ + StarkgateMiddlewareConfig { + middleware: starkgate_middleware, + l2_bridge: STARKGATE_USDC_BRIDGE(), + l2_token: USDC(), + l1_recipient: L1_RECIPIENT(), + token_to_claim: USDT(), + }, + ] + .span(), + ); + + _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); + + // Set manager on vault allocator + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + // Set manage root for strategist + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + TestSetup { vault_allocator, manager, decoder_and_sanitizer, starkgate_middleware, leafs, tree } +} + +// ==================================== + +#[fork("STARKGATE_MIDDLEWARE")] +#[test] +fn test_starkgate_middleware_bridge_usdc() { + let setup = setup(); + + // Add USDC balance to vault allocator (USDC has 6 decimals) + let initial_usdc_balance: u256 = 10_000_000; // 10 USDC + set_token_balance(USDC(), setup.vault_allocator.contract_address, initial_usdc_balance); + let usdc_disp = ERC20ABIDispatcher { contract_address: USDC() }; + assert( + usdc_disp.balance_of(setup.vault_allocator.contract_address) == initial_usdc_balance, + 'usdc balance is not correct', + ); + + // Bridge 5 USDC + let bridge_amount: u256 = 5_000_000; // 5 USDC + + // Build calldata for approve + initiate_token_withdraw + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(setup.decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(USDC()); // approve + array_of_targets.append(setup.starkgate_middleware); // initiate_token_withdraw + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("initiate_token_withdraw")); + + let mut array_of_calldatas = ArrayTrait::new(); + + // Calldata for approve(starkgate_middleware, bridge_amount) + let mut calldata_approve: Array = ArrayTrait::new(); + setup.starkgate_middleware.serialize(ref calldata_approve); + bridge_amount.serialize(ref calldata_approve); + array_of_calldatas.append(calldata_approve.span()); + + // Calldata for initiate_token_withdraw + let mut calldata_initiate: Array = ArrayTrait::new(); + + // starkgate_token_bridge + STARKGATE_USDC_BRIDGE().serialize(ref calldata_initiate); + + // l1_token - get it from the bridge + let bridge = IStarkgateABIDispatcher { contract_address: STARKGATE_USDC_BRIDGE() }; + let l1_token = bridge.get_l1_token(USDC()); + l1_token.serialize(ref calldata_initiate); + + // l1_recipient + L1_RECIPIENT().serialize(ref calldata_initiate); + + // amount + bridge_amount.serialize(ref calldata_initiate); + + // token_to_claim + USDT().serialize(ref calldata_initiate); + + array_of_calldatas.append(calldata_initiate.span()); + + // Get proofs + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(setup.leafs.at(0).clone()); // approve + manage_leafs.append(setup.leafs.at(1).clone()); // initiate_token_withdraw + + let manage_proofs = _get_proofs_using_tree(manage_leafs, setup.tree.clone()); + + // Spy on events + let mut spy = spy_events(); + + // Execute the bridge operation + cheat_caller_address_once(setup.manager.contract_address, STRATEGIST()); + setup + .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 USDC was transferred from vault allocator + let new_usdc_balance = usdc_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_usdc_balance == initial_usdc_balance - bridge_amount, 'usdc balance incorrect'); + + // Verify middleware has pending balance + let middleware_disp = IStarkgateMiddlewareDispatcher { + contract_address: setup.starkgate_middleware, + }; + let pending = middleware_disp.get_pending_balance(USDC(), USDT()); + assert(pending == bridge_amount, 'pending balance incorrect'); + + // Verify WithdrawInitiated event was emitted + spy + .assert_emitted( + @array![ + ( + setup.starkgate_middleware, + StarkgateMiddleware::Event::WithdrawInitiated( + StarkgateMiddleware::WithdrawInitiated { + token_to_bridge: USDC(), + token_to_claim: USDT(), + l1_token: l1_token, + l1_recipient: L1_RECIPIENT(), + amount: bridge_amount, + }, + ), + ), + ], + ); +} + + +#[fork("STARKGATE_MIDDLEWARE")] +#[test] +fn test_starkgate_middleware_claim_usdt() { + let setup = setup(); + + // SETUP: Simulate that a bridge operation already happened + // 1. Set pending balance in middleware storage + let bridge_amount: u256 = 5_000_000; // 5 USDC + + // Store pending balance: Map<(token_to_bridge, token_to_claim), u256> + let mut pending_calldata = ArrayTrait::new(); + bridge_amount.serialize(ref pending_calldata); + store( + setup.starkgate_middleware, + map_entry_address( + selector!("pending_balance"), array![USDC().into(), USDT().into()].span(), + ), + pending_calldata.span(), + ); + + // 2. Simulate USDT arriving at the middleware (from L1 bridge) + // USDT has 6 decimals, simulate receiving 5 USDT + let usdt_received: u256 = 5_000_000; // 5 USDT + set_token_balance(USDT(), setup.starkgate_middleware, usdt_received); + + // Verify setup + let middleware_disp = IStarkgateMiddlewareDispatcher { + contract_address: setup.starkgate_middleware, + }; + let pending = middleware_disp.get_pending_balance(USDC(), USDT()); + assert(pending == bridge_amount, 'setup: pending incorrect'); + + let usdt_disp = ERC20ABIDispatcher { contract_address: USDT() }; + assert( + usdt_disp.balance_of(setup.starkgate_middleware) == usdt_received, + 'setup: usdt + incorrect', + ); + + // Verify vault allocator has 0 USDT initially + let initial_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(initial_vault_usdt == Zero::zero(), 'vault should have 0 usdt'); + + // Spy on events + let mut spy = spy_events(); + + // Call claim_token + middleware_disp.claim_token(USDC(), USDT()); + + // Verify pending balance is now zero + let new_pending = middleware_disp.get_pending_balance(USDC(), USDT()); + assert(new_pending == Zero::zero(), 'pending should be zero'); + + // Verify USDT was transferred to vault allocator + let new_vault_usdt = usdt_disp.balance_of(setup.vault_allocator.contract_address); + assert(new_vault_usdt == usdt_received, 'vault should have usdt'); + + // Verify middleware has 0 USDT now + assert( + usdt_disp.balance_of(setup.starkgate_middleware) == Zero::zero(), + 'middleware should have 0', + ); + + // Verify ClaimedToken event was emitted + spy + .assert_emitted( + @array![ + ( + setup.starkgate_middleware, + StarkgateMiddleware::Event::ClaimedToken( + StarkgateMiddleware::ClaimedToken { + token_to_bridge: USDC(), + token_to_claim: USDT(), + amount_claimed: usdt_received, + }, + ), + ), + ], + ); +} + diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 050deec2..782ab59a 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -3,8 +3,13 @@ // Licensed under the MIT License. See LICENSE file for details. use openzeppelin::merkle_tree::hashes::PedersenCHasher; -use snforge_std::{CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare}; +use snforge_std::{ + CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, + map_entry_address, store, +}; use starknet::{ClassHash, ContractAddress}; +use vault_allocator::adapters::ekubo_adapter::interface::IEkuboAdapterDispatcher; +use vault_allocator::integration_interfaces::ekubo::{Bounds, PoolKey}; 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, @@ -16,6 +21,7 @@ use vault_allocator::periphery::price_router::interface::{ IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; 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% @@ -45,6 +51,28 @@ pub fn deploy_vault_allocator() -> IVaultAllocatorDispatcher { IVaultAllocatorDispatcher { contract_address: vault_allocator_address } } +pub fn deploy_ekubo_adapter( + owner: ContractAddress, + vault_allocator: ContractAddress, + ekubo_positions_contract: ContractAddress, + bounds_settings: Bounds, + pool_key: PoolKey, + ekubo_positions_nft: ContractAddress, + ekubo_core: ContractAddress, +) -> IEkuboAdapterDispatcher { + let ekubo_adapter = declare("EkuboAdapter").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + owner.serialize(ref calldata); + vault_allocator.serialize(ref calldata); + ekubo_positions_contract.serialize(ref calldata); + bounds_settings.serialize(ref calldata); + pool_key.serialize(ref calldata); + ekubo_positions_nft.serialize(ref calldata); + ekubo_core.serialize(ref calldata); + let (ekubo_adapter_address, _) = ekubo_adapter.deploy(@calldata).unwrap(); + IEkuboAdapterDispatcher { contract_address: ekubo_adapter_address } +} + pub fn deploy_manager(vault_allocator: IVaultAllocatorDispatcher) -> IManagerDispatcher { let manager = declare("Manager").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); @@ -160,3 +188,26 @@ pub fn cheat_caller_address_once( cheat_caller_address(:contract_address, :caller_address, span: CheatSpan::TargetCalls(1)); } +/// Sets the ERC20 balance of `account` for the given `token` to `amount`. +/// This directly writes to the contract storage, bypassing normal transfer logic. +pub fn set_token_balance(token: ContractAddress, account: ContractAddress, amount: u256) { + let mut calldata = ArrayTrait::new(); + amount.serialize(ref calldata); + store( + token, + map_entry_address(selector!("ERC20_balances"), array![account.into()].span()), + calldata.span(), + ); +} + +/// Sets the ERC20 balance for tokens that use 'balances' storage selector (e.g., Circle USDC_CCTP). +pub fn set_token_balance_circle(token: ContractAddress, account: ContractAddress, amount: u256) { + let mut calldata = ArrayTrait::new(); + amount.serialize(ref calldata); + store( + token, + map_entry_address(selector!("balances"), array![account.into()].span()), + calldata.span(), + ); +} + diff --git a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo index 54be9608..ace67bb9 100644 --- a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo +++ b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo @@ -5,7 +5,9 @@ #[starknet::contract] pub mod VaultAllocator { use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc721::{ERC721ReceiverMixin, IERC721_RECEIVER_ID}; use openzeppelin::interfaces::upgrades::IUpgradeable; + use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::account::Call; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; @@ -14,11 +16,14 @@ pub mod VaultAllocator { use vault_allocator::vault_allocator::errors::Errors; use vault_allocator::vault_allocator::interface::IVaultAllocator; + component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[storage] struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -29,7 +34,11 @@ pub mod VaultAllocator { #[event] #[derive(Drop, starknet::Event)] pub enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] OwnableEvent: OwnableComponent::Event, + #[flat] UpgradeableEvent: UpgradeableComponent::Event, CallPerformed: CallPerformed, } @@ -55,6 +64,9 @@ pub mod VaultAllocator { impl OwnableInternalImpl = OwnableComponent::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + #[abi(embed_v0)] impl UpgradeableImpl of IUpgradeable { fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { @@ -92,6 +104,32 @@ pub mod VaultAllocator { } } + #[abi(embed_v0)] + impl ERC721ReceiverMixinImpl of ERC721ReceiverMixin { + fn on_erc721_received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span, + ) -> felt252 { + IERC721_RECEIVER_ID + } + fn onERC721Received( + self: @ContractState, + operator: ContractAddress, + from: ContractAddress, + tokenId: u256, + data: Span, + ) -> felt252 { + IERC721_RECEIVER_ID + } + + fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + self.src5.supports_interface(interface_id) + } + } + #[generate_trait] impl InternalFunctions of InternalFunctionsTrait { @@ -107,7 +145,9 @@ pub mod VaultAllocator { selector: felt252, calldata: Span, ) -> Span { + self.src5.register_interface(IERC721_RECEIVER_ID); let result = call_contract_syscall(to, selector, calldata).unwrap_syscall(); + self.src5.deregister_interface(IERC721_RECEIVER_ID); self.emit(CallPerformed { to, selector, calldata, result }); result } diff --git a/scripts/calculate_tick.ts b/scripts/calculate_tick.ts new file mode 100644 index 00000000..45f1271c --- /dev/null +++ b/scripts/calculate_tick.ts @@ -0,0 +1,34 @@ +import { Decimal } from "decimal.js"; + +Decimal.set({ precision: 78 }); + +function getSqrtPriceRangeAroundParity( + token0Decimals: number, + token1Decimals: number, + deltaPercent: number = 0.04 +): { sqrtRatioLower: bigint; sqrtRatioUpper: bigint } { + const decimalDiff = token1Decimals - token0Decimals; + const parityPrice = new Decimal(10).pow(decimalDiff); + const deltaMultiplier = new Decimal(deltaPercent).div(100); + const priceLower = parityPrice.mul(new Decimal(1).minus(deltaMultiplier)); + const priceUpper = parityPrice.mul(new Decimal(1).plus(deltaMultiplier)); + const twoTo128 = new Decimal(2).pow(128); + const sqrtRatioLower = priceLower.sqrt().mul(twoTo128).floor(); + const sqrtRatioUpper = priceUpper.sqrt().mul(twoTo128).floor(); + return { + sqrtRatioLower: BigInt(sqrtRatioLower.toFixed(0)), + sqrtRatioUpper: BigInt(sqrtRatioUpper.toFixed(0)), + }; +} + +async function main() { + const wbtcDecimals = 8; + const solvDecimals = 18; + const { sqrtRatioLower, sqrtRatioUpper } = getSqrtPriceRangeAroundParity( + wbtcDecimals, + solvDecimals + ); + console.log(sqrtRatioLower, sqrtRatioUpper); +} + +main(); diff --git a/scripts/configs/config.json b/scripts/configs/config.json index 6d971624..01c4c231 100644 --- a/scripts/configs/config.json +++ b/scripts/configs/config.json @@ -3,22 +3,41 @@ "hash": {}, "periphery": {} }, + "paradex_prod": { + "hash": {}, + "periphery": {} + }, + "paradex_testnet": { + "hash": { + "VaultAllocator": "0x64e1ce32f32901d651878dfa4f6363024deb5c270bc4e532f77ac6c71fbbcd2", + "ForgeyieldsParadexDecoderAndSanitizer": "0x69fa1a453135ec4a40ef3471ba8b7475c58fba4a818a4174cdef1bf6be9c79a", + "ParadexGigaVaultMiddleware": "0x7088ae1b362dfdf615394f88e384ec76fb4016f76f0020d588e9da5314813", + "Manager": "0x474abeaa804f4dbad841fa782cef87501ee6659cd53a5c01bcf268ebae933d4" + }, + "periphery": {} + }, "mainnet": { "hash": { - "Vault": "0x3c06f3c8a6a3f9f9bfdf67c4e3ff45613dabb6c5e4c84baca4b923361ff66fc", - "VaultAllocator": "0xbf475be37c67c2b2b400dc4433f162ea516c40bd6ac4fc0bc5452a6a61539f", - "RedeemRequest": "0x76b42bba1d387b1d4a91f4b3e660365b25e5e6f1a0a27b9417283e45e7034ed", - "AvnuMiddleware": "0x3203bdc3f9011677cab5ce2eac757a31ff9694c7d5b78cc7d1de045a0201b28", - "Manager": "0x743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", - "PriceRouter": "0x677bd52be1ce091b133c5bd637cec20df7f1390adfa64de98791e2928d5d3c1", - "SimpleDecoderAndSanitizer": "0x36c29d91a5f1cf1eb62acad4c39803061fd184b0d16a78721b7fcd725756625", + "Vault": "0x225ca31b59b9f0e72dbbe72696aae6cf3431c68a2b993e9ecc10a62dd91ea04", + "VaultAllocator": "0x4d838ecc1ef60da3461c1687bee3ce175a5d5c8f6f3bd645d70ef5efe86eb1f", + "RedeemRequest": "0x6dff890f7d5976a343b111f5c275cccd5838498221d199558a1c46799d072cc", + "AvnuMiddleware": "0x30c8c751cedb2339a65b1b4e518cd9a8ccb56b7c397c1609362b02ac482ae6a", + "Manager": "0x23a91c52488034095902965133a91f4a271ba00e9cdf2c2f944965e28876c67", + "PriceRouter": "0x0388513a8239b53c9dba7a19049e9159f73a9d196dcb10745489bdc7d65839e5", + "SimpleDecoderAndSanitizer": "0x45cfd32b5555ba4665fe4dca3602bf88d7780a58f7a5dfe57ccb935b7cd2627", "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb", - "VesuV2SpecificDecoderAndSanitizer": "0x2da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29" + "VesuV2SpecificDecoderAndSanitizer": "0x02da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29", + "StarkgateMiddleware": "0x02ba274e79e43284d2003f18d059af27d87165594e2902792eb9f5876499c3a9", + "HyperlaneMiddleware": "0x4ed2b0b657e9894ef31c0cc2db747270290cdb8983e87caf8dd9f458ba5bab2", + "EkuboAdapter": "0xa0f901eb8a6654a367fffc95e0bf0f782f50aa3102dbe01649904bd99604bb", + "FyWBTCDecoderAndSanitizer": "0x15b6e96eb6fb84e3aad8ec2252b59b89ade1f34a8550fa78f9fdb4ef5d8e987", + "AssetTransferPod": "0x33a25fdbfb3104698d37bfead121bd7d09d1280ea04b26cbe6a9e0544e99fc1" }, "periphery": { "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", "avnuRouter": "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", - "pragma": "0x02a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b" + "pragma": "0x02a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b", + "priceRouter": "0x0566440457a0c6189C5dCBb661bD8F9A09961c78bFb50F6648977e9ca5972619" } } } \ No newline at end of file diff --git a/scripts/declareContract.ts b/scripts/declareContract.ts index a4299ea7..10c07823 100644 --- a/scripts/declareContract.ts +++ b/scripts/declareContract.ts @@ -8,13 +8,11 @@ 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 owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); export async function declareContract( envNetwork: string, @@ -100,6 +98,47 @@ async function main() { "VesuV2SpecificDecoderAndSanitizer" ); break; + case "ForgeyieldsParadexDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "ForgeyieldsParadexDecoderAndSanitizer" + ); + break; + case "FyWBTCDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "FyWBTCDecoderAndSanitizer" + ); + break; + case "ParadexGigaVaultMiddleware": + await declareContract( + envNetwork, + "vault_allocator", + "ParadexGigaVaultMiddleware" + ); + break; + case "StarkgateMiddleware": + await declareContract( + envNetwork, + "vault_allocator", + "StarkgateMiddleware" + ); + break; + case "HyperlaneMiddleware": + await declareContract( + envNetwork, + "vault_allocator", + "HyperlaneMiddleware" + ); + break; + case "EkuboAdapter": + await declareContract(envNetwork, "vault_allocator", "EkuboAdapter"); + break; + case "AssetTransferPod": + await declareContract(envNetwork, "vault_allocator", "AssetTransferPod"); + break; case "AumProvider4626": await declareContract(envNetwork, "vault", "AumProvider4626"); break; diff --git a/scripts/deployAssetTransferPod.ts b/scripts/deployAssetTransferPod.ts new file mode 100644 index 00000000..93db84e4 --- /dev/null +++ b/scripts/deployAssetTransferPod.ts @@ -0,0 +1,145 @@ +import { Account, RpcProvider, CallData } from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveContractDeployment } from "./utils/deployment"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); + +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()); + }); + }); +} + +// ============================================ +// FILL IN THE PARAMETERS BELOW +// ============================================ + +// Vault allocator address that will control this pod +const VAULT_ALLOCATOR_ADDRESS = + "0x7347602aedf0197492a6d10f7e9d9dda45493e62b26bd540e980617e92b4e38"; + +// Owner address that can manage the pod +const OWNER_ADDRESS = owner.address; + +// Authorized caller address that can transfer assets +const AUTHORIZED_CALLER_ADDRESS = + "0x0725F4506F4459E816164a3aA22660A47E8fD91aa4284416592541bC100E254A"; + +// ============================================ +// END OF PARAMETERS +// ============================================ + +export async function deployAssetTransferPod(envNetwork: string) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.AssetTransferPod; + if (!classHash) { + throw new Error( + `AssetTransferPod class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + // Validate parameters + if (!VAULT_ALLOCATOR_ADDRESS) { + throw new Error("VAULT_ALLOCATOR_ADDRESS is required"); + } + if (!OWNER_ADDRESS) { + throw new Error("OWNER_ADDRESS is required"); + } + if (!AUTHORIZED_CALLER_ADDRESS) { + throw new Error("AUTHORIZED_CALLER_ADDRESS is required"); + } + + try { + console.log(`Deploying AssetTransferPod with constructor params:`); + console.log(` Vault Allocator: ${VAULT_ALLOCATOR_ADDRESS}`); + console.log(` Owner: ${OWNER_ADDRESS}`); + console.log(` Authorized Caller: ${AUTHORIZED_CALLER_ADDRESS}`); + + // Construct calldata matching constructor order: + // vault_allocator, owner, authorized_caller + const constructorCalldata = CallData.compile({ + vault_allocator: VAULT_ALLOCATOR_ADDRESS, + owner: OWNER_ADDRESS, + authorized_caller: AUTHORIZED_CALLER_ADDRESS, + }); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`\nAssetTransferPod deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying AssetTransferPod:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + console.log("Initializing AssetTransferPod deployment process...\n"); + + console.log("\nDeployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Deployer: ${owner.address}`); + + const confirm = await askQuestion( + "\nDo you want to proceed with deployment? (yes/no): " + ); + if (confirm.toLowerCase() !== "yes") { + console.log("Deployment cancelled."); + rl.close(); + return; + } + + console.log("\nDeploying AssetTransferPod..."); + const podAddress = await deployAssetTransferPod(envNetwork); + + const saveName = await askQuestion( + "\nEnter a name to save this deployment (leave empty to skip): " + ); + if (saveName) { + saveContractDeployment(envNetwork, saveName, podAddress, "deployed"); + console.log(`Deployment saved as: ${saveName}`); + } + + console.log("\nDeployment completed successfully!"); + console.log(`AssetTransferPod Address: ${podAddress}`); + } catch (error) { + console.error("\nDeployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/deployContract.ts b/scripts/deployContract.ts index 9897ad9c..afedfd18 100644 --- a/scripts/deployContract.ts +++ b/scripts/deployContract.ts @@ -1,4 +1,4 @@ -import { Account, RpcProvider, CallData, CairoUint256 } from "starknet"; +import { Account, RpcProvider, CallData } from "starknet"; import dotenv from "dotenv"; import { readConfigs } from "./configs/utils"; import { getNetworkEnv } from "./utils"; @@ -7,13 +7,11 @@ 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" -); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); export async function deployContract( envNetwork: string, @@ -74,6 +72,10 @@ export async function deploySimpleDecoderAndSanitizer(envNetwork: string) { return await deployContract(envNetwork, "SimpleDecoderAndSanitizer", []); } +export async function deployFyWBTCDecoderAndSanitizer(envNetwork: string) { + return await deployContract(envNetwork, "FyWBTCDecoderAndSanitizer", []); +} + export async function deployPriceRouter(envNetwork: string) { const config = readConfigs(); const networkConfig = config[envNetwork]; @@ -96,7 +98,10 @@ export async function deployPriceRouter(envNetwork: string) { export async function deployAvnuMiddleware( envNetwork: string, - slippage_tolerance_bps: number + vaultAllocator: string, + slippage: number, + period: number, + allowedCallsPerPeriod: number ) { const config = readConfigs(); const networkConfig = config[envNetwork]; @@ -104,29 +109,20 @@ export async function deployAvnuMiddleware( 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; + const priceRouter = networkConfig.periphery?.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, + vaultAllocator, priceRouter, - slippage_tolerance_bps_uint256, + slippage, + period, + allowedCallsPerPeriod, ]); } @@ -141,32 +137,28 @@ export async function deployAumProvider4626( ]); } -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 "FyWBTCDecoderAndSanitizer": + return {}; + case "PriceRouter": return {}; case "AvnuMiddleware": - if (args.length < 1) { + if (args.length < 4) { throw new Error( - "AvnuMiddleware requires: " + "AvnuMiddleware requires: " ); } return { - slippageToleranceBps: validateSlippageTolerancePercentage(args[0]), + vaultAllocator: args[0], + slippage: parseInt(args[1]), + period: parseInt(args[2]), + allowedCallsPerPeriod: parseInt(args[3]), }; case "AumProvider4626": @@ -196,12 +188,12 @@ 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 " + " Usage: --contract AvnuMiddleware " ); console.log( - " Note: slippage_tolerance_percentage should be between 0-100% (e.g., 2.5 for 2.5%)" + " Note: slippage_bps is in basis points (e.g., 250 for 2.5%), period is in seconds" ); console.log(" - AumProvider4626 "); console.log( @@ -224,6 +216,9 @@ async function main() { case "SimpleDecoderAndSanitizer": deployedAddress = await deploySimpleDecoderAndSanitizer(envNetwork); break; + case "FyWBTCDecoderAndSanitizer": + deployedAddress = await deployFyWBTCDecoderAndSanitizer(envNetwork); + break; case "PriceRouter": deployedAddress = await deployPriceRouter(envNetwork); @@ -232,7 +227,10 @@ async function main() { case "AvnuMiddleware": deployedAddress = await deployAvnuMiddleware( envNetwork, - parsedArgs.slippageToleranceBps as number + parsedArgs.vaultAllocator as string, + parsedArgs.slippage as number, + parsedArgs.period as number, + parsedArgs.allowedCallsPerPeriod as number ); break; diff --git a/scripts/deployEkuboAdapter.ts b/scripts/deployEkuboAdapter.ts new file mode 100644 index 00000000..854bbe40 --- /dev/null +++ b/scripts/deployEkuboAdapter.ts @@ -0,0 +1,228 @@ +import { Account, RpcProvider, CallData } from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveContractDeployment } from "./utils/deployment"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); + +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()); + }); + }); +} + +// ============================================ +// FILL IN THE PARAMETERS BELOW +// ============================================ + +// Vault allocator address that will control this adapter +const VAULT_ALLOCATOR_ADDRESS = + "0x7347602aedf0197492a6d10f7e9d9dda45493e62b26bd540e980617e92b4e38"; + +// Ekubo positions contract address +const EKUBO_POSITIONS_CONTRACT = + "0x02e0af29598b407c8716b17f6d2795eca1b471413fa03fb145a5e33722184067"; + +// Ekubo positions NFT contract address +const EKUBO_POSITIONS_NFT = + "0x07b696af58c967c1b14c9dde0ace001720635a660a8e90c565ea459345318b30"; + +// Ekubo core contract address +const EKUBO_CORE = + "0x00000005dd3D2F4429AF886cD1a3b08289DBcEa99A294197E9eB43b0e0325b4b"; + +// Pool key parameters +const POOL_KEY = { + token0: "0x03Fe2b97C1Fd336E750087D68B9b867997Fd64a2661fF3ca5A7C771641e8e7AC", // Lower address token + token1: "0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68", // Higher address token + fee: "0x0", // Fee tier (e.g., "170141183460469235273462165868118016") + tick_spacing: "100", // Tick spacing (e.g., "1000") + extension: "0x0", // Extension address (usually 0) +}; + +// Bounds settings for the position +// i129 is represented as { mag: u128, sign: bool } where sign=true means negative +const BOUNDS_SETTINGS = { + lower: { + mag: "23025400", // Magnitude of lower tick + sign: false, // + }, + upper: { + mag: "23026200", // Magnitude of upper tick + sign: false, // + }, +}; + +// ============================================ +// END OF PARAMETERS +// ============================================ + +export async function deployEkuboAdapter(envNetwork: string) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.EkuboAdapter; + if (!classHash) { + throw new Error( + `EkuboAdapter class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + // Validate parameters + if (!VAULT_ALLOCATOR_ADDRESS) { + throw new Error("VAULT_ALLOCATOR_ADDRESS is required"); + } + if (!EKUBO_POSITIONS_CONTRACT) { + throw new Error("EKUBO_POSITIONS_CONTRACT is required"); + } + if (!EKUBO_POSITIONS_NFT) { + throw new Error("EKUBO_POSITIONS_NFT is required"); + } + if (!EKUBO_CORE) { + throw new Error("EKUBO_CORE is required"); + } + if (!POOL_KEY.token0 || !POOL_KEY.token1) { + throw new Error("POOL_KEY tokens are required"); + } + if (!POOL_KEY.fee) { + throw new Error("POOL_KEY fee is required"); + } + if (!POOL_KEY.tick_spacing) { + throw new Error("POOL_KEY tick_spacing is required"); + } + if (!BOUNDS_SETTINGS.lower.mag || !BOUNDS_SETTINGS.upper.mag) { + throw new Error("BOUNDS_SETTINGS magnitudes are required"); + } + + try { + console.log(`Deploying EkuboAdapter with constructor params:`); + console.log(` Vault Allocator: ${VAULT_ALLOCATOR_ADDRESS}`); + console.log(` Ekubo Positions Contract: ${EKUBO_POSITIONS_CONTRACT}`); + console.log(` Ekubo Positions NFT: ${EKUBO_POSITIONS_NFT}`); + console.log(` Ekubo Core: ${EKUBO_CORE}`); + console.log(` Pool Key:`); + console.log(` Token0: ${POOL_KEY.token0}`); + console.log(` Token1: ${POOL_KEY.token1}`); + console.log(` Fee: ${POOL_KEY.fee}`); + console.log(` Tick Spacing: ${POOL_KEY.tick_spacing}`); + console.log(` Extension: ${POOL_KEY.extension}`); + console.log(` Bounds Settings:`); + console.log( + ` Lower: ${BOUNDS_SETTINGS.lower.sign ? "-" : ""}${ + BOUNDS_SETTINGS.lower.mag + }` + ); + console.log( + ` Upper: ${BOUNDS_SETTINGS.upper.sign ? "-" : ""}${ + BOUNDS_SETTINGS.upper.mag + }` + ); + + // Construct calldata + // Constructor order: vault_allocator, ekubo_positions_contract, bounds_settings, pool_key, ekubo_positions_nft, ekubo_core + const constructorCalldata = CallData.compile({ + vault_allocator: VAULT_ALLOCATOR_ADDRESS, + ekubo_positions_contract: EKUBO_POSITIONS_CONTRACT, + bounds_settings: { + lower: { + mag: BOUNDS_SETTINGS.lower.mag, + sign: BOUNDS_SETTINGS.lower.sign, + }, + upper: { + mag: BOUNDS_SETTINGS.upper.mag, + sign: BOUNDS_SETTINGS.upper.sign, + }, + }, + pool_key: { + token0: POOL_KEY.token0, + token1: POOL_KEY.token1, + fee: POOL_KEY.fee, + tick_spacing: POOL_KEY.tick_spacing, + extension: POOL_KEY.extension, + }, + ekubo_positions_nft: EKUBO_POSITIONS_NFT, + ekubo_core: EKUBO_CORE, + }); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`\nEkuboAdapter deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying EkuboAdapter:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + console.log("Initializing EkuboAdapter deployment process...\n"); + + console.log("\nDeployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Deployer: ${owner.address}`); + + const confirm = await askQuestion( + "\nDo you want to proceed with deployment? (yes/no): " + ); + if (confirm.toLowerCase() !== "yes") { + console.log("Deployment cancelled."); + rl.close(); + return; + } + + console.log("\nDeploying EkuboAdapter..."); + const ekuboAdapterAddress = await deployEkuboAdapter(envNetwork); + + const saveName = await askQuestion( + "\nEnter a name to save this deployment (leave empty to skip): " + ); + if (saveName) { + saveContractDeployment( + envNetwork, + saveName, + ekuboAdapterAddress, + "deployed" + ); + console.log(`Deployment saved as: ${saveName}`); + } + + console.log("\nDeployment completed successfully!"); + console.log(`EkuboAdapter Address: ${ekuboAdapterAddress}`); + } catch (error) { + console.error("\nDeployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/deployManager.ts b/scripts/deployManager.ts new file mode 100644 index 00000000..d05a7510 --- /dev/null +++ b/scripts/deployManager.ts @@ -0,0 +1,123 @@ +import { Account, RpcProvider } from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveVaultDeployment } from "./utils/deployment"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); + +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()); + }); + }); +} + +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.` + ); + } + + try { + console.log(`Deploying Manager with constructor params:`); + console.log(` Owner: ${owner.address}`); + console.log(` VaultAllocator: ${vaultAllocatorAddress}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: [owner.address, vaultAllocatorAddress], + }); + + 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; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + console.log("Initializing Manager deployment process...\n"); + + const vaultAllocatorAddress = await askQuestion( + "VaultAllocator address (required): " + ); + + if (!vaultAllocatorAddress) { + throw new Error("VaultAllocator address is required"); + } + + const vaultSymbol = await askQuestion( + "Vault symbol (for saving deployment, leave empty to skip): " + ); + + console.log("\nDeployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Owner: ${owner.address}`); + console.log(`VaultAllocator: ${vaultAllocatorAddress}`); + if (vaultSymbol) { + console.log(`Vault Symbol: ${vaultSymbol}`); + } + + console.log("\nDeploying Manager..."); + const managerAddress = await deployManager( + envNetwork, + vaultAllocatorAddress, + vaultSymbol || undefined + ); + + console.log("\nDeployment completed successfully!"); + console.log(`Manager Address: ${managerAddress}`); + } catch (error) { + console.error("\nDeployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/deployVault.ts b/scripts/deployVault.ts index e93318b3..b5a504f3 100644 --- a/scripts/deployVault.ts +++ b/scripts/deployVault.ts @@ -2,7 +2,6 @@ import { Account, byteArray, CairoUint256, - CallData, RpcProvider, validateAndParseAddress, } from "starknet"; @@ -40,13 +39,11 @@ interface DeploymentConfig { 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 owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); const rl = readline.createInterface({ input: process.stdin, @@ -452,28 +449,14 @@ export async function deployManager( ); } - 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, + constructorCalldata: [owner.address, vaultAllocatorAddress], }); console.log(`Manager deployed successfully!`); @@ -577,8 +560,8 @@ async function main() { vaultConfig.reportDelay, vaultConfig.maxDeltaPercentage ); - console.log("\n⏳ Waiting 3 seconds before deploying RedeemRequest..."); - await new Promise(resolve => setTimeout(resolve, 3000)); + console.log("\n⏳ Waiting 10 seconds before deploying RedeemRequest..."); + await new Promise(resolve => setTimeout(resolve, 10000)); console.log("\n📦 Deploying RedeemRequest..."); const redeemRequestAddress = await deployRedeemRequest( @@ -588,14 +571,14 @@ async function main() { vaultConfig.symbol ); - console.log("\n⏳ Waiting 3 seconds before linking RedeemRequest to Vault..."); - await new Promise(resolve => setTimeout(resolve, 3000)); + console.log("\n⏳ Waiting 10 seconds before linking RedeemRequest to Vault..."); + await new Promise(resolve => setTimeout(resolve, 10000)); 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)); + console.log("\n⏳ Waiting 10 seconds before vault allocator operations..."); + await new Promise(resolve => setTimeout(resolve, 10000)); if (isCustodial) { console.log("\n🔗 Attaching existing VaultAllocator to Vault..."); @@ -608,14 +591,14 @@ async function main() { vaultConfig.symbol ); - console.log("\n⏳ Waiting 3 seconds before attaching VaultAllocator to Vault..."); - await new Promise(resolve => setTimeout(resolve, 3000)); + console.log("\n⏳ Waiting 10 seconds before attaching VaultAllocator to Vault..."); + await new Promise(resolve => setTimeout(resolve, 10000)); 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⏳ Waiting 10 seconds before deploying Manager..."); + await new Promise(resolve => setTimeout(resolve, 10000)); console.log("\n📦 Deploying Manager..."); const managerAddress = await deployManager( @@ -624,8 +607,8 @@ async function main() { vaultConfig.symbol ); - console.log("\n⏳ Waiting 3 seconds before setting Manager in VaultAllocator..."); - await new Promise(resolve => setTimeout(resolve, 3000)); + console.log("\n⏳ Waiting 10 seconds before setting Manager in VaultAllocator..."); + await new Promise(resolve => setTimeout(resolve, 10000)); console.log("\n🔗 Setting Manager in VaultAllocator..."); await setManagerInVaultAllocator( diff --git a/scripts/deployVaultAllocator.ts b/scripts/deployVaultAllocator.ts new file mode 100644 index 00000000..4ebf72b4 --- /dev/null +++ b/scripts/deployVaultAllocator.ts @@ -0,0 +1,111 @@ +import { Account, RpcProvider } from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveVaultDeployment } from "./utils/deployment"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); + +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()); + }); + }); +} + +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; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + console.log("🚀 Initializing VaultAllocator deployment process...\n"); + + const vaultSymbol = await askQuestion( + "Vault symbol (for saving deployment, leave empty to skip): " + ); + + console.log("\n📋 Deployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Owner: ${owner.address}`); + if (vaultSymbol) { + console.log(`Vault Symbol: ${vaultSymbol}`); + } + + console.log("\n📦 Deploying VaultAllocator..."); + const vaultAllocatorAddress = await deployVaultAllocator( + envNetwork, + vaultSymbol || undefined + ); + + console.log("\n✅ Deployment completed successfully!"); + console.log(`📍 VaultAllocator Address: ${vaultAllocatorAddress}`); + } catch (error) { + console.error("\n❌ Deployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/exportMerkle.ts b/scripts/exportMerkle.ts new file mode 100644 index 00000000..0fd3aae5 --- /dev/null +++ b/scripts/exportMerkle.ts @@ -0,0 +1,121 @@ +import { readFileSync, writeFileSync } from 'fs'; + +interface LeafAdditionalData { + decoder_and_sanitizer: string; + target: string; + selector: string; + argument_addresses: string[]; + description: string; + leaf_index: number; + leaf_hash: string; +} + +interface Metadata { + vault: string; + vault_allocator: string; + manager: string; + decoder_and_sanitizer: string; + root: string; + tree_capacity: number; + leaf_used: number; +} + +interface MerkleDocument { + metadata: Metadata; + leafs: LeafAdditionalData[]; + tree: string[][]; +} + +function main() { + const args = process.argv.slice(2); + + if (args.length < 2) { + console.error('Usage: tsx exportMerkle.ts '); + process.exit(1); + } + + const [logPath, outPath] = args; + const s = readFileSync(logPath, 'utf-8'); + + // Helper function to extract metadata fields + const get = (key: string): string => { + const pattern = new RegExp(`^${key}:\\s*([0-9]+)\\s*$`, 'm'); + const match = s.match(pattern); + return match ? match[1] : ''; + }; + + // Extract leaf additional data + const leafBlock = s.match(/leaf_additional_data:\s*\[(.*)\]\s*tree:/s); + const items = leafBlock + ? [...leafBlock[1].matchAll(/ManageLeafAdditionalData\s*\{(.*?)\}/gs)] + : []; + + const leafs: LeafAdditionalData[] = items.map((item) => { + const content = item[1]; + + const g = (pattern: string): string => { + const match = content.match(new RegExp(pattern, 's')); + return match ? match[1] : ''; + }; + + const argMatch = content.match(/argument_addresses:\s*\[(.*?)\]/s); + const args = argMatch ? [...argMatch[1].matchAll(/[0-9]+/g)].map(m => m[0]) : []; + + return { + decoder_and_sanitizer: g(String.raw`decoder_and_sanitizer:\s*([0-9]+)`), + target: g(String.raw`target:\s*([0-9]+)`), + selector: g(String.raw`selector:\s*([0-9]+)`), + argument_addresses: args, + description: g(String.raw`description:\s*"([^"]*)"`), + leaf_index: parseInt(g(String.raw`leaf_index:\s*([0-9]+)`) || '0', 10), + leaf_hash: g(String.raw`leaf_hash:\s*([0-9]+)`), + }; + }); + + // Extract tree via bracket counting + const tree: string[][] = []; + const treeStart = s.indexOf('tree:'); + + if (treeStart !== -1) { + const bracketStart = s.indexOf('[', treeStart); + + if (bracketStart !== -1) { + let depth = 0; + let buf = ''; + + for (const ch of s.slice(bracketStart)) { + buf += ch; + if (ch === '[') depth++; + else if (ch === ']') depth--; + if (depth === 0) break; + } + + const rowMatches = [...buf.matchAll(/\[([0-9,\s]+)\]/g)]; + for (const row of rowMatches) { + const numbers = [...row[1].matchAll(/[0-9]+/g)].map(m => m[0]); + tree.push(numbers); + } + } + } + + // Build the output document + const doc: MerkleDocument = { + metadata: { + vault: get('vault'), + vault_allocator: get('vault_allocator'), + manager: get('manager'), + decoder_and_sanitizer: get('decoder_and_sanitizer'), + root: get('root'), + tree_capacity: parseInt(get('tree_capacity') || '0', 10), + leaf_used: parseInt(get('leaf_used') || '0', 10), + }, + leafs, + tree, + }; + + // Write output + writeFileSync(outPath, JSON.stringify(doc, null, 2), 'utf-8'); + console.log(`Wrote ${outPath} (log: ${logPath})`); +} + +main(); diff --git a/scripts/managerConfig.ts b/scripts/managerConfig.ts index 5b8a5b65..d0289a96 100644 --- a/scripts/managerConfig.ts +++ b/scripts/managerConfig.ts @@ -10,13 +10,11 @@ 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 owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); const rl = readline.createInterface({ input: process.stdin, diff --git a/scripts/package.json b/scripts/package.json index c7d3a157..b4d0b8b4 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -14,15 +14,30 @@ "declare:price-router": "tsx declareContract.ts --contract PriceRouter", "declare:simple-decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", "declare:vesu-v2-specific-decoder-sanitizer": "tsx declareContract.ts --contract VesuV2SpecificDecoderAndSanitizer", + "declare:forgeyields-paradex-decoder-sanitizer": "tsx declareContract.ts --contract ForgeyieldsParadexDecoderAndSanitizer", + "declare:fywbtc-decoder-sanitizer": "tsx declareContract.ts --contract FyWBTCDecoderAndSanitizer", + "declare:starkgate-middleware": "tsx declareContract.ts --contract StarkgateMiddleware", + "declare:hyperlane-middleware": "tsx declareContract.ts --contract HyperlaneMiddleware", + "declare:paradex-gigavault-middleware": "tsx declareContract.ts --contract ParadexGigaVaultMiddleware", "declare:aum-provider-4626": "tsx declareContract.ts --contract AumProvider4626", + "declare:ekubo-adapter": "tsx declareContract.ts --contract EkuboAdapter", + "declare:asset-transfer-pod": "tsx declareContract.ts --contract AssetTransferPod", "deploy:contract": "tsx deployContract.ts --contract", "deploy:vault": "tsx deployVault.ts", + "deploy:vault-allocator": "tsx deployVaultAllocator.ts", + "deploy:manager": "tsx deployManager.ts", + "deploy:ekubo-adapter": "tsx deployEkuboAdapter.ts", + "deploy:asset-transfer-pod": "tsx deployAssetTransferPod.ts", "vault:config": "tsx vaultConfig.ts", - "manager:config": "tsx managerConfig.ts" + "manager:config": "tsx managerConfig.ts", + "export:merkle": "tsx exportMerkle.ts", + "set-manager": "tsx setManager.ts" }, "dependencies": { + "@ekubo/starknet-sdk": "^0.0.7", + "decimal.js": "^10.6.0", "dotenv": "^16.4.5", - "starknet": "7.6.4" + "starknet": "9.2.1" }, "devDependencies": { "@types/node": "^22.5.4", diff --git a/scripts/pnpm-lock.yaml b/scripts/pnpm-lock.yaml index 58ba7a08..b93ee408 100644 --- a/scripts/pnpm-lock.yaml +++ b/scripts/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@ekubo/starknet-sdk': + specifier: ^0.0.7 + version: 0.0.7 + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 dotenv: specifier: ^16.4.5 version: 16.6.1 starknet: - specifier: 7.6.4 - version: 7.6.4 + specifier: 9.2.1 + version: 9.2.1(typescript@5.9.2) devDependencies: '@types/node': specifier: ^22.5.4 @@ -27,6 +33,12 @@ importers: packages: + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + + '@ekubo/starknet-sdk@0.0.7': + resolution: {integrity: sha512-YJf+DjijbOpX/lRGl4K56+bQZIHcdqCEuU6WzSFqUn+Po4p20NEHPQtCOhUq3BUTTHxhoWWPGRB9zC0nQEoiAA==} + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -187,29 +199,71 @@ packages: resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + '@noble/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} + '@scure/base@1.2.1': resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@scure/starknet@1.1.0': resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + '@starknet-io/get-starknet-wallet-standard@5.0.0': + resolution: {integrity: sha512-isDNGDlp16W24HE4IuweYXLDRZN0JbsDnazAieeKXE87Mn+jqhsjgTsMxcwWTjX7v906Bjz39FiDjGUddnr36g==} + + '@starknet-io/types-js@0.10.0': + resolution: {integrity: sha512-7ALSydz6pq3YIOpq5a7OkkxqwJciMc9Nlph0OGjhcC3xX0xH30XgizmziLyYVN10oO9+BJk8M9KbJjpzdbtRSw==} + '@starknet-io/types-js@0.7.10': resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} - '@starknet-io/types-js@0.8.4': - resolution: {integrity: sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==} + '@starknet-io/types-js@0.9.2': + resolution: {integrity: sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg==} '@types/node@22.18.0': resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} + '@wallet-standard/base@1.1.0': + resolution: {integrity: sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==} + engines: {node: '>=16'} + + '@wallet-standard/features@1.1.0': + resolution: {integrity: sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==} + engines: {node: '>=16'} + abi-wan-kanabi@2.2.4: resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} hasBin: true + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -236,6 +290,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -257,6 +314,9 @@ packages: engines: {node: '>=4'} hasBin: true + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -283,8 +343,16 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - lossless-json@4.1.1: - resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + lossless-json@4.3.0: + resolution: {integrity: sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==} + + ox@0.4.4: + resolution: {integrity: sha512-oJPEeCDs9iNiPs6J0rTx+Y0KGeCGyCAA3zo94yZhm8G5WpOxrwUtn2Ie/Y8IyARSqqY/j9JTKA3Fc1xs1DvFnw==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} @@ -299,8 +367,8 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - starknet@7.6.4: - resolution: {integrity: sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ==} + starknet@9.2.1: + resolution: {integrity: sha512-bFJY2sMZ9tsLBhPCm719MWjoz+doabXIwPX/xtW56EHwAJMRAS6mICF6H2dCwOQHJmCMKpOSFBwW0SaiHzcioQ==} engines: {node: '>=22'} string-width@4.2.3: @@ -349,6 +417,10 @@ packages: snapshots: + '@adraffy/ens-normalize@1.11.1': {} + + '@ekubo/starknet-sdk@0.0.7': {} + '@esbuild/aix-ppc64@0.25.9': optional: true @@ -431,23 +503,60 @@ snapshots: dependencies: '@noble/hashes': 1.6.0 + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + '@noble/hashes@1.6.0': {} + '@noble/hashes@1.8.0': {} + '@scure/base@1.2.1': {} + '@scure/base@1.2.6': {} + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@scure/starknet@1.1.0': dependencies: '@noble/curves': 1.7.0 '@noble/hashes': 1.6.0 + '@starknet-io/get-starknet-wallet-standard@5.0.0(typescript@5.9.2)': + dependencies: + '@starknet-io/types-js': 0.7.10 + '@wallet-standard/base': 1.1.0 + '@wallet-standard/features': 1.1.0 + ox: 0.4.4(typescript@5.9.2) + transitivePeerDependencies: + - typescript + - zod + + '@starknet-io/types-js@0.10.0': {} + '@starknet-io/types-js@0.7.10': {} - '@starknet-io/types-js@0.8.4': {} + '@starknet-io/types-js@0.9.2': {} '@types/node@22.18.0': dependencies: undici-types: 6.21.0 + '@wallet-standard/base@1.1.0': {} + + '@wallet-standard/features@1.1.0': + dependencies: + '@wallet-standard/base': 1.1.0 + abi-wan-kanabi@2.2.4: dependencies: ansicolors: 0.3.2 @@ -455,6 +564,10 @@ snapshots: fs-extra: 10.1.0 yargs: 17.7.2 + abitype@1.2.3(typescript@5.9.2): + optionalDependencies: + typescript: 5.9.2 + ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -480,6 +593,8 @@ snapshots: color-name@1.1.4: {} + decimal.js@10.6.0: {} + dotenv@16.6.1: {} emoji-regex@8.0.0: {} @@ -517,6 +632,8 @@ snapshots: esprima@4.0.1: {} + eventemitter3@5.0.1: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -542,7 +659,21 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - lossless-json@4.1.1: {} + lossless-json@4.3.0: {} + + ox@0.4.4(typescript@5.9.2): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.2) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - zod pako@2.1.0: {} @@ -554,18 +685,22 @@ snapshots: resolve-pkg-maps@1.0.0: {} - starknet@7.6.4: + starknet@9.2.1(typescript@5.9.2): 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' + '@starknet-io/get-starknet-wallet-standard': 5.0.0(typescript@5.9.2) + '@starknet-io/starknet-types-010': '@starknet-io/types-js@0.10.0' + '@starknet-io/starknet-types-09': '@starknet-io/types-js@0.9.2' abi-wan-kanabi: 2.2.4 - lossless-json: 4.1.1 + lossless-json: 4.3.0 pako: 2.1.0 ts-mixer: 6.0.4 + transitivePeerDependencies: + - typescript + - zod string-width@4.2.3: dependencies: diff --git a/scripts/setManager.ts b/scripts/setManager.ts new file mode 100644 index 00000000..21ee5bc2 --- /dev/null +++ b/scripts/setManager.ts @@ -0,0 +1,69 @@ +import { Account, RpcProvider, Contract } from "starknet"; +import dotenv from "dotenv"; +import { getNetworkEnv } from "./utils"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); + +const VAULT_ALLOCATOR_ABI = [ + { + type: "function", + name: "set_manager", + inputs: [ + { + name: "manager", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, +]; + +async function setManager( + vaultAllocatorAddress: string, + newManagerAddress: string +) { + const contract = new Contract( + VAULT_ALLOCATOR_ABI, + vaultAllocatorAddress, + owner + ); + + console.log(`Setting manager on VaultAllocator: ${vaultAllocatorAddress}`); + console.log(`New manager address: ${newManagerAddress}`); + + const tx = await contract.set_manager(newManagerAddress); + console.log(`Transaction hash: ${tx.transaction_hash}`); + + await provider.waitForTransaction(tx.transaction_hash); + console.log("Manager set successfully!"); + + return tx.transaction_hash; +} + +async function main() { + const vaultAllocatorAddress = process.argv[2]; + const newManagerAddress = process.argv[3]; + + if (!vaultAllocatorAddress || !newManagerAddress) { + console.error( + "Usage: npx tsx setManager.ts " + ); + process.exit(1); + } + + const envNetwork = await getNetworkEnv(provider); + console.log(`Network: ${envNetwork}`); + console.log(`Caller: ${owner.address}`); + + await setManager(vaultAllocatorAddress, newManagerAddress); +} + +main().catch(console.error); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 00000000..4a0e3409 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": ".", + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/utils.ts b/scripts/utils.ts index d20645f8..9a27de9f 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -14,13 +14,17 @@ export async function appendToEnv(name: string, address: string) { ); } +const chainIdToNetwork = { + [constants.StarknetChainId.SN_SEPOLIA]: "sepolia", + [constants.StarknetChainId.SN_MAIN]: "mainnet", + ["0x505249564154455f534e5f50415241434c4541525f4d41494e4e4554"]: + "paradex_prod", + ["0x505249564154455f534e5f504f54435f5345504f4c4941"]: "paradex_testnet", +}; 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"; + if (chainIdToNetwork[chainIdFromRpc]) { + return chainIdToNetwork[chainIdFromRpc]; } throw new Error(`Unsupported network: ${chainIdFromRpc}`); } diff --git a/scripts/utils/deployment.ts b/scripts/utils/deployment.ts index d9caacb5..d7bc03e6 100644 --- a/scripts/utils/deployment.ts +++ b/scripts/utils/deployment.ts @@ -7,14 +7,15 @@ interface DeploymentInfo { timestamp: number; } +interface VaultDeployments { + vault?: DeploymentInfo; + redeemRequest?: DeploymentInfo; + vaultAllocator?: DeploymentInfo; + manager?: DeploymentInfo; +} + interface NetworkDeployments { - [contractName: string]: DeploymentInfo; - [symbol: string]: { - vault?: DeploymentInfo; - redeemRequest?: DeploymentInfo; - vaultAllocator?: DeploymentInfo; - manager?: DeploymentInfo; - }; + [key: string]: DeploymentInfo | VaultDeployments; } interface DeploymentsJson { diff --git a/scripts/vaultConfig.ts b/scripts/vaultConfig.ts index 82eea705..92b7e0a4 100644 --- a/scripts/vaultConfig.ts +++ b/scripts/vaultConfig.ts @@ -13,13 +13,11 @@ 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 owner = new Account({ + provider: provider, + address: process.env.ACCOUNT_ADDRESS as string, + signer: process.env.ACCOUNT_PK as string, +}); const rl = readline.createInterface({ input: process.stdin, diff --git a/sdk/README.md b/sdk/README.md index f90f19ad..9b51e1dc 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -107,29 +107,80 @@ console.log("Total pending assets:", pendingRedemptions.totalPendingAssets); ### VaultCuratorSDK -#### Calldata Generation +The `VaultCuratorSDK` enables curators to execute DeFi operations through the vault allocator using Merkle-verified calldata. It supports multiple protocol integrations. -- `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 +#### Initialization -#### View Methods +```typescript +import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; + +// Load from a vault config JSON file +const sdk = VaultCuratorSDK.fromFile('./vault-config.json'); + +// Or initialize with config object +const sdk = new VaultCuratorSDK(vaultConfig); +``` + +#### Core Methods + +- `buildCall(operations)` - Build a multicall from multiple `MerkleOperation` objects +- `bringLiquidity(params)` - Move liquidity from buffer to allocator +- `approve(params)` - Approve tokens for a spender + +#### Integrations + +##### ERC4626 (Nested Vault Operations) + +Interact with other ERC-4626 vaults: + +- `deposit(params)` - Deposit assets into a vault +- `mint(params)` - Mint shares from a vault +- `withdraw(params)` - Withdraw assets from a vault +- `redeem(params)` - Redeem shares for assets +- `requestRedeem(params)` - Request async redemption (epoched vaults) +- `claimRedeem(params)` - Claim completed async redemption + +##### AVNU (DEX Aggregator) + +Execute token swaps via AVNU: + +- `multiRouteSwap(params)` - Execute multi-hop token swaps with optimal routing + +##### Vesu V2 (Lending Protocol) + +Manage lending positions on Vesu V2: + +- `modifyPositionV2(params)` - Supply collateral, borrow, repay, or withdraw + +##### Ekubo (DEX Liquidity) + +Provide liquidity on Ekubo DEX: + +- `ekuboDepositLiquidity(params)` - Deposit liquidity to a pool +- `ekuboWithdrawLiquidity(params)` - Withdraw liquidity from a pool +- `ekuboCollectFees(params)` - Collect accumulated trading fees +- `ekuboHarvest(params)` - Harvest farming rewards -- `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 +##### Starkgate (L1 Bridge) + +Bridge tokens between Starknet and Ethereum: + +- `bridgeTokenStarkgate(params)` - Initiate token withdrawal to L1 +- `claimTokenStarkgate(params)` - Claim tokens bridged back from L1 + +##### Hyperlane Middleware (Cross-Chain Bridge) + +Bridge tokens across chains via Hyperlane middleware: + +- `bridgeTokenHyperlaneMiddleware(params)` - Bridge tokens to another chain +- `claimTokenHyperlaneMiddleware(params)` - Claim bridged tokens + +##### CCTP Middleware (Circle Cross-Chain Transfer) + +Bridge USDC via Circle's CCTP middleware: + +- `bridgeTokenCctpMiddleware(params)` - Bridge USDC to another chain +- `claimTokenCctpMiddleware(params)` - Claim bridged USDC ## Types @@ -241,44 +292,60 @@ const redeemTx = await account.execute([{ console.log("Redeem request tx:", redeemTx.transaction_hash); ``` -### Curator Operations +### Curator Operations (Allocator SDK) ```typescript import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; +import { Account, RpcProvider } from 'starknet'; -const curatorSDK = new VaultCuratorSDK(vaultConfig, provider); +// Load SDK from vault config file +const sdk = VaultCuratorSDK.fromFile('./vault-config.json'); -// 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; -} +const provider = new RpcProvider({ nodeUrl: "your-node-url" }); +const curatorAccount = new Account(provider, "0x...", "your-private-key"); + +// Example 1: Swap tokens via AVNU +const swapOp = sdk.multiRouteSwap({ + target: "0x...", // AVNU middleware address + sell_token_address: "0x...", // STRK + sell_token_amount: "1000000000000000000", // 1 STRK + buy_token_address: "0x...", // USDC + buy_token_amount: "500000", // Expected USDC + buy_token_min_amount: "490000", // Min USDC (slippage) + beneficiary: "0x...", // Vault allocator + integrator_fee_amount_bps: 0, + integrator_fee_recipient: "0x0", + routes: [/* route data from AVNU API */] +}); -// 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 -}]); +// Example 2: Supply collateral to Vesu V2 +const vesuOp = sdk.modifyPositionV2({ + target: "0x...", // Vesu pool + collateral_asset: "0x...", // USDC + debt_asset: "0x...", // ETH + user: "0x...", // Vault allocator + collateral: { denomination: "Native", value: { abs: "1000000", is_negative: false } }, + debt: { denomination: "Native", value: { abs: "0", is_negative: false } } +}); + +// Example 3: Provide liquidity on Ekubo +const ekuboOp = sdk.ekuboDepositLiquidity({ + target: "0x...", // Ekubo adapter + amount0: "1000000", + amount1: "1000000" +}); + +// Build and execute a multicall with multiple operations +const approveOp = sdk.approve({ + target: "0x...", // Token to approve + spender: "0x...", // Protocol contract + amount: "1000000000" +}); + +const call = sdk.buildCall([approveOp, swapOp]); -console.log("Report tx:", reportTx.transaction_hash); +const tx = await curatorAccount.execute(call); +console.log("Transaction hash:", tx.transaction_hash); ``` ## License diff --git a/sdk/examples/test.json b/sdk/examples/test.json new file mode 100644 index 00000000..1cf55947 --- /dev/null +++ b/sdk/examples/test.json @@ -0,0 +1,665 @@ +{ + "metadata": { + "vault": "2377346306932442647668715307155893939410637060064215905807098748014852339502", + "vault_allocator": "3148260697098218922501559176188655100084124891713026095862682167186975521235", + "manager": "", + "decoder_and_sanitizer": "", + "root": "2449105167902902548126568532058667994709448514093459577729455329561496838080", + "tree_capacity": 64, + "leaf_used": 44 + }, + "leafs": [ + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1442471627432665843583957153937277124821302887621015682060980008275741980155", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2377346306932442647668715307155893939410637060064215905807098748014852339502" + ], + "description": "Approve tstV to spend USDC", + "leaf_index": 0, + "leaf_hash": "518828104195178410952792894421209471164609782577348961146679119635134742479" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2377346306932442647668715307155893939410637060064215905807098748014852339502", + "selector": "795107580278555665286724985164326599277536645621139781006281074977070250280", + "argument_addresses": [], + "description": "Bring liquidity tstV", + "leaf_index": 1, + "leaf_hash": "2695270051820032957766625000255559477465892957763476362888702415444815821528" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "1326796927197022071246993880086420967181713746138493709882850328569146018479" + ], + "description": "Approve pool contract_2eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf to spend WBTC", + "leaf_index": 2, + "leaf_hash": "2454940064123680288221277498461215256217976392767714571391800321946862189755" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1326796927197022071246993880086420967181713746138493709882850328569146018479", + "selector": "1631880094539101464511272129576525542256330665932643760479341072373928343757", + "argument_addresses": [ + "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Modify position extension_pid_2eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf with collateral WBTC and debt USDC", + "leaf_index": 3, + "leaf_hash": "761325788416913708703651304325703294726779195466978283225684570453914544043" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "154717502686997779505242937237748798500912348117963555524611254740330341259", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "1326796927197022071246993880086420967181713746138493709882850328569146018479" + ], + "description": "Approve pool contract_2eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf to spend wstETH", + "leaf_index": 4, + "leaf_hash": "2799304616617757009657841335251531721838064800414302658544739642506813060153" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1326796927197022071246993880086420967181713746138493709882850328569146018479", + "selector": "1631880094539101464511272129576525542256330665932643760479341072373928343757", + "argument_addresses": [ + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Modify position extension_pid_2eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf with collateral wstETH and debt USDC", + "leaf_index": 5, + "leaf_hash": "1515026465543805576017828925395050703357904139012616030240008623148569888016" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1326796927197022071246993880086420967181713746138493709882850328569146018479", + "selector": "1631880094539101464511272129576525542256330665932643760479341072373928343757", + "argument_addresses": [ + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2967174050445828070862061291903957281356339325911846264948421066253307482040", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Modify position extension_pid_2eef0c13b10b487ea5916b54c0a7f98ec43fb3048f60fdeedaf5b08f6f88aaf with collateral wstETH and debt USDT", + "leaf_index": 6, + "leaf_hash": "2774656196291001112692006416705149953654721944240310367064990533985733938923" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2273985559333219724429290159602994127325561082984750994597522992026660496918" + ], + "description": "Approve \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyETH to spend ETH", + "leaf_index": 7, + "leaf_hash": "3307100662396130633284253195111263217175522442887298289196239098207405352713" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2273985559333219724429290159602994127325561082984750994597522992026660496918", + "selector": "352040181584456735608515580760888541466059565068553383579463728554843487745", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Deposit ETH for \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyETH", + "leaf_index": 8, + "leaf_hash": "1062645762997359547481712721339730846522942446846670988614562532851352237417" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2273985559333219724429290159602994127325561082984750994597522992026660496918", + "selector": "602617684354587743731238934093348436560137034424203693318834094005509508215", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Withdraw ETH from \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyETH", + "leaf_index": 9, + "leaf_hash": "1896767212582418394203269244727157703448483760597946761381431816779169587357" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2273985559333219724429290159602994127325561082984750994597522992026660496918", + "selector": "1329909728320632088402217562277154056711815095720684343816173432540100887380", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Mint \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyETH from ETH", + "leaf_index": 10, + "leaf_hash": "1666226120252202043644522038213110051293096173166838998949865474688358219722" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2273985559333219724429290159602994127325561082984750994597522992026660496918", + "selector": "1326975239452520649139862114866922847703907595552193666703522601892858398217", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Redeem \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyETH for ETH", + "leaf_index": 11, + "leaf_hash": "517964771423563721217795380964016783792421453223806413066396891619861477035" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1442471627432665843583957153937277124821302887621015682060980008275741980155", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3614629205322087119066064540472892217795595114760929205615786401399069436865" + ], + "description": "Approve \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC to spend USDC", + "leaf_index": 12, + "leaf_hash": "3285850406826300377034966597008743819886375920833259820070671794785877208772" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3614629205322087119066064540472892217795595114760929205615786401399069436865", + "selector": "352040181584456735608515580760888541466059565068553383579463728554843487745", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Deposit USDC for \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC", + "leaf_index": 13, + "leaf_hash": "1095179039761254706430668891962961679302548653252503977802473659426625441341" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3614629205322087119066064540472892217795595114760929205615786401399069436865", + "selector": "1329909728320632088402217562277154056711815095720684343816173432540100887380", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Mint \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC from \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC", + "leaf_index": 14, + "leaf_hash": "1301103023914737780628556345567606803947118956465806282101630500562959400210" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3614629205322087119066064540472892217795595114760929205615786401399069436865", + "selector": "1578242289853123651327795300953725283871618938771607228005655004011474055529", + "argument_addresses": [ + "3148260697098218922501559176188655100084124891713026095862682167186975521235", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Request Redeem \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC", + "leaf_index": 15, + "leaf_hash": "3059433273154753209653902933060597804586753494252697201834965624346836681909" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3614629205322087119066064540472892217795595114760929205615786401399069436865", + "selector": "363732230706164684590129844719591982178196546761740023886679048585267052907", + "argument_addresses": [], + "description": "Claim Redeem \\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0fyUSDC", + "leaf_index": 16, + "leaf_hash": "2461138255657939341346707245554042690180402855859227108719748299259111482085" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2516568162210255095453483626839089014569257854319119258892841327983140950402" + ], + "description": "Approve ekubo_adapter to spend WBTC", + "leaf_index": 17, + "leaf_hash": "446115384351843955137410504284864718553108358189840980523079351245690841975" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2522838177878422711967992029571128884451814651829189911296693586560466229864", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2516568162210255095453483626839089014569257854319119258892841327983140950402" + ], + "description": "Approve ekubo_adapter to spend SolvBTC", + "leaf_index": 18, + "leaf_hash": "3066095266583155198764208180639102981540267320396676094632394193754960197482" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "1485162454000453106748438092882350352049376254989977238717084794132152066047", + "argument_addresses": [], + "description": "Deposit liquidity to Ekubofor WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 19, + "leaf_hash": "3243920330006219195317888393421363286180434284657752746446894215236980901352" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "322637753074552370500544931377150993467524337001753746958704872129235461672", + "argument_addresses": [], + "description": "Withdraw liquidity from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 20, + "leaf_hash": "615103199740132777414887668453855798976181414285602061018627649798475123099" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "38427523166402100478120264784512528971576573049949957236189387122891476727", + "argument_addresses": [], + "description": "Collect fees from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 21, + "leaf_hash": "382619824677425646912591458202195060860822149867190870041329505099301683971" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2516568162210255095453483626839089014569257854319119258892841327983140950402", + "selector": "1154545024571392208615796453779759631244452775454593974158722847429584475265", + "argument_addresses": [], + "description": "Harvest rewards from Ekubo for WBTC and SolvBTC via ekubo_adapter", + "leaf_index": 22, + "leaf_hash": "925565738326550449939901431677377137452803466613353711508706775116431961535" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3357347207369430956573753970315372111359878978740136719808196559187186094847" + ], + "description": "Approve avnu_router to spend STRK", + "leaf_index": 23, + "leaf_hash": "378526479750761202418480421886104470154757233811733959224561690678642997358" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3357347207369430956573753970315372111359878978740136719808196559187186094847", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Multi route swap STRK for USDC", + "leaf_index": 24, + "leaf_hash": "626185466379731885348374317754158141148717903024803678462774817982422299380" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3357347207369430956573753970315372111359878978740136719808196559187186094847" + ], + "description": "Approve avnu_router to spend USDC", + "leaf_index": 25, + "leaf_hash": "1341340830611869960360288108313591030768237279070496193309036958130619514348" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3357347207369430956573753970315372111359878978740136719808196559187186094847", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Multi route swap USDC for STRK", + "leaf_index": 26, + "leaf_hash": "654040167586606044512208784350374073806323236455307530680335394541149389053" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3357347207369430956573753970315372111359878978740136719808196559187186094847" + ], + "description": "Approve avnu_router to spend ETH", + "leaf_index": 27, + "leaf_hash": "1170239322038969565407729371550083585960046162306144254964118176471442475738" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3357347207369430956573753970315372111359878978740136719808196559187186094847", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Multi route swap ETH for USDC", + "leaf_index": 28, + "leaf_hash": "1797909002091718114621888238781018981557265373132397209598643222270965557394" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "3357347207369430956573753970315372111359878978740136719808196559187186094847", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "Multi route swap USDC for ETH", + "leaf_index": 29, + "leaf_hash": "898633134767829854752581683909633509842460520330556455762839143893051476664" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743299" + ], + "description": "Approve starkgate_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4203 to spend USDC", + "leaf_index": 30, + "leaf_hash": "1885327925922112408780198947672064344009545013515703954967942556041179240320" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2481085077367507779430085564211470162232307088275067678916369282054874743299", + "selector": "405852601487139132244494309743039711091605094719341446212637486410648343561", + "argument_addresses": [ + "2624271632322125921217374734393920890821192138210577916078337694621182820758", + "917551056842671309452305380979543736893630245704", + "657322120784522198527611271132108531893007429161", + "2368576823837625528275935341135881659748932889268308403712618244410713532584" + ], + "description": "Starkgate: bridge USDC for USDC to recipient 732357e321bf7a02cbb690fc2a629161d7722e29", + "leaf_index": 31, + "leaf_hash": "737959175909541820247822671089647722018751710445581864332086982715483963297" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743300" + ], + "description": "Approve hyperlane_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4204 to spend USDC", + "leaf_index": 32, + "leaf_hash": "2489668646038966577416249258638545218880371597810286536118161928661173892344" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743300" + ], + "description": "Approve hyperlane_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4204 to spend STRK", + "leaf_index": 33, + "leaf_hash": "2981316954386958759669390311866949156148246037223827275875111834863255469762" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2481085077367507779430085564211470162232307088275067678916369282054874743300", + "selector": "1801422457688512760947010799952476149039729159647432133530729819264776357977", + "argument_addresses": [ + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "1", + "44858727236356512580505469151245119017", + "1931696099" + ], + "description": "Hyperlane: bridge USDC for USDC on domain 1 to recipient 732357e321bf7a02cbb690fc2a629161d7722e29", + "leaf_index": 34, + "leaf_hash": "1319817641540438400859680811424754782740668317110613853855517114831722572571" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1442471627432665843583957153937277124821302887621015682060980008275741980155", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743301" + ], + "description": "Approve cctp_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4205 to spend USDC", + "leaf_index": 35, + "leaf_hash": "2702570063897428457985758704692980368590401572093282202629990584084727273736" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2481085077367507779430085564211470162232307088275067678916369282054874743301", + "selector": "880194175714117648791879152295801635362900716958922299684101959561306433474", + "argument_addresses": [ + "0", + "44858727236356512580505469151245119017", + "1931696099", + "1442471627432665843583957153937277124821302887621015682060980008275741980155", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "0", + "0" + ], + "description": "CCTP: burn USDC for USDC on domain 0 to recipient 732357e321bf7a02cbb690fc2a629161d7722e29", + "leaf_index": 36, + "leaf_hash": "1065054673980661517813127743712043752383405867394055655178987095534761851839" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2481085077367507779430085564211470162232307088275067678916369282054874743302", + "selector": "323941825463020211252781080720490861905751490209587316351695433661350998802", + "argument_addresses": [], + "description": "DefiSpring: claim STRK from 57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4206", + "leaf_index": 37, + "leaf_hash": "2707730999352301092057195979795629799965222749735432131560249362372403323869" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2987327108307389660628511300677478412428831578776543507146742552736034022137" + ], + "description": "Approve lz_oft to spend WBTC", + "leaf_index": 38, + "leaf_hash": "2221516126202809034762354826626511340392105516191222307367272621824705265544" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2987327108307389660628511300677478412428831578776543507146742552736034022137" + ], + "description": "Approve lz_oft to spend STRK", + "leaf_index": 39, + "leaf_hash": "603722515321815994611176743052188480703694960246469542778308478339950398717" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2987327108307389660628511300677478412428831578776543507146742552736034022137", + "selector": "1610865518453967559455951560376016560028512058565201727361936752947915253004", + "argument_addresses": [ + "30101", + "44858727236356512580505469151245119017", + "1931696099", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "LayerZero: send WBTC to eid 7595 to 732357e321bf7a02cbb690fc2a629161d7722e29", + "leaf_index": 40, + "leaf_hash": "3244208650768946998385518227660849733246901215144855982816937244916122835373" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743303" + ], + "description": "Approve lz_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4207 to spend WBTC", + "leaf_index": 41, + "leaf_hash": "2447646972223733209407778608886088229744799035749877932447140991266622666705" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2009894490435840142178314390393166646092438090257831307886760648929397478285", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2481085077367507779430085564211470162232307088275067678916369282054874743303" + ], + "description": "Approve lz_middleware_57c3e904b23095e905e00e61f49ef46007f64f8b0ceb3c1de729d936f4c4207 to spend STRK", + "leaf_index": 42, + "leaf_hash": "3357167014342113256787158639070502340983876615751255123737934255551636383447" + }, + { + "decoder_and_sanitizer": "1335318297083250476949626155416953996670985367185906286177684408403395913944", + "target": "2481085077367507779430085564211470162232307088275067678916369282054874743303", + "selector": "1610865518453967559455951560376016560028512058565201727361936752947915253004", + "argument_addresses": [ + "2987327108307389660628511300677478412428831578776543507146742552736034022137", + "1806018566677800621296032626439935115720767031724401394291089442012247156652", + "2368576823837625528275935341135881659748932889268308403712618244410713532584", + "30101", + "44858727236356512580505469151245119017", + "1931696099", + "3148260697098218922501559176188655100084124891713026095862682167186975521235" + ], + "description": "LayerZero: send via middleware WBTC for USDC to eid 7595 to 732357e321bf7a02cbb690fc2a629161d7722e29", + "leaf_index": 43, + "leaf_hash": "1715353930178487889182131430522800068438688475751960950056264074023710885271" + } + ], + "tree": [ + [ + "518828104195178410952792894421209471164609782577348961146679119635134742479", + "2695270051820032957766625000255559477465892957763476362888702415444815821528", + "2454940064123680288221277498461215256217976392767714571391800321946862189755", + "761325788416913708703651304325703294726779195466978283225684570453914544043", + "2799304616617757009657841335251531721838064800414302658544739642506813060153", + "1515026465543805576017828925395050703357904139012616030240008623148569888016", + "2774656196291001112692006416705149953654721944240310367064990533985733938923", + "3307100662396130633284253195111263217175522442887298289196239098207405352713", + "1062645762997359547481712721339730846522942446846670988614562532851352237417", + "1896767212582418394203269244727157703448483760597946761381431816779169587357", + "1666226120252202043644522038213110051293096173166838998949865474688358219722", + "517964771423563721217795380964016783792421453223806413066396891619861477035", + "3285850406826300377034966597008743819886375920833259820070671794785877208772", + "1095179039761254706430668891962961679302548653252503977802473659426625441341", + "1301103023914737780628556345567606803947118956465806282101630500562959400210", + "3059433273154753209653902933060597804586753494252697201834965624346836681909", + "2461138255657939341346707245554042690180402855859227108719748299259111482085", + "446115384351843955137410504284864718553108358189840980523079351245690841975", + "3066095266583155198764208180639102981540267320396676094632394193754960197482", + "3243920330006219195317888393421363286180434284657752746446894215236980901352", + "615103199740132777414887668453855798976181414285602061018627649798475123099", + "382619824677425646912591458202195060860822149867190870041329505099301683971", + "925565738326550449939901431677377137452803466613353711508706775116431961535", + "378526479750761202418480421886104470154757233811733959224561690678642997358", + "626185466379731885348374317754158141148717903024803678462774817982422299380", + "1341340830611869960360288108313591030768237279070496193309036958130619514348", + "654040167586606044512208784350374073806323236455307530680335394541149389053", + "1170239322038969565407729371550083585960046162306144254964118176471442475738", + "1797909002091718114621888238781018981557265373132397209598643222270965557394", + "898633134767829854752581683909633509842460520330556455762839143893051476664", + "1885327925922112408780198947672064344009545013515703954967942556041179240320", + "737959175909541820247822671089647722018751710445581864332086982715483963297", + "2489668646038966577416249258638545218880371597810286536118161928661173892344", + "2981316954386958759669390311866949156148246037223827275875111834863255469762", + "1319817641540438400859680811424754782740668317110613853855517114831722572571", + "2702570063897428457985758704692980368590401572093282202629990584084727273736", + "1065054673980661517813127743712043752383405867394055655178987095534761851839", + "2707730999352301092057195979795629799965222749735432131560249362372403323869", + "2221516126202809034762354826626511340392105516191222307367272621824705265544", + "603722515321815994611176743052188480703694960246469542778308478339950398717", + "3244208650768946998385518227660849733246901215144855982816937244916122835373", + "2447646972223733209407778608886088229744799035749877932447140991266622666705", + "3357167014342113256787158639070502340983876615751255123737934255551636383447", + "1715353930178487889182131430522800068438688475751960950056264074023710885271", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691" + ], + [ + "3297402687646137543504143355568489367717548677557061003464612910876687071977", + "3339112299479690701792116905348801534274458474989624202218941849938318394244", + "809273581131172767695718776119596105492272242910549037315948184338692871291", + "2643727380946111310614891994493660317195550315181904998764542085337205835629", + "383706475308072930206906104417280710490784021340287107377601474303147569353", + "2354005658632590001673143365921564413965007954805179880526542887228308113060", + "2556279644695027672694288925325490965897772333286226792087046823459885810833", + "1750064018400760646061648514996480074547774388943683172100845793376911429551", + "1376125971624493353239281971368913605504631102126197533938563469221423381984", + "1465854859857399538357509881475721579107806416203028341113736355360349121266", + "473117348996171172456724467790108421053315058141020141810683575960966696424", + "1668493714376490738969794688815702573689135569574206503866043090020963091648", + "2398404933266189684135647566287339075196708638148181143677216791144440962800", + "1164350820273188670325413910001846532418146488490113544759189425143847446093", + "709198519674192404534286902115065760336727595269735110835008101704769014375", + "3253960462979183998237737515142975255823287508618512812043202081962947789036", + "2165180009140329144205726171878175555190112319448959035249982965505984729925", + "2960037260064481899183694255860098816483771070084014682939145012959256653353", + "994179906532043713104116777744435646614225342809153280397462282833386562430", + "999539171002659424170242799220878808255790127256377123059643317634156470558", + "2413672633473222958585789689932557113773094671231321804182183225730513068352", + "2509312368652003243002829553343142346655249868258429706274851950464537210751", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341" + ], + [ + "1729957273557224845165375322635893892654794618280038055219547092725326896013", + "2888942770320891934074354473836754738769153156753779255949656579173851797527", + "850277492731383243045677770686871262353647295179074013849241239017493773721", + "492430724492378043399385450471523835914752682004468703415501029743092931511", + "205902830011156883588500252420159445134551229474047193335167334277339881609", + "2817053195674293611913449036210034506845669134980133911315016129509115513860", + "522205819036194789409030526098980850764161752737240786058601560673462503733", + "1156331213721788358116431610009040390636946231864915274957403813887140530122", + "3051784117898286082135241593457618073944530037281827238076881405126988273026", + "263101304123415444221940365941650106938387481157255321742922498496921700005", + "2301171701386328167533001537326179364036671565116503573372487544800456966202", + "2002344887409916074004609464034933685885954831475272208035150614502178175918", + "2002344887409916074004609464034933685885954831475272208035150614502178175918", + "2002344887409916074004609464034933685885954831475272208035150614502178175918", + "2002344887409916074004609464034933685885954831475272208035150614502178175918", + "2002344887409916074004609464034933685885954831475272208035150614502178175918" + ], + [ + "1662316215431576922875716804346115046642613153570602646238719959764602087041", + "3170227743734646678728136621241478439240852450754520485040884228637294172110", + "186751120661033902331787919429617079753595455163537433038802509380041131550", + "2849069006410805395151508458409417280006407691438174649163875650582722742116", + "1976191944766399781725496164945331946855336181186535488870397528231205568448", + "1197807765128040184941145102342093723885235844270569548034041929080134508776", + "920384207413583467608949238390008583417600091247180803716715374098965866115", + "920384207413583467608949238390008583417600091247180803716715374098965866115" + ], + [ + "3538206225661991502313871020329516980995433594971138925039156901482436979887", + "2802702182750009472983230767361070167233471367601428565964037127598703996762", + "98684379619226114615122393744881075317626211777480038639421164115557057724", + "2867843415308758512403329583581929815822189615216122810650544189758483568126" + ], + [ + "14451928781640413659852846656467675577772087223481327503149931377504989594", + "1948648168262995154744568931856325591520643363431754373645165964922715453843" + ], + [ + "2449105167902902548126568532058667994709448514093459577729455329561496838080" + ] + ] +} \ No newline at end of file diff --git a/sdk/examples/test_avnu_swaps.ts b/sdk/examples/test_avnu_swaps.ts new file mode 100644 index 00000000..675c29ca --- /dev/null +++ b/sdk/examples/test_avnu_swaps.ts @@ -0,0 +1,302 @@ +/** + * Example: AVNU Swap Operations with VaultCuratorSDK + * Demonstrates multi-route swaps with the AVNU aggregator + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + ETH: "2087021424722619777119509474943472645767659996348769578120564519014510906823", + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + STRK: "2009894490435840142178314390393166646092438090257831307886760648929397478285", +}; + +const AVNU_ROUTER = + "3357347207369430956573753970315372111359878978740136719808196559187186094847"; + +const VAULT_ALLOCATOR = + "3148260697098218922501559176188655100084124891713026095862682167186975521235"; + +async function testAvnuSwaps() { + console.log("=== AVNU Swap Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const ethAmount = "1000000000000000000"; // 1 ETH (18 decimals) + const strkAmount = "100000000000000000000"; // 100 STRK (18 decimals) + const usdcAmount = "1000000"; // 1 USDC (6 decimals) + + // ============================================ + // 1. Approve ETH for AVNU router + // ============================================ + console.log("1. Approve ETH for AVNU router"); + + const approveETH = sdk.approve({ + target: TOKENS.ETH, + spender: AVNU_ROUTER, + amount: ethAmount, + }); + console.log(" Approve operation:", { target: approveETH.target }); + + // ============================================ + // 2. Swap ETH -> USDC (single route) + // ============================================ + console.log("\n2. Swap ETH -> USDC"); + + const ethToUsdcSwap = sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.ETH, + sell_token_amount: ethAmount, + buy_token_address: TOKENS.USDC, + buy_token_amount: "3000000000", // Expected ~3000 USDC + buy_token_min_amount: "2900000000", // Min 2900 USDC (slippage protection) + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x123", // Actual DEX address from AVNU API + percent: "100", // 100% through this route + additional_swap_params: [], + }, + ], + }); + console.log(" ETH -> USDC swap:", { + target: ethToUsdcSwap.target, + selector: ethToUsdcSwap.selector, + }); + + const ethToUsdcCall = sdk.buildCall([approveETH, ethToUsdcSwap]); + console.log(" Combined call built with", (ethToUsdcCall.calldata as string[]).length, "elements"); + + // ============================================ + // 3. Swap USDC -> ETH (reverse) + // ============================================ + console.log("\n3. Swap USDC -> ETH"); + + const approveUSDC = sdk.approve({ + target: TOKENS.USDC, + spender: AVNU_ROUTER, + amount: usdcAmount, + }); + + const usdcToEthSwap = sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.USDC, + sell_token_amount: usdcAmount, + buy_token_address: TOKENS.ETH, + buy_token_amount: "300000000000000", // Expected ~0.0003 ETH + buy_token_min_amount: "290000000000000", // Min with slippage + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.USDC, + buy_token: TOKENS.ETH, + exchange_address: "0x123", + percent: "100", + additional_swap_params: [], + }, + ], + }); + console.log(" USDC -> ETH swap:", { + target: usdcToEthSwap.target, + }); + + const usdcToEthCall = sdk.buildCall([approveUSDC, usdcToEthSwap]); + console.log(" Reverse swap call built"); + + // ============================================ + // 4. Swap STRK -> USDC + // ============================================ + console.log("\n4. Swap STRK -> USDC"); + + const approveSTRK = sdk.approve({ + target: TOKENS.STRK, + spender: AVNU_ROUTER, + amount: strkAmount, + }); + + const strkToUsdcSwap = sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.STRK, + sell_token_amount: strkAmount, + buy_token_address: TOKENS.USDC, + buy_token_amount: "50000000", // Expected ~50 USDC + buy_token_min_amount: "48000000", // Min 48 USDC + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.STRK, + buy_token: TOKENS.USDC, + exchange_address: "0x456", + percent: "100", + additional_swap_params: [], + }, + ], + }); + console.log(" STRK -> USDC swap:", { + target: strkToUsdcSwap.target, + }); + + const strkToUsdcCall = sdk.buildCall([approveSTRK, strkToUsdcSwap]); + console.log(" STRK swap call built"); + + // ============================================ + // 5. Swap USDC -> STRK (reverse) + // ============================================ + console.log("\n5. Swap USDC -> STRK"); + + const usdcToStrkSwap = sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.USDC, + sell_token_amount: usdcAmount, + buy_token_address: TOKENS.STRK, + buy_token_amount: "2000000000000000000", // Expected ~2 STRK + buy_token_min_amount: "1900000000000000000", // Min with slippage + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.USDC, + buy_token: TOKENS.STRK, + exchange_address: "0x456", + percent: "100", + additional_swap_params: [], + }, + ], + }); + console.log(" USDC -> STRK swap:", { + target: usdcToStrkSwap.target, + }); + + // ============================================ + // 6. Multi-route swap (split across DEXes) + // ============================================ + console.log("\n6. Multi-route swap (split across DEXes)"); + + const multiRouteSwap = sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.ETH, + sell_token_amount: ethAmount, + buy_token_address: TOKENS.USDC, + buy_token_amount: "3000000000", + buy_token_min_amount: "2900000000", + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x111", // JediSwap + percent: "50", // 50% through JediSwap + additional_swap_params: [], + }, + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x222", // MySwap + percent: "30", // 30% through MySwap + additional_swap_params: [], + }, + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x333", // Ekubo + percent: "20", // 20% through Ekubo + additional_swap_params: [], + }, + ], + }); + console.log(" Multi-route swap:", { + target: multiRouteSwap.target, + routeCount: 3, + }); + + // ============================================ + // 7. Full trading session + // ============================================ + console.log("\n7. Full trading session: Multiple swaps"); + + const tradingSessionOps = [ + sdk.approve({ + target: TOKENS.ETH, + spender: AVNU_ROUTER, + amount: ethAmount, + }), + sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.ETH, + sell_token_amount: ethAmount, + buy_token_address: TOKENS.USDC, + buy_token_amount: "3000000000", + buy_token_min_amount: "2900000000", + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x123", + percent: "100", + additional_swap_params: [], + }, + ], + }), + sdk.approve({ + target: TOKENS.USDC, + spender: AVNU_ROUTER, + amount: "1500000000", // 1500 USDC + }), + sdk.multiRouteSwap({ + target: AVNU_ROUTER, + sell_token_address: TOKENS.USDC, + sell_token_amount: "1500000000", + buy_token_address: TOKENS.STRK, + buy_token_amount: "3000000000000000000000", + buy_token_min_amount: "2900000000000000000000", + beneficiary: VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.USDC, + buy_token: TOKENS.STRK, + exchange_address: "0x456", + percent: "100", + additional_swap_params: [], + }, + ], + }), + ]; + + const tradingSessionCall = sdk.buildCall(tradingSessionOps); + console.log(" Trading session call:", { + contractAddress: tradingSessionCall.contractAddress, + entrypoint: tradingSessionCall.entrypoint, + operationCount: tradingSessionOps.length, + calldataLength: (tradingSessionCall.calldata as string[]).length, + }); + + console.log("\n=== AVNU Swap Example Complete ==="); + console.log("\nNotes:"); + console.log("- Always approve tokens before swapping"); + console.log("- buy_token_min_amount provides slippage protection"); + console.log("- Routes can be split across multiple DEXes for better execution"); + console.log("- Get actual routes from AVNU API for production use"); +} + +testAvnuSwaps().catch(console.error); + +export { testAvnuSwaps }; diff --git a/sdk/examples/test_cctp.ts b/sdk/examples/test_cctp.ts new file mode 100644 index 00000000..f60e8ba9 --- /dev/null +++ b/sdk/examples/test_cctp.ts @@ -0,0 +1,138 @@ +/** + * Example: CCTP (Cross-Chain Transfer Protocol) Operations with VaultCuratorSDK + * Demonstrates native USDC bridging via Circle's CCTP + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + // CCTP uses native USDC which may have a different address + USDC_CCTP: + "1442471627432665843583957153937277124821302887621015682060980008275741980155", +}; + +// CCTP middleware address +const CCTP_MIDDLEWARE = + "2481085077367507779430085564211470162232307088275067678916369282054874743301"; + +// Destination domain (Ethereum = 0 for CCTP) +const ETHEREUM_CCTP_DOMAIN = "0"; + +// Recipient address on destination chain (as u256) +// 0x732357e321Bf7a02CbB690fc2a629161D7722e29 +const MINT_RECIPIENT_LOW = "44858727236356512580505469151245119017"; +const MINT_RECIPIENT_HIGH = "1931696099"; +const MINT_RECIPIENT = "0x732357e321Bf7a02CbB690fc2a629161D7722e29"; + +// Destination caller (0 = no restriction) +const DESTINATION_CALLER = "0"; + +async function testCctpOperations() { + console.log("=== CCTP Bridge Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const bridgeAmount = "1000000"; // 1 USDC (6 decimals) + const maxFee = "10000"; // Max fee in USDC + const minFinalityThreshold = "1"; // Minimum finality blocks + + // ============================================ + // 1. Approve USDC for CCTP middleware + // ============================================ + console.log("1. Approve USDC for CCTP middleware"); + + const approveOp = sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: CCTP_MIDDLEWARE, + amount: bridgeAmount, + }); + console.log(" Approve operation:", { + target: approveOp.target, + }); + + // ============================================ + // 2. Bridge USDC via CCTP (deposit_for_burn) + // ============================================ + console.log("\n2. Bridge USDC to Ethereum via CCTP"); + console.log(" Destination domain:", ETHEREUM_CCTP_DOMAIN, "(Ethereum)"); + console.log(" Mint recipient:", MINT_RECIPIENT); + + const bridgeOp = sdk.bridgeTokenCctpMiddleware({ + burn_token: TOKENS.USDC_CCTP, + token_to_claim: TOKENS.USDC, + amount: bridgeAmount, + destination_domain: ETHEREUM_CCTP_DOMAIN, + mint_recipient: MINT_RECIPIENT, + destination_caller: DESTINATION_CALLER, + max_fee: maxFee, + min_finality_threshold: minFinalityThreshold, + }); + console.log(" Bridge operation:", { + target: bridgeOp.target, + selector: bridgeOp.selector, + }); + + const bridgeCall = sdk.buildCall([approveOp, bridgeOp]); + console.log(" Combined call:", { + contractAddress: bridgeCall.contractAddress, + entrypoint: bridgeCall.entrypoint, + calldataLength: (bridgeCall.calldata as string[]).length, + }); + + // ============================================ + // 3. Full bridge cycle + // ============================================ + console.log("\n3. Full bridge cycle: Approve + Bridge"); + + const fullBridgeOps = [ + sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: CCTP_MIDDLEWARE, + amount: bridgeAmount, + }), + sdk.bridgeTokenCctpMiddleware({ + burn_token: TOKENS.USDC_CCTP, + token_to_claim: TOKENS.USDC, + amount: bridgeAmount, + destination_domain: ETHEREUM_CCTP_DOMAIN, + mint_recipient: MINT_RECIPIENT, + destination_caller: DESTINATION_CALLER, + max_fee: maxFee, + min_finality_threshold: minFinalityThreshold, + }), + ]; + + const fullBridgeCall = sdk.buildCall(fullBridgeOps); + console.log(" Full bridge call:", { + contractAddress: fullBridgeCall.contractAddress, + entrypoint: fullBridgeCall.entrypoint, + operationCount: fullBridgeOps.length, + }); + + // ============================================ + // 4. Understanding CCTP parameters + // ============================================ + console.log("\n4. CCTP Parameter Reference:"); + console.log(" - burn_token: The USDC token to burn on source chain"); + console.log(" - token_to_claim: The USDC token to receive on destination"); + console.log(" - destination_domain: 0=Ethereum, 1=Avalanche, etc."); + console.log(" - mint_recipient: Address receiving USDC on destination"); + console.log(" - destination_caller: Restrict who can complete the transfer (0=anyone)"); + console.log(" - max_fee: Maximum fee willing to pay"); + console.log(" - min_finality_threshold: Blocks to wait for finality"); + + console.log("\n=== CCTP Bridge Example Complete ==="); + console.log("\nNotes:"); + console.log("- CCTP is Circle's native USDC bridging protocol"); + console.log("- USDC is burned on source chain and minted on destination"); + console.log("- More secure than wrapped tokens (no bridge risk)"); + console.log("- Domain 0 = Ethereum for CCTP (different from Hyperlane)"); +} + +testCctpOperations().catch(console.error); + +export { testCctpOperations }; diff --git a/sdk/examples/test_curator.ts b/sdk/examples/test_curator.ts new file mode 100644 index 00000000..2e935483 --- /dev/null +++ b/sdk/examples/test_curator.ts @@ -0,0 +1,403 @@ +/** + * Comprehensive VaultCuratorSDK Example + * Demonstrates all available operations in a single test file + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// ============================================ +// Token and Contract Addresses (from test.json) +// ============================================ +const TOKENS = { + ETH: "2087021424722619777119509474943472645767659996348769578120564519014510906823", + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + USDT: "2967174050445828070862061291903957281356339325911846264948421066253307482040", + STRK: "2009894490435840142178314390393166646092438090257831307886760648929397478285", + WBTC: "1806018566677800621296032626439935115720767031724401394291089442012247156652", + wstETH: "154717502686997779505242937237748798500912348117963555524611254740330341259", + SolvBTC: "2522838177878422711967992029571128884451814651829189911296693586560466229864", + // fyUSDC uses USDC_CCTP as underlying + USDC_CCTP: "1442471627432665843583957153937277124821302887621015682060980008275741980155", +}; + +const VAULTS = { + fyETH: "2273985559333219724429290159602994127325561082984750994597522992026660496918", + fyUSDC: "3614629205322087119066064540472892217795595114760929205615786401399069436865", +}; + +const CONTRACTS = { + VAULT_ALLOCATOR: "3148260697098218922501559176188655100084124891713026095862682167186975521235", + AVNU_ROUTER: "3357347207369430956573753970315372111359878978740136719808196559187186094847", + VESU_POOL: "1326796927197022071246993880086420967181713746138493709882850328569146018479", + EKUBO_ADAPTER: "2516568162210255095453483626839089014569257854319119258892841327983140950402", + STARKGATE_MIDDLEWARE: "2481085077367507779430085564211470162232307088275067678916369282054874743299", + HYPERLANE_MIDDLEWARE: "2481085077367507779430085564211470162232307088275067678916369282054874743300", + CCTP_MIDDLEWARE: "2481085077367507779430085564211470162232307088275067678916369282054874743301", +}; + +// L1/Cross-chain addresses +const L1_RECIPIENT = "917551056842671309452305380979543736893630245704"; +const L1_USDC = "657322120784522198527611271132108531893007429161"; +const CROSS_CHAIN_RECIPIENT = "0x732357e321Bf7a02CbB690fc2a629161D7722e29"; + +async function testAllOperations() { + console.log("=== Comprehensive VaultCuratorSDK Example ===\n"); + + // Load the SDK + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // ============================================ + // 1. BRING LIQUIDITY + // ============================================ + console.log("1. BRING LIQUIDITY"); + const bringLiquidityOp = sdk.bringLiquidity({ + amount: "1000000", // 1 USDC + }); + console.log(" Created bring liquidity operation"); + + // ============================================ + // 2. ERC4626 OPERATIONS + // ============================================ + console.log("\n2. ERC4626 OPERATIONS (fyETH vault)"); + + const erc4626Ops = [ + sdk.approve({ + target: TOKENS.ETH, + spender: VAULTS.fyETH, + amount: "1000000000000000000", + }), + sdk.deposit({ + target: VAULTS.fyETH, + assets: "1000000000000000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + }), + sdk.mint({ + target: VAULTS.fyETH, + shares: "500000000000000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + }), + sdk.withdraw({ + target: VAULTS.fyETH, + assets: "500000000000000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + owner: CONTRACTS.VAULT_ALLOCATOR, + }), + sdk.redeem({ + target: VAULTS.fyETH, + shares: "250000000000000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + owner: CONTRACTS.VAULT_ALLOCATOR, + }), + ]; + console.log(" Created", erc4626Ops.length, "ERC4626 operations"); + + // ============================================ + // 3. SVK STRATEGIES (Async Redemption) + // ============================================ + console.log("\n3. SVK STRATEGIES (fyUSDC vault with async redemption)"); + + const svkOps = [ + sdk.approve({ + target: TOKENS.USDC_CCTP, // fyUSDC uses USDC_CCTP as underlying + spender: VAULTS.fyUSDC, + amount: "1000000", + }), + sdk.deposit({ + target: VAULTS.fyUSDC, + assets: "1000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + }), + sdk.requestRedeem({ + target: VAULTS.fyUSDC, + shares: "500000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + owner: CONTRACTS.VAULT_ALLOCATOR, + }), + sdk.claimRedeem({ + target: VAULTS.fyUSDC, + id: "1", + }), + ]; + console.log(" Created", svkOps.length, "SVK operations"); + + // ============================================ + // 4. VESU V2 LENDING + // ============================================ + console.log("\n4. VESU V2 LENDING"); + + const vesuOps = [ + sdk.approve({ + target: TOKENS.WBTC, + spender: CONTRACTS.VESU_POOL, + amount: "100000000", + }), + sdk.modifyPositionV2({ + target: CONTRACTS.VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: CONTRACTS.VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { abs: "100000000", is_negative: false }, + }, + debt: { + denomination: "Native", + value: { abs: "50000000", is_negative: false }, + }, + }), + ]; + console.log(" Created", vesuOps.length, "Vesu V2 operations"); + + // ============================================ + // 5. EKUBO LP + // ============================================ + console.log("\n5. EKUBO LP OPERATIONS"); + + const ekuboOps = [ + sdk.approve({ + target: TOKENS.WBTC, + spender: CONTRACTS.EKUBO_ADAPTER, + amount: "10000000", + }), + sdk.approve({ + target: TOKENS.SolvBTC, + spender: CONTRACTS.EKUBO_ADAPTER, + amount: "10000000", + }), + sdk.ekuboDepositLiquidity({ + target: CONTRACTS.EKUBO_ADAPTER, + amount0: "10000000", + amount1: "10000000", + }), + sdk.ekuboWithdrawLiquidity({ + target: CONTRACTS.EKUBO_ADAPTER, + ratioWad: "500000000000000000", // 50% + minToken0: "0", + minToken1: "0", + }), + sdk.ekuboCollectFees({ + target: CONTRACTS.EKUBO_ADAPTER, + }), + ]; + console.log(" Created", ekuboOps.length, "Ekubo operations"); + + // ============================================ + // 6. AVNU SWAPS + // ============================================ + console.log("\n6. AVNU SWAP OPERATIONS"); + + const avnuOps = [ + sdk.approve({ + target: TOKENS.ETH, + spender: CONTRACTS.AVNU_ROUTER, + amount: "1000000000000000000", + }), + sdk.multiRouteSwap({ + target: CONTRACTS.AVNU_ROUTER, + sell_token_address: TOKENS.ETH, + sell_token_amount: "1000000000000000000", + buy_token_address: TOKENS.USDC, + buy_token_amount: "3000000000", + buy_token_min_amount: "2900000000", + beneficiary: CONTRACTS.VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.ETH, + buy_token: TOKENS.USDC, + exchange_address: "0x123", + percent: "100", + additional_swap_params: [], + }, + ], + }), + ]; + console.log(" Created", avnuOps.length, "AVNU operations"); + + // ============================================ + // 7. STARKGATE BRIDGE + // ============================================ + console.log("\n7. STARKGATE BRIDGE"); + + const starkgateOps = [ + sdk.approve({ + target: TOKENS.USDC, + spender: CONTRACTS.STARKGATE_MIDDLEWARE, + amount: "1000000", + }), + sdk.bridgeTokenStarkgate({ + l1_token: L1_USDC, + l1_recipient: L1_RECIPIENT, + amount: "1000000", + }), + ]; + console.log(" Created", starkgateOps.length, "Starkgate operations"); + + // ============================================ + // 8. HYPERLANE BRIDGE + // ============================================ + console.log("\n8. HYPERLANE BRIDGE"); + + const hyperlaneOps = [ + sdk.approve({ + target: TOKENS.USDC, + spender: CONTRACTS.HYPERLANE_MIDDLEWARE, + amount: "1000000", + }), + sdk.approve({ + target: TOKENS.STRK, + spender: CONTRACTS.HYPERLANE_MIDDLEWARE, + amount: "100000000000000000", + }), + sdk.bridgeTokenHyperlaneMiddleware({ + source_token: TOKENS.USDC, + destination_token: TOKENS.USDC, + amount: "1000000", + destination_domain: "1", + recipient: CROSS_CHAIN_RECIPIENT, + strk_fee: "100000000000000000", + }), + ]; + console.log(" Created", hyperlaneOps.length, "Hyperlane operations"); + + // ============================================ + // 9. CCTP BRIDGE + // ============================================ + console.log("\n9. CCTP BRIDGE"); + + const cctpOps = [ + sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: CONTRACTS.CCTP_MIDDLEWARE, + amount: "1000000", + }), + sdk.bridgeTokenCctpMiddleware({ + burn_token: TOKENS.USDC_CCTP, + token_to_claim: TOKENS.USDC, + amount: "1000000", + destination_domain: "0", + mint_recipient: CROSS_CHAIN_RECIPIENT, + destination_caller: "0", + max_fee: "10000", + min_finality_threshold: "1", + }), + ]; + console.log(" Created", cctpOps.length, "CCTP operations"); + + // ============================================ + // 10. BUILD COMBINED CALLS + // ============================================ + console.log("\n10. BUILDING COMBINED CALLS"); + + // Example: Investment strategy + const investmentStrategyOps = [ + // Bring liquidity to the vault + sdk.bringLiquidity({ amount: "10000000" }), + // Deposit into fyUSDC (uses USDC_CCTP as underlying) + sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: VAULTS.fyUSDC, + amount: "5000000", + }), + sdk.deposit({ + target: VAULTS.fyUSDC, + assets: "5000000", + receiver: CONTRACTS.VAULT_ALLOCATOR, + }), + // Add collateral to Vesu + sdk.approve({ + target: TOKENS.WBTC, + spender: CONTRACTS.VESU_POOL, + amount: "50000000", + }), + sdk.modifyPositionV2({ + target: CONTRACTS.VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: CONTRACTS.VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { abs: "50000000", is_negative: false }, + }, + debt: { + denomination: "Native", + value: { abs: "0", is_negative: false }, + }, + }), + ]; + + const investmentCall = sdk.buildCall(investmentStrategyOps); + console.log(" Investment strategy call:", { + contractAddress: investmentCall.contractAddress, + entrypoint: investmentCall.entrypoint, + operationCount: investmentStrategyOps.length, + calldataLength: (investmentCall.calldata as string[]).length, + }); + + // Example: Rebalancing strategy + const rebalanceOps = [ + // Withdraw from Ekubo + sdk.ekuboWithdrawLiquidity({ + target: CONTRACTS.EKUBO_ADAPTER, + ratioWad: "1000000000000000000", // 100% + minToken0: "0", + minToken1: "0", + }), + sdk.ekuboCollectFees({ + target: CONTRACTS.EKUBO_ADAPTER, + }), + // Swap rewards + sdk.approve({ + target: TOKENS.STRK, + spender: CONTRACTS.AVNU_ROUTER, + amount: "1000000000000000000", + }), + sdk.multiRouteSwap({ + target: CONTRACTS.AVNU_ROUTER, + sell_token_address: TOKENS.STRK, + sell_token_amount: "1000000000000000000", + buy_token_address: TOKENS.USDC, + buy_token_amount: "500000", + buy_token_min_amount: "480000", + beneficiary: CONTRACTS.VAULT_ALLOCATOR, + integrator_fee_amount_bps: "0", + integrator_fee_recipient: "0", + routes: [ + { + sell_token: TOKENS.STRK, + buy_token: TOKENS.USDC, + exchange_address: "0x456", + percent: "100", + additional_swap_params: [], + }, + ], + }), + ]; + + const rebalanceCall = sdk.buildCall(rebalanceOps); + console.log(" Rebalance strategy call:", { + operationCount: rebalanceOps.length, + calldataLength: (rebalanceCall.calldata as string[]).length, + }); + + // ============================================ + // SUMMARY + // ============================================ + console.log("\n=== SUMMARY ==="); + console.log("Available integrations:"); + console.log(" - ERC4626: deposit, mint, withdraw, redeem"); + console.log(" - SVK: requestRedeem, claimRedeem (async redemption)"); + console.log(" - Vesu V2: modifyPositionV2 (lending/borrowing)"); + console.log(" - Ekubo: depositLiquidity, withdrawLiquidity, collectFees, harvest"); + console.log(" - AVNU: multiRouteSwap (DEX aggregator)"); + console.log(" - Starkgate: bridgeTokenStarkgate, claimTokenStarkgate"); + console.log(" - Hyperlane: bridgeTokenHyperlaneMiddleware, claimTokenHyperlaneMiddleware"); + console.log(" - CCTP: bridgeTokenCctpMiddleware, claimTokenCctpMiddleware"); + console.log("\nAll operations return MerkleOperation objects."); + console.log("Use buildCall() to combine multiple operations into a single transaction."); +} + +testAllOperations().catch(console.error); + +export { testAllOperations }; diff --git a/sdk/examples/test_ekubo.ts b/sdk/examples/test_ekubo.ts new file mode 100644 index 00000000..20d330d1 --- /dev/null +++ b/sdk/examples/test_ekubo.ts @@ -0,0 +1,200 @@ +/** + * Example: Ekubo LP Operations with VaultCuratorSDK + * Demonstrates liquidity provision, withdrawal, fee collection, and harvesting + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + WBTC: "1806018566677800621296032626439935115720767031724401394291089442012247156652", + SolvBTC: + "2522838177878422711967992029571128884451814651829189911296693586560466229864", + STRK: "2009894490435840142178314390393166646092438090257831307886760648929397478285", +}; + +// Ekubo adapter for WBTC/SolvBTC pair +const EKUBO_ADAPTER = + "2516568162210255095453483626839089014569257854319119258892841327983140950402"; + +async function testEkuboOperations() { + console.log("=== Ekubo LP Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const wbtcAmount = "10000000"; // 0.1 WBTC (8 decimals) + const solvBtcAmount = "10000000"; // 0.1 SolvBTC (8 decimals) + const withdrawRatioWad = "500000000000000000"; // 50% (18 decimals, WAD format) + + // ============================================ + // 1. Approve tokens for Ekubo adapter + // ============================================ + console.log("1. Approve WBTC and SolvBTC for Ekubo adapter"); + + const approveWBTC = sdk.approve({ + target: TOKENS.WBTC, + spender: EKUBO_ADAPTER, + amount: wbtcAmount, + }); + console.log(" Approve WBTC:", { target: approveWBTC.target }); + + const approveSolvBTC = sdk.approve({ + target: TOKENS.SolvBTC, + spender: EKUBO_ADAPTER, + amount: solvBtcAmount, + }); + console.log(" Approve SolvBTC:", { target: approveSolvBTC.target }); + + // ============================================ + // 2. Deposit liquidity to Ekubo + // ============================================ + console.log("\n2. Deposit liquidity to Ekubo pool"); + + const depositLiquidityOp = sdk.ekuboDepositLiquidity({ + target: EKUBO_ADAPTER, + amount0: wbtcAmount, + amount1: solvBtcAmount, + }); + console.log(" Deposit liquidity operation:", { + target: depositLiquidityOp.target, + selector: depositLiquidityOp.selector, + }); + + const depositLiquidityCall = sdk.buildCall([ + approveWBTC, + approveSolvBTC, + depositLiquidityOp, + ]); + console.log(" Combined call built with", (depositLiquidityCall.calldata as string[]).length, "elements"); + + // ============================================ + // 3. Withdraw liquidity from Ekubo + // ============================================ + console.log("\n3. Withdraw liquidity from Ekubo pool"); + + const withdrawLiquidityOp = sdk.ekuboWithdrawLiquidity({ + target: EKUBO_ADAPTER, + ratioWad: withdrawRatioWad, // 50% withdrawal + minToken0: "0", // Minimum WBTC to receive + minToken1: "0", // Minimum SolvBTC to receive + }); + console.log(" Withdraw liquidity operation:", { + target: withdrawLiquidityOp.target, + selector: withdrawLiquidityOp.selector, + }); + + const withdrawCall = sdk.buildCall([withdrawLiquidityOp]); + console.log(" Withdraw call built"); + + // ============================================ + // 4. Collect trading fees + // ============================================ + console.log("\n4. Collect accumulated trading fees"); + + const collectFeesOp = sdk.ekuboCollectFees({ + target: EKUBO_ADAPTER, + }); + console.log(" Collect fees operation:", { + target: collectFeesOp.target, + selector: collectFeesOp.selector, + }); + + const collectFeesCall = sdk.buildCall([collectFeesOp]); + console.log(" Collect fees call built"); + + // ============================================ + // 5. Harvest rewards (e.g., STRK incentives) + // ============================================ + console.log("\n5. Harvest STRK rewards"); + + // Note: proof and rewardContract would come from Ekubo's reward API + const mockRewardContract = + "123456789"; // This would be the actual reward contract + const mockProof = ["0x123", "0x456", "0x789"]; // Merkle proof from Ekubo + + const harvestOp = sdk.ekuboHarvest({ + target: EKUBO_ADAPTER, + rewardContract: mockRewardContract, + id: "35", // Claim ID from Ekubo's reward API + amount: "1000000000000000000", // 1 STRK reward + proof: mockProof, + rewardToken: TOKENS.STRK, + }); + console.log(" Harvest operation:", { + target: harvestOp.target, + selector: harvestOp.selector, + }); + + const harvestCall = sdk.buildCall([harvestOp]); + console.log(" Harvest call built"); + + // ============================================ + // 6. Full LP management cycle + // ============================================ + console.log("\n6. Full LP management: Approve + Deposit + Collect fees"); + + const fullCycleOps = [ + sdk.approve({ + target: TOKENS.WBTC, + spender: EKUBO_ADAPTER, + amount: wbtcAmount, + }), + sdk.approve({ + target: TOKENS.SolvBTC, + spender: EKUBO_ADAPTER, + amount: solvBtcAmount, + }), + sdk.ekuboDepositLiquidity({ + target: EKUBO_ADAPTER, + amount0: wbtcAmount, + amount1: solvBtcAmount, + }), + sdk.ekuboCollectFees({ + target: EKUBO_ADAPTER, + }), + ]; + + const fullCycleCall = sdk.buildCall(fullCycleOps); + console.log(" Full cycle call:", { + contractAddress: fullCycleCall.contractAddress, + entrypoint: fullCycleCall.entrypoint, + operationCount: fullCycleOps.length, + calldataLength: (fullCycleCall.calldata as string[]).length, + }); + + // ============================================ + // 7. Withdraw all and collect fees + // ============================================ + console.log("\n7. Full exit: Withdraw all + Collect fees"); + + const exitOps = [ + sdk.ekuboWithdrawLiquidity({ + target: EKUBO_ADAPTER, + ratioWad: "1000000000000000000", // 100% withdrawal (1 WAD) + minToken0: "0", + minToken1: "0", + }), + sdk.ekuboCollectFees({ + target: EKUBO_ADAPTER, + }), + ]; + + const exitCall = sdk.buildCall(exitOps); + console.log(" Exit call:", { + contractAddress: exitCall.contractAddress, + entrypoint: exitCall.entrypoint, + operationCount: exitOps.length, + }); + + console.log("\n=== Ekubo LP Example Complete ==="); + console.log("\nNotes:"); + console.log("- ratioWad uses WAD format (1e18 = 100%)"); + console.log("- amount0/amount1 correspond to the pool's token ordering"); + console.log("- harvest requires a valid merkle proof from Ekubo's reward system"); +} + +testEkuboOperations().catch(console.error); + +export { testEkuboOperations }; diff --git a/sdk/examples/test_erc4626.ts b/sdk/examples/test_erc4626.ts new file mode 100644 index 00000000..4de3621c --- /dev/null +++ b/sdk/examples/test_erc4626.ts @@ -0,0 +1,168 @@ +/** + * Example: ERC4626 Operations with VaultCuratorSDK + * Demonstrates deposit, mint, withdraw, and redeem operations on ERC4626 vaults + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + ETH: "2087021424722619777119509474943472645767659996348769578120564519014510906823", + fyETH: + "2273985559333219724429290159602994127325561082984750994597522992026660496918", + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + fyUSDC: + "3614629205322087119066064540472892217795595114760929205615786401399069436865", +}; + +const VAULT_ALLOCATOR = + "3148260697098218922501559176188655100084124891713026095862682167186975521235"; + +async function testERC4626Operations() { + console.log("=== ERC4626 Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const depositAmount = "1000000000000000000"; // 1 ETH (18 decimals) + const mintShares = "500000000000000000"; // 0.5 shares + const withdrawAmount = "500000000000000000"; // 0.5 ETH + const redeemShares = "250000000000000000"; // 0.25 shares + + // ============================================ + // 1. Approve + Deposit into fyETH vault + // ============================================ + console.log("1. Approve ETH for fyETH vault + Deposit"); + + const approveForDeposit = sdk.approve({ + target: TOKENS.ETH, + spender: TOKENS.fyETH, + amount: depositAmount, + }); + console.log(" Approve operation created:", { + target: approveForDeposit.target, + selector: approveForDeposit.selector, + }); + + const depositOp = sdk.deposit({ + target: TOKENS.fyETH, + assets: depositAmount, + receiver: VAULT_ALLOCATOR, + }); + console.log(" Deposit operation created:", { + target: depositOp.target, + selector: depositOp.selector, + }); + + // Build the combined call + const depositCall = sdk.buildCall([approveForDeposit, depositOp]); + console.log(" Combined call:", { + contractAddress: depositCall.contractAddress, + entrypoint: depositCall.entrypoint, + calldataLength: (depositCall.calldata as string[]).length, + }); + + // ============================================ + // 2. Mint shares from fyETH vault + // ============================================ + console.log("\n2. Mint shares from fyETH vault"); + + const mintOp = sdk.mint({ + target: TOKENS.fyETH, + shares: mintShares, + receiver: VAULT_ALLOCATOR, + }); + console.log(" Mint operation created:", { + target: mintOp.target, + selector: mintOp.selector, + }); + + const mintCall = sdk.buildCall([mintOp]); + console.log(" Mint call:", { + contractAddress: mintCall.contractAddress, + entrypoint: mintCall.entrypoint, + }); + + // ============================================ + // 3. Withdraw from fyETH vault + // ============================================ + console.log("\n3. Withdraw from fyETH vault"); + + const withdrawOp = sdk.withdraw({ + target: TOKENS.fyETH, + assets: withdrawAmount, + receiver: VAULT_ALLOCATOR, + owner: VAULT_ALLOCATOR, + }); + console.log(" Withdraw operation created:", { + target: withdrawOp.target, + selector: withdrawOp.selector, + }); + + const withdrawCall = sdk.buildCall([withdrawOp]); + console.log(" Withdraw call:", { + contractAddress: withdrawCall.contractAddress, + entrypoint: withdrawCall.entrypoint, + }); + + // ============================================ + // 4. Redeem shares from fyETH vault + // ============================================ + console.log("\n4. Redeem shares from fyETH vault"); + + const redeemOp = sdk.redeem({ + target: TOKENS.fyETH, + shares: redeemShares, + receiver: VAULT_ALLOCATOR, + owner: VAULT_ALLOCATOR, + }); + console.log(" Redeem operation created:", { + target: redeemOp.target, + selector: redeemOp.selector, + }); + + const redeemCall = sdk.buildCall([redeemOp]); + console.log(" Redeem call:", { + contractAddress: redeemCall.contractAddress, + entrypoint: redeemCall.entrypoint, + }); + + // ============================================ + // 5. Full cycle: Approve + Deposit + Withdraw in one transaction + // ============================================ + console.log("\n5. Full cycle: Approve + Deposit + Withdraw"); + + const fullCycleOps = [ + sdk.approve({ + target: TOKENS.ETH, + spender: TOKENS.fyETH, + amount: depositAmount, + }), + sdk.deposit({ + target: TOKENS.fyETH, + assets: depositAmount, + receiver: VAULT_ALLOCATOR, + }), + sdk.withdraw({ + target: TOKENS.fyETH, + assets: withdrawAmount, + receiver: VAULT_ALLOCATOR, + owner: VAULT_ALLOCATOR, + }), + ]; + + const fullCycleCall = sdk.buildCall(fullCycleOps); + console.log(" Full cycle call:", { + contractAddress: fullCycleCall.contractAddress, + entrypoint: fullCycleCall.entrypoint, + operationCount: fullCycleOps.length, + calldataLength: (fullCycleCall.calldata as string[]).length, + }); + + console.log("\n=== ERC4626 Example Complete ==="); +} + +testERC4626Operations().catch(console.error); + +export { testERC4626Operations }; diff --git a/sdk/examples/test_hyperlane.ts b/sdk/examples/test_hyperlane.ts new file mode 100644 index 00000000..0acd1941 --- /dev/null +++ b/sdk/examples/test_hyperlane.ts @@ -0,0 +1,128 @@ +/** + * Example: Hyperlane Bridge Operations with VaultCuratorSDK + * Demonstrates cross-chain token bridging via Hyperlane + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + STRK: "2009894490435840142178314390393166646092438090257831307886760648929397478285", +}; + +// Hyperlane middleware address +const HYPERLANE_MIDDLEWARE = + "2481085077367507779430085564211470162232307088275067678916369282054874743300"; + +// Destination domain (Ethereum = 1) +const ETHEREUM_DOMAIN = "1"; + +// Recipient address on destination chain (as u256) +// 0x732357e321Bf7a02CbB690fc2a629161D7722e29 +const RECIPIENT_LOW = "44858727236356512580505469151245119017"; // low 128 bits +const RECIPIENT_HIGH = "1931696099"; // high 128 bits +const RECIPIENT = "0x732357e321Bf7a02CbB690fc2a629161D7722e29"; + +async function testHyperlaneOperations() { + console.log("=== Hyperlane Bridge Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const bridgeAmount = "1000000"; // 1 USDC (6 decimals) + const strkFee = "100000000000000000"; // 0.1 STRK fee + + // ============================================ + // 1. Approve tokens for Hyperlane middleware + // ============================================ + console.log("1. Approve USDC and STRK for Hyperlane middleware"); + console.log(" Note: STRK is used to pay for cross-chain message fees"); + + const approveUSDC = sdk.approve({ + target: TOKENS.USDC, + spender: HYPERLANE_MIDDLEWARE, + amount: bridgeAmount, + }); + console.log(" Approve USDC:", { target: approveUSDC.target }); + + const approveSTRK = sdk.approve({ + target: TOKENS.STRK, + spender: HYPERLANE_MIDDLEWARE, + amount: strkFee, + }); + console.log(" Approve STRK (for fees):", { target: approveSTRK.target }); + + // ============================================ + // 2. Bridge USDC to Ethereum via Hyperlane + // ============================================ + console.log("\n2. Bridge USDC to Ethereum via Hyperlane"); + console.log(" Destination domain:", ETHEREUM_DOMAIN, "(Ethereum)"); + console.log(" Recipient:", RECIPIENT); + + const bridgeOp = sdk.bridgeTokenHyperlaneMiddleware({ + source_token: TOKENS.USDC, + destination_token: TOKENS.USDC, + amount: bridgeAmount, + destination_domain: ETHEREUM_DOMAIN, + recipient: RECIPIENT, + strk_fee: strkFee, + }); + console.log(" Bridge operation:", { + target: bridgeOp.target, + selector: bridgeOp.selector, + }); + + const bridgeCall = sdk.buildCall([approveUSDC, approveSTRK, bridgeOp]); + console.log(" Combined call:", { + contractAddress: bridgeCall.contractAddress, + entrypoint: bridgeCall.entrypoint, + calldataLength: (bridgeCall.calldata as string[]).length, + }); + + // ============================================ + // 3. Full bridge cycle + // ============================================ + console.log("\n3. Full bridge cycle: Approve USDC + Approve STRK + Bridge"); + + const fullBridgeOps = [ + sdk.approve({ + target: TOKENS.USDC, + spender: HYPERLANE_MIDDLEWARE, + amount: bridgeAmount, + }), + sdk.approve({ + target: TOKENS.STRK, + spender: HYPERLANE_MIDDLEWARE, + amount: strkFee, + }), + sdk.bridgeTokenHyperlaneMiddleware({ + source_token: TOKENS.USDC, + destination_token: TOKENS.USDC, + amount: bridgeAmount, + destination_domain: ETHEREUM_DOMAIN, + recipient: RECIPIENT, + strk_fee: strkFee, + }), + ]; + + const fullBridgeCall = sdk.buildCall(fullBridgeOps); + console.log(" Full bridge call:", { + contractAddress: fullBridgeCall.contractAddress, + entrypoint: fullBridgeCall.entrypoint, + operationCount: fullBridgeOps.length, + }); + + console.log("\n=== Hyperlane Bridge Example Complete ==="); + console.log("\nNotes:"); + console.log("- Hyperlane enables cross-chain token transfers"); + console.log("- STRK is used to pay for cross-chain messaging fees"); + console.log("- destination_domain identifies the target chain (1 = Ethereum)"); + console.log("- recipient is the address on the destination chain"); + console.log("- claim_token is used to receive tokens bridged to Starknet"); +} + +testHyperlaneOperations().catch(console.error); + +export { testHyperlaneOperations }; diff --git a/sdk/examples/test_lz.ts b/sdk/examples/test_lz.ts new file mode 100644 index 00000000..4fcff404 --- /dev/null +++ b/sdk/examples/test_lz.ts @@ -0,0 +1,171 @@ +/** + * Example: LayerZero Bridge Operations with VaultCuratorSDK + * Demonstrates cross-chain token bridging via LayerZero OFT + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (decimal format to match test.json) +const TOKENS = { + WBTC: "1806018566677800621296032626439935115720767031724401394291089442012247156652", + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + STRK: "2009894490435840142178314390393166646092438090257831307886760648929397478285", +}; + +// LayerZero WBTC OFT Adapter contract (decimal format) +const LZ_WBTC_OFT_ADAPTER = + "2987327108307389660628511300677478412428831578776543507146742552736034022137"; + +// LayerZero middleware address (decimal format) +const LZ_MIDDLEWARE = + "2481085077367507779430085564211470162232307088275067678916369282054874743303"; + +// Destination endpoint ID (e.g., Ethereum mainnet = 30101) +const ETH_EID = "30101"; + +// Recipient address on destination chain (as u256 hex string) +// Example: 0x732357e321Bf7a02CbB690fc2a629161D7722e29 +const RECIPIENT = "0x732357e321Bf7a02CbB690fc2a629161D7722e29"; + +async function testLZDirectOFT() { + console.log("=== LayerZero Direct OFT Bridge Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const bridgeAmount = "10000000"; // 0.1 WBTC (8 decimals) + const minAmount = "9900000"; // 0.099 WBTC minimum (1% slippage) + const nativeFee = "100000000000000000"; // 0.1 STRK fee + + // ============================================ + // 1. Approve tokens for OFT adapter contract + // ============================================ + console.log("1. Approve WBTC and STRK for OFT Adapter contract"); + console.log(" Note: STRK is used to pay for cross-chain message fees"); + + const approveWBTC = sdk.approve({ + target: TOKENS.WBTC, + spender: LZ_WBTC_OFT_ADAPTER, + amount: bridgeAmount, + }); + console.log(" Approve WBTC:", { target: approveWBTC.target }); + + const approveSTRK = sdk.approve({ + target: TOKENS.STRK, + spender: LZ_WBTC_OFT_ADAPTER, + amount: nativeFee, + }); + console.log(" Approve STRK (for fees):", { target: approveSTRK.target }); + + // ============================================ + // 2. Bridge WBTC to Ethereum via LayerZero + // ============================================ + console.log("\n2. Bridge WBTC to Ethereum via LayerZero OFT Adapter"); + console.log(" Destination EID:", ETH_EID, "(Ethereum)"); + console.log(" Recipient:", RECIPIENT); + + try { + const bridgeOp = sdk.bridgeLZ({ + oft: LZ_WBTC_OFT_ADAPTER, + dst_eid: ETH_EID, + to: RECIPIENT, + amount: bridgeAmount, + min_amount: minAmount, + native_fee: nativeFee, + }); + console.log(" Bridge operation:", { + target: bridgeOp.target, + selector: bridgeOp.selector, + }); + } catch (e) { + console.log(" Error:", (e as Error).message); + } + + console.log("\n=== Direct OFT Example Complete ==="); +} + +async function testLZMiddleware() { + console.log("\n=== LayerZero Middleware Bridge Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const bridgeAmount = "10000000"; // 0.1 WBTC (8 decimals) + const minAmount = "9900000"; // 0.099 WBTC minimum + const nativeFee = "100000000000000000"; // 0.1 STRK fee + + // ============================================ + // 1. Approve tokens for middleware + // ============================================ + console.log("1. Approve WBTC (underlying) and STRK for middleware"); + + const approveWBTC = sdk.approve({ + target: TOKENS.WBTC, + spender: LZ_MIDDLEWARE, + amount: bridgeAmount, + }); + console.log(" Approve WBTC:", { target: approveWBTC.target }); + + const approveSTRK = sdk.approve({ + target: TOKENS.STRK, + spender: LZ_MIDDLEWARE, + amount: nativeFee, + }); + console.log(" Approve STRK (for fees):", { target: approveSTRK.target }); + + // ============================================ + // 2. Bridge via middleware (for adapter OFT) + // ============================================ + console.log("\n2. Bridge via LZ Middleware"); + console.log(" OFT contract:", LZ_WBTC_OFT_ADAPTER); + console.log(" Underlying token:", TOKENS.WBTC); + console.log(" Token to claim:", TOKENS.USDC); + console.log(" Destination EID:", ETH_EID); + console.log(" Recipient:", RECIPIENT); + + try { + const bridgeOp = sdk.bridgeLZMiddleware({ + oft: LZ_WBTC_OFT_ADAPTER, + underlying_token: TOKENS.WBTC, + token_to_claim: TOKENS.USDC, // Token to receive back (e.g., swap to USDC on dest) + dst_eid: ETH_EID, + to: RECIPIENT, + amount: bridgeAmount, + min_amount: minAmount, + native_fee: nativeFee, + }); + console.log(" Bridge operation:", { + target: bridgeOp.target, + selector: bridgeOp.selector, + }); + + // Build full call with approvals + const fullCall = sdk.buildCall([approveWBTC, approveSTRK, bridgeOp]); + console.log(" Full call:", { + contractAddress: fullCall.contractAddress, + entrypoint: fullCall.entrypoint, + }); + } catch (e) { + console.log(" Error:", (e as Error).message); + } + + console.log("\n=== LayerZero Middleware Example Complete ==="); + console.log("\nNotes:"); + console.log("- LayerZero enables cross-chain token transfers via OFT standard"); + console.log("- STRK is used to pay for cross-chain messaging fees (native_fee)"); + console.log("- dst_eid identifies the destination chain endpoint"); + console.log("- For adapter OFT: underlying_token != oft (approval needed)"); + console.log("- For native OFT: underlying_token == oft"); + console.log("- claim_token is PERMISSIONLESS - call directly on middleware contract"); +} + +async function main() { + await testLZDirectOFT(); + await testLZMiddleware(); +} + +main().catch(console.error); + +export { testLZDirectOFT, testLZMiddleware }; diff --git a/sdk/examples/test_starkgate.ts b/sdk/examples/test_starkgate.ts new file mode 100644 index 00000000..65093971 --- /dev/null +++ b/sdk/examples/test_starkgate.ts @@ -0,0 +1,110 @@ +/** + * Example: Starkgate Bridge Operations with VaultCuratorSDK + * Demonstrates bridging tokens from Starknet L2 to Ethereum L1 + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", +}; + +// Starkgate middleware address +const STARKGATE_MIDDLEWARE = + "2481085077367507779430085564211470162232307088275067678916369282054874743299"; + +// L2 Bridge address for USDC +const STARKGATE_USDC_BRIDGE = + "2624271632322125921217374734393920890821192138210577916078337694621182820758"; + +// L1 recipient address (Ethereum) +const L1_RECIPIENT = "917551056842671309452305380979543736893630245704"; // 0x732357e321Bf7a02CbB690fc2a629161D7722e29 + +// L1 USDC token address +const L1_USDC = "657322120784522198527611271132108531893007429161"; // Ethereum USDC address + +async function testStarkgateOperations() { + console.log("=== Starkgate Bridge Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amount + const bridgeAmount = "1000000"; // 1 USDC (6 decimals) + + // ============================================ + // 1. Approve USDC for Starkgate middleware + // ============================================ + console.log("1. Approve USDC for Starkgate middleware"); + + const approveOp = sdk.approve({ + target: TOKENS.USDC, + spender: STARKGATE_MIDDLEWARE, + amount: bridgeAmount, + }); + console.log(" Approve operation:", { + target: approveOp.target, + }); + + // ============================================ + // 2. Bridge USDC to Ethereum via Starkgate + // ============================================ + console.log("\n2. Bridge USDC to Ethereum"); + console.log(" L1 Recipient:", L1_RECIPIENT); + + const bridgeOp = sdk.bridgeTokenStarkgate({ + l1_token: L1_USDC, + l1_recipient: L1_RECIPIENT, + amount: bridgeAmount, + }); + console.log(" Bridge operation:", { + target: bridgeOp.target, + selector: bridgeOp.selector, + }); + + const bridgeCall = sdk.buildCall([approveOp, bridgeOp]); + console.log(" Combined call:", { + contractAddress: bridgeCall.contractAddress, + entrypoint: bridgeCall.entrypoint, + calldataLength: (bridgeCall.calldata as string[]).length, + }); + + // ============================================ + // 3. Full bridge cycle + // ============================================ + console.log("\n3. Full bridge cycle: Approve + Bridge"); + + const fullBridgeOps = [ + sdk.approve({ + target: TOKENS.USDC, + spender: STARKGATE_MIDDLEWARE, + amount: bridgeAmount, + }), + sdk.bridgeTokenStarkgateMiddleware({ + starkgate_token_bridge: STARKGATE_USDC_BRIDGE, + l1_token: L1_USDC, + l1_recipient: L1_RECIPIENT, + amount: bridgeAmount, + token_to_claim: TOKENS.USDC, + }), + ]; + + const fullBridgeCall = sdk.buildCall(fullBridgeOps); + console.log(" Full bridge call:", { + contractAddress: fullBridgeCall.contractAddress, + entrypoint: fullBridgeCall.entrypoint, + operationCount: fullBridgeOps.length, + }); + + console.log("\n=== Starkgate Bridge Example Complete ==="); + console.log("\nNotes:"); + console.log("- Starkgate bridges tokens between Starknet L2 and Ethereum L1"); + console.log("- L1 withdrawals require waiting for L1 finality"); + console.log("- claim_token_bridged_back is for receiving tokens from L1"); + console.log("- The middleware handles the bridge contract interactions"); +} + +testStarkgateOperations().catch(console.error); + +export { testStarkgateOperations }; diff --git a/sdk/examples/test_svk.ts b/sdk/examples/test_svk.ts new file mode 100644 index 00000000..fd4c51b6 --- /dev/null +++ b/sdk/examples/test_svk.ts @@ -0,0 +1,161 @@ +/** + * Example: Starknet Vault Kit (SVK) Strategy Operations + * Demonstrates async redemption with request_redeem and claim_redeem + * SVK strategies use ERC7540 async redemption pattern + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +// Note: fyUSDC vault uses USDC_CCTP as underlying, not regular USDC +const TOKENS = { + USDC_CCTP: + "1442471627432665843583957153937277124821302887621015682060980008275741980155", + fyUSDC: + "3614629205322087119066064540472892217795595114760929205615786401399069436865", +}; + +const VAULT_ALLOCATOR = + "3148260697098218922501559176188655100084124891713026095862682167186975521235"; + +async function testSVKOperations() { + console.log("=== Starknet Vault Kit Strategy Operations ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const depositAmount = "1000000"; // 1 USDC (6 decimals) + const mintShares = "500000"; // 0.5 shares + const redeemShares = "250000"; // 0.25 shares + const claimId = "1"; // NFT ID for claim + + // ============================================ + // 1. Approve + Deposit into fyUSDC vault + // ============================================ + console.log("1. Approve USDC for fyUSDC vault + Deposit"); + + const approveForDeposit = sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: TOKENS.fyUSDC, + amount: depositAmount, + }); + console.log(" Approve operation:", { + target: approveForDeposit.target, + }); + + const depositOp = sdk.deposit({ + target: TOKENS.fyUSDC, + assets: depositAmount, + receiver: VAULT_ALLOCATOR, + }); + console.log(" Deposit operation:", { + target: depositOp.target, + }); + + const depositCall = sdk.buildCall([approveForDeposit, depositOp]); + console.log(" Combined call built with", (depositCall.calldata as string[]).length, "calldata elements"); + + // ============================================ + // 2. Mint shares from fyUSDC vault + // ============================================ + console.log("\n2. Mint shares from fyUSDC vault"); + + const mintOp = sdk.mint({ + target: TOKENS.fyUSDC, + shares: mintShares, + receiver: VAULT_ALLOCATOR, + }); + console.log(" Mint operation:", { + target: mintOp.target, + }); + + const mintCall = sdk.buildCall([mintOp]); + console.log(" Mint call built"); + + // ============================================ + // 3. Request Redeem (async redemption step 1) + // ============================================ + console.log("\n3. Request Redeem (ERC7540 async redemption)"); + console.log(" Note: This creates an NFT representing the redemption request"); + + const requestRedeemOp = sdk.requestRedeem({ + target: TOKENS.fyUSDC, + shares: redeemShares, + receiver: VAULT_ALLOCATOR, + owner: VAULT_ALLOCATOR, + }); + console.log(" Request redeem operation:", { + target: requestRedeemOp.target, + selector: requestRedeemOp.selector, + }); + + const requestRedeemCall = sdk.buildCall([requestRedeemOp]); + console.log(" Request redeem call built"); + + // ============================================ + // 4. Claim Redeem (async redemption step 2) + // ============================================ + console.log("\n4. Claim Redeem (after epoch transition)"); + console.log(" Note: This burns the NFT and transfers the underlying assets"); + + const claimRedeemOp = sdk.claimRedeem({ + target: TOKENS.fyUSDC, + id: claimId, + }); + console.log(" Claim redeem operation:", { + target: claimRedeemOp.target, + selector: claimRedeemOp.selector, + }); + + const claimRedeemCall = sdk.buildCall([claimRedeemOp]); + console.log(" Claim redeem call built"); + + // ============================================ + // 5. Full SVK investment cycle + // ============================================ + console.log("\n5. Full SVK investment cycle"); + console.log(" Approve -> Deposit -> Mint -> RequestRedeem"); + + const fullCycleOps = [ + sdk.approve({ + target: TOKENS.USDC_CCTP, + spender: TOKENS.fyUSDC, + amount: depositAmount, + }), + sdk.deposit({ + target: TOKENS.fyUSDC, + assets: depositAmount, + receiver: VAULT_ALLOCATOR, + }), + sdk.mint({ + target: TOKENS.fyUSDC, + shares: mintShares, + receiver: VAULT_ALLOCATOR, + }), + sdk.requestRedeem({ + target: TOKENS.fyUSDC, + shares: redeemShares, + receiver: VAULT_ALLOCATOR, + owner: VAULT_ALLOCATOR, + }), + ]; + + const fullCycleCall = sdk.buildCall(fullCycleOps); + console.log(" Full cycle call:", { + contractAddress: fullCycleCall.contractAddress, + entrypoint: fullCycleCall.entrypoint, + operationCount: fullCycleOps.length, + calldataLength: (fullCycleCall.calldata as string[]).length, + }); + + console.log("\n=== SVK Strategy Example Complete ==="); + console.log("\nNotes:"); + console.log("- SVK strategies use ERC7540 async redemption pattern"); + console.log("- request_redeem creates an NFT that can be claimed after epoch transition"); + console.log("- claim_redeem burns the NFT and transfers the underlying assets"); +} + +testSVKOperations().catch(console.error); + +export { testSVKOperations }; diff --git a/sdk/examples/test_vesu_v2.ts b/sdk/examples/test_vesu_v2.ts new file mode 100644 index 00000000..e5e483b3 --- /dev/null +++ b/sdk/examples/test_vesu_v2.ts @@ -0,0 +1,245 @@ +/** + * Example: Vesu V2 Operations with VaultCuratorSDK + * Demonstrates lending/borrowing with modify_position + */ + +import { VaultCuratorSDK } from "../src/curator"; + +// Token addresses (from test.json) +const TOKENS = { + WBTC: "1806018566677800621296032626439935115720767031724401394291089442012247156652", + USDC: "2368576823837625528275935341135881659748932889268308403712618244410713532584", + USDT: "2967174050445828070862061291903957281356339325911846264948421066253307482040", + wstETH: + "154717502686997779505242937237748798500912348117963555524611254740330341259", +}; + +const VESU_POOL = + "1326796927197022071246993880086420967181713746138493709882850328569146018479"; + +const VAULT_ALLOCATOR = + "3148260697098218922501559176188655100084124891713026095862682167186975521235"; + +async function testVesuV2Operations() { + console.log("=== Vesu V2 Operations Example ===\n"); + + // Load the SDK with test config + const sdk = VaultCuratorSDK.fromFile("./examples/test.json"); + + // Example amounts + const collateralAmount = "100000000"; // 1 WBTC (8 decimals) + const borrowAmount = "50000000"; // 50 USDC (6 decimals) + + // ============================================ + // 1. Approve WBTC for Vesu pool + // ============================================ + console.log("1. Approve WBTC for Vesu pool"); + + const approveWBTC = sdk.approve({ + target: TOKENS.WBTC, + spender: VESU_POOL, + amount: collateralAmount, + }); + console.log(" Approve operation created:", { + target: approveWBTC.target, + }); + + // ============================================ + // 2. Add collateral (WBTC) to Vesu + // ============================================ + console.log("\n2. Add WBTC collateral to Vesu"); + + const addCollateralOp = sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { + abs: collateralAmount, + is_negative: false, // positive = add collateral + }, + }, + debt: { + denomination: "Native", + value: { + abs: "0", + is_negative: false, + }, + }, + }); + console.log(" Add collateral operation:", { + target: addCollateralOp.target, + selector: addCollateralOp.selector, + }); + + const addCollateralCall = sdk.buildCall([approveWBTC, addCollateralOp]); + console.log(" Combined call built with", (addCollateralCall.calldata as string[]).length, "elements"); + + // ============================================ + // 3. Borrow USDC against WBTC collateral + // ============================================ + console.log("\n3. Borrow USDC against WBTC collateral"); + + const borrowOp = sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { + abs: "0", + is_negative: false, + }, + }, + debt: { + denomination: "Native", + value: { + abs: borrowAmount, + is_negative: false, // positive = borrow + }, + }, + }); + console.log(" Borrow operation:", { + target: borrowOp.target, + selector: borrowOp.selector, + }); + + const borrowCall = sdk.buildCall([borrowOp]); + console.log(" Borrow call built"); + + // ============================================ + // 4. Repay debt (USDC) + // ============================================ + console.log("\n4. Repay USDC debt"); + + const repayOp = sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { + abs: "0", + is_negative: false, + }, + }, + debt: { + denomination: "Native", + value: { + abs: borrowAmount, + is_negative: true, // negative = repay + }, + }, + }); + console.log(" Repay operation:", { + target: repayOp.target, + selector: repayOp.selector, + }); + + // ============================================ + // 5. Remove collateral (WBTC) + // ============================================ + console.log("\n5. Remove WBTC collateral"); + + const removeCollateralOp = sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { + abs: collateralAmount, + is_negative: true, // negative = remove collateral + }, + }, + debt: { + denomination: "Native", + value: { + abs: "0", + is_negative: false, + }, + }, + }); + console.log(" Remove collateral operation:", { + target: removeCollateralOp.target, + selector: removeCollateralOp.selector, + }); + + // ============================================ + // 6. Full leverage cycle + // ============================================ + console.log("\n6. Full leverage cycle: Approve + Add collateral + Borrow"); + + const leverageCycleOps = [ + sdk.approve({ + target: TOKENS.WBTC, + spender: VESU_POOL, + amount: collateralAmount, + }), + sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.WBTC, + debt_asset: TOKENS.USDC, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { abs: collateralAmount, is_negative: false }, + }, + debt: { + denomination: "Native", + value: { abs: borrowAmount, is_negative: false }, + }, + }), + ]; + + const leverageCall = sdk.buildCall(leverageCycleOps); + console.log(" Leverage call:", { + contractAddress: leverageCall.contractAddress, + entrypoint: leverageCall.entrypoint, + operationCount: leverageCycleOps.length, + }); + + // ============================================ + // 7. Using wstETH as collateral + // ============================================ + console.log("\n7. Alternative: wstETH collateral, USDT debt"); + + const wstETHCollateralOp = sdk.modifyPositionV2({ + target: VESU_POOL, + collateral_asset: TOKENS.wstETH, + debt_asset: TOKENS.USDT, + user: VAULT_ALLOCATOR, + collateral: { + denomination: "Native", + value: { + abs: "1000000000000000000", // 1 wstETH + is_negative: false, + }, + }, + debt: { + denomination: "Native", + value: { + abs: "1000000", // 1 USDT + is_negative: false, + }, + }, + }); + console.log(" wstETH/USDT position:", { + target: wstETHCollateralOp.target, + }); + + console.log("\n=== Vesu V2 Example Complete ==="); + console.log("\nNotes:"); + console.log("- is_negative: false for collateral = deposit, true = withdraw"); + console.log("- is_negative: false for debt = borrow, true = repay"); + console.log("- denomination: 'Native' uses raw amounts, 'Assets' uses scaled amounts"); +} + +testVesuV2Operations().catch(console.error); + +export { testVesuV2Operations }; diff --git a/sdk/package.json b/sdk/package.json index 02f0ac52..ee3e5414 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { - "name": "@starknet-vault-kit/sdk", - "version": "0.1.0", + "name": "@forge_yields/starknet_vault_kit_sdk", + "version": "0.1.1", "description": "TypeScript SDK for Starknet Vault Kit - user and curator operations", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -43,4 +43,4 @@ "url": "https://github.com/your-org/starknet-vault-kit.git", "directory": "sdk" } -} \ No newline at end of file +} diff --git a/sdk/src/curator/index.ts b/sdk/src/curator/index.ts index 1865fe0f..60fd0785 100644 --- a/sdk/src/curator/index.ts +++ b/sdk/src/curator/index.ts @@ -1 +1,388 @@ -export class VaultCuratorSDK {} +import { Call, uint256, hash, selector } from "starknet"; +import * as fs from "fs"; + +// Re-export all types +export * from "./types"; + +// Import integration modules +import * as erc4626 from "./integrations/erc4626"; +import * as avnu from "./integrations/avnu"; +import * as starkgate from "./integrations/starkgate"; +import * as hyperlane from "./integrations/hyperlane"; +import * as cctp from "./integrations/cctp"; +import * as lz from "./integrations/lz"; +import * as vesu from "./integrations/vesu"; +import * as ekubo from "./integrations/ekubo"; + +import { + VaultConfigData, + MerkleOperation, + BringLiquidityParams, + ApproveParams, + DepositParams, + MintParams, + WithdrawParams, + RedeemParams, + MultiRouteSwapParams, + RequestRedeemParams, + ClaimRedeemParams, + BridgeTokenStarkgateParams, + BridgeTokenStarkgateMiddlewareParams, + BridgeTokenHyperlaneMiddlewareParams, + BridgeTokenCctpMiddlewareParams, + BridgeLZParams, + BridgeLZMiddlewareParams, + ModifyPositionParamsV2, + EkuboDepositLiquidityParams, + EkuboWithdrawLiquidityParams, + EkuboCollectFeesParams, + EkuboHarvestParams, +} from "./types"; + +export class VaultCuratorSDK { + private config: VaultConfigData; + + constructor(config: VaultConfigData) { + this.config = config; + } + + static fromFile(configPath: string): VaultCuratorSDK { + const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + return new VaultCuratorSDK(config); + } + + // ============================================ + // Core methods + // ============================================ + + public buildCall(operations: MerkleOperation[]): Call { + if (operations.length === 0) { + throw new Error("No operations provided"); + } + + const manageProofs: string[] = []; + const decodersAndSanitizers: string[] = []; + const targets: string[] = []; + const selectors: string[] = []; + const calldatas: string[] = []; + + for (const op of operations) { + manageProofs.push(op.manageProofs.length.toString(), ...op.manageProofs); + decodersAndSanitizers.push(op.decoderAndSanitizer); + targets.push(op.target); + selectors.push(op.selector); + calldatas.push(op.calldata.length.toString(), ...op.calldata); + } + + return { + contractAddress: this.config.metadata.manager, + entrypoint: "manage_vault_with_merkle_verification", + calldata: [ + operations.length.toString(), + ...manageProofs, + operations.length.toString(), + ...decodersAndSanitizers, + operations.length.toString(), + ...targets, + operations.length.toString(), + ...selectors, + operations.length.toString(), + ...calldatas, + ], + }; + } + + public getManageProofs(tree: Array, leafHash: string): string[] { + const proof: string[] = []; + let currentHash = leafHash; + + // Check if leaf hash exists at level 0 (leaf level) + const leafLevel = tree[0]; + if (!leafLevel.includes(currentHash)) { + throw new Error("❌ Leaf hash not found at level 0 of the Merkle tree"); + } + + // Generate proof by traversing up the tree from level 0 + for (let level = 0; level < tree.length - 1; level++) { + const layer = tree[level]; + const index = layer.indexOf(currentHash); + + if (index === -1) { + throw new Error(`❌ Hash ${currentHash} not found at level ${level}`); + } + + const siblingIndex = index % 2 === 0 ? index + 1 : index - 1; + + if (siblingIndex >= layer.length) { + throw new Error(`❌ No sibling for index ${index} at level ${level}`); + } + + const sibling = layer[siblingIndex]; + proof.push(sibling); + + // Calculate parent hash for next level - using commutative hash, order doesn't matter + currentHash = this.hashPair(currentHash, sibling); + } + + return proof; + } + + public hashPair(a: string, b: string): string { + // Use commutative Pedersen hash - sort inputs first to ensure commutativity + const aBig = BigInt(a); + const bBig = BigInt(b); + const [first, second] = aBig < bBig ? [a, b] : [b, a]; + + const result = hash.computePedersenHashOnElements([first, second]); + // Convert from hex to decimal string + return BigInt(result).toString(); + } + + // ============================================ + // Generic operations + // ============================================ + + public bringLiquidity(params: BringLiquidityParams): MerkleOperation { + const bringLiquidityLeaf = this.config.leafs.find((leaf) => + leaf.description.toLowerCase().includes("bring liquidity") + ); + + if (!bringLiquidityLeaf) { + throw new Error( + "Bring liquidity operation not found in vault configuration" + ); + } + + const proofs = this.getManageProofs( + this.config.tree, + bringLiquidityLeaf.leaf_hash + ); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: bringLiquidityLeaf.decoder_and_sanitizer, + target: bringLiquidityLeaf.target, + selector: bringLiquidityLeaf.selector, + calldata: [amountUint256.low.toString(), amountUint256.high.toString()], + }; + } + + public approve(approveParams: ApproveParams): MerkleOperation { + const approveSelector = BigInt( + selector.getSelectorFromName("approve") + ).toString(); + const approveLeaf = this.config.leafs.find( + (leaf) => + leaf.selector === approveSelector && + leaf.target === approveParams.target && + leaf.argument_addresses.includes(approveParams.spender) + ); + if (!approveLeaf) { + throw new Error("Approve operation not found in vault configuration"); + } + + const proofs = this.getManageProofs( + this.config.tree, + approveLeaf.leaf_hash + ); + const amountUint256 = uint256.bnToUint256(approveParams.amount.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: approveLeaf.decoder_and_sanitizer, + target: approveLeaf.target, + selector: approveLeaf.selector, + calldata: [ + approveParams.spender, + amountUint256.low.toString(), + amountUint256.high.toString(), + ], + }; + } + + // ============================================ + // ERC4626 operations + // ============================================ + + public deposit(params: DepositParams): MerkleOperation { + return erc4626.deposit( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public mint(params: MintParams): MerkleOperation { + return erc4626.mint(this.config, this.getManageProofs.bind(this), params); + } + + public withdraw(params: WithdrawParams): MerkleOperation { + return erc4626.withdraw( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public redeem(params: RedeemParams): MerkleOperation { + return erc4626.redeem(this.config, this.getManageProofs.bind(this), params); + } + + public requestRedeem(params: RequestRedeemParams): MerkleOperation { + return erc4626.requestRedeem( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public claimRedeem(params: ClaimRedeemParams): MerkleOperation { + return erc4626.claimRedeem( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // ============================================ + // AVNU swap + // ============================================ + + public multiRouteSwap(params: MultiRouteSwapParams): MerkleOperation { + return avnu.multiRouteSwap( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // ============================================ + // Starkgate bridge + // ============================================ + + public bridgeTokenStarkgate( + params: BridgeTokenStarkgateParams + ): MerkleOperation { + return starkgate.bridgeTokenStarkgate( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public bridgeTokenStarkgateMiddleware( + params: BridgeTokenStarkgateMiddlewareParams + ): MerkleOperation { + return starkgate.bridgeTokenStarkgateMiddleware( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // Note: claim_token_bridged_back is permissionless - call directly on middleware contract + + // ============================================ + // Hyperlane middleware bridge + // ============================================ + + public bridgeTokenHyperlaneMiddleware( + params: BridgeTokenHyperlaneMiddlewareParams + ): MerkleOperation { + return hyperlane.bridgeTokenHyperlaneMiddleware( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // Note: claim_token is permissionless - call directly on middleware contract + + // ============================================ + // CCTP middleware bridge + // ============================================ + + public bridgeTokenCctpMiddleware( + params: BridgeTokenCctpMiddlewareParams + ): MerkleOperation { + return cctp.bridgeTokenCctpMiddleware( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // Note: claim_token is permissionless - call directly on middleware contract + + // ============================================ + // LayerZero bridge + // ============================================ + + public bridgeLZ(params: BridgeLZParams): MerkleOperation { + return lz.bridgeLZ(this.config, this.getManageProofs.bind(this), params); + } + + public bridgeLZMiddleware(params: BridgeLZMiddlewareParams): MerkleOperation { + return lz.bridgeLZMiddleware( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // Note: claim_token for LZ middleware is permissionless - call directly on middleware contract + + // ============================================ + // Vesu V2 + // ============================================ + + public modifyPositionV2(params: ModifyPositionParamsV2): MerkleOperation { + return vesu.modifyPositionV2( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + // ============================================ + // Ekubo LP + // ============================================ + + public ekuboDepositLiquidity( + params: EkuboDepositLiquidityParams + ): MerkleOperation { + return ekubo.ekuboDepositLiquidity( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public ekuboWithdrawLiquidity( + params: EkuboWithdrawLiquidityParams + ): MerkleOperation { + return ekubo.ekuboWithdrawLiquidity( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public ekuboCollectFees(params: EkuboCollectFeesParams): MerkleOperation { + return ekubo.ekuboCollectFees( + this.config, + this.getManageProofs.bind(this), + params + ); + } + + public ekuboHarvest(params: EkuboHarvestParams): MerkleOperation { + return ekubo.ekuboHarvest( + this.config, + this.getManageProofs.bind(this), + params + ); + } +} diff --git a/sdk/src/curator/integrations/avnu.ts b/sdk/src/curator/integrations/avnu.ts new file mode 100644 index 00000000..c4fc7ac5 --- /dev/null +++ b/sdk/src/curator/integrations/avnu.ts @@ -0,0 +1,79 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + MultiRouteSwapParams, +} from "../types"; + +export function multiRouteSwap( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: MultiRouteSwapParams +): MerkleOperation { + const multiRouteSwapSelector = BigInt( + selector.getSelectorFromName("multi_route_swap") + ).toString(); + const swapLeaf = config.leafs.find( + (leaf) => + leaf.selector === multiRouteSwapSelector && + BigInt(leaf.target) === BigInt(params.target) && + leaf.argument_addresses.length === 3 && + BigInt(leaf.argument_addresses[0]) === + BigInt(params.sell_token_address) && + BigInt(leaf.argument_addresses[1]) === BigInt(params.buy_token_address) && + BigInt(leaf.argument_addresses[2]) === + BigInt(config.metadata.vault_allocator) + ); + + if (!swapLeaf) { + throw new Error( + "Multi route swap operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, swapLeaf.leaf_hash); + + const sellAmountUint256 = uint256.bnToUint256( + params.sell_token_amount.toString() + ); + const buyAmountUint256 = uint256.bnToUint256( + params.buy_token_amount.toString() + ); + const buyMinAmountUint256 = uint256.bnToUint256( + params.buy_token_min_amount.toString() + ); + + // Serialize routes array + const routesCalldata: string[] = []; + routesCalldata.push(params.routes.length.toString()); + + for (const route of params.routes) { + routesCalldata.push(route.sell_token); + routesCalldata.push(route.buy_token); + routesCalldata.push(route.exchange_address); + routesCalldata.push(route.percent.toString()); + routesCalldata.push(route.additional_swap_params.length.toString()); + routesCalldata.push(...route.additional_swap_params); + } + + return { + manageProofs: proofs, + decoderAndSanitizer: swapLeaf.decoder_and_sanitizer, + target: swapLeaf.target, + selector: swapLeaf.selector, + calldata: [ + params.sell_token_address, + sellAmountUint256.low.toString(), + sellAmountUint256.high.toString(), + params.buy_token_address, + buyAmountUint256.low.toString(), + buyAmountUint256.high.toString(), + buyMinAmountUint256.low.toString(), + buyMinAmountUint256.high.toString(), + params.beneficiary, + params.integrator_fee_amount_bps.toString(), + params.integrator_fee_recipient, + ...routesCalldata, + ], + }; +} diff --git a/sdk/src/curator/integrations/cctp.ts b/sdk/src/curator/integrations/cctp.ts new file mode 100644 index 00000000..c0053c7c --- /dev/null +++ b/sdk/src/curator/integrations/cctp.ts @@ -0,0 +1,81 @@ +import { uint256 } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + BridgeTokenCctpMiddlewareParams, +} from "../types"; + +// Note: claim_token is permissionless - call directly on middleware contract + +export function bridgeTokenCctpMiddleware( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeTokenCctpMiddlewareParams +): MerkleOperation { + // Convert mint_recipient string to u256 + const mintRecipientUint256 = uint256.bnToUint256( + params.mint_recipient.toString() + ); + // Convert destination_caller string to u256 + const destinationCallerUint256 = uint256.bnToUint256( + params.destination_caller.toString() + ); + + // Convert hex to decimal strings for comparison + const mintRecipientLowDecimal = BigInt(mintRecipientUint256.low).toString(); + const mintRecipientHighDecimal = BigInt(mintRecipientUint256.high).toString(); + const destinationCallerLowDecimal = BigInt( + destinationCallerUint256.low + ).toString(); + const destinationCallerHighDecimal = BigInt( + destinationCallerUint256.high + ).toString(); + + // Find the CCTP deposit_for_burn leaf by matching argument addresses + const cctpLeaf = config.leafs.find( + (leaf) => + leaf.argument_addresses.length >= 7 && + BigInt(leaf.argument_addresses[0]) === + BigInt(params.destination_domain) && + BigInt(leaf.argument_addresses[1]) === BigInt(mintRecipientLowDecimal) && + BigInt(leaf.argument_addresses[2]) === BigInt(mintRecipientHighDecimal) && + BigInt(leaf.argument_addresses[3]) === BigInt(params.burn_token) && + BigInt(leaf.argument_addresses[4]) === BigInt(params.token_to_claim) && + BigInt(leaf.argument_addresses[5]) === + BigInt(destinationCallerLowDecimal) && + BigInt(leaf.argument_addresses[6]) === + BigInt(destinationCallerHighDecimal) + ); + + if (!cctpLeaf) { + throw new Error( + "CCTP deposit_for_burn operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, cctpLeaf.leaf_hash); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + const maxFeeUint256 = uint256.bnToUint256(params.max_fee.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: cctpLeaf.decoder_and_sanitizer, + target: cctpLeaf.target, + selector: cctpLeaf.selector, + calldata: [ + amountUint256.low.toString(), + amountUint256.high.toString(), + params.destination_domain.toString(), + mintRecipientLowDecimal, + mintRecipientHighDecimal, + params.burn_token, + params.token_to_claim, + destinationCallerLowDecimal, + destinationCallerHighDecimal, + maxFeeUint256.low.toString(), + maxFeeUint256.high.toString(), + params.min_finality_threshold.toString(), + ], + }; +} diff --git a/sdk/src/curator/integrations/ekubo.ts b/sdk/src/curator/integrations/ekubo.ts new file mode 100644 index 00000000..0bf53af9 --- /dev/null +++ b/sdk/src/curator/integrations/ekubo.ts @@ -0,0 +1,153 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + EkuboDepositLiquidityParams, + EkuboWithdrawLiquidityParams, + EkuboCollectFeesParams, + EkuboHarvestParams, +} from "../types"; + +export function ekuboDepositLiquidity( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: EkuboDepositLiquidityParams +): MerkleOperation { + const depositLiquiditySelector = BigInt( + selector.getSelectorFromName("deposit_liquidity") + ).toString(); + const depositLiquidityLeaf = config.leafs.find( + (leaf) => + leaf.selector === depositLiquiditySelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!depositLiquidityLeaf) { + throw new Error( + "Ekubo deposit liquidity operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, depositLiquidityLeaf.leaf_hash); + + const amount0Uint256 = uint256.bnToUint256(params.amount0.toString()); + const amount1Uint256 = uint256.bnToUint256(params.amount1.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: depositLiquidityLeaf.decoder_and_sanitizer, + target: depositLiquidityLeaf.target, + selector: depositLiquidityLeaf.selector, + calldata: [ + amount0Uint256.low.toString(), + amount0Uint256.high.toString(), + amount1Uint256.low.toString(), + amount1Uint256.high.toString(), + ], + }; +} + +export function ekuboWithdrawLiquidity( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: EkuboWithdrawLiquidityParams +): MerkleOperation { + const withdrawLiquiditySelector = BigInt( + selector.getSelectorFromName("withdraw_liquidity") + ).toString(); + const withdrawLiquidityLeaf = config.leafs.find( + (leaf) => + leaf.selector === withdrawLiquiditySelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!withdrawLiquidityLeaf) { + throw new Error( + "Ekubo withdraw liquidity operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, withdrawLiquidityLeaf.leaf_hash); + + const ratioWadUint256 = uint256.bnToUint256(params.ratioWad.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: withdrawLiquidityLeaf.decoder_and_sanitizer, + target: withdrawLiquidityLeaf.target, + selector: withdrawLiquidityLeaf.selector, + calldata: [ + ratioWadUint256.low.toString(), + ratioWadUint256.high.toString(), + params.minToken0.toString(), + params.minToken1.toString(), + ], + }; +} + +export function ekuboCollectFees( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: EkuboCollectFeesParams +): MerkleOperation { + const collectFeesSelector = BigInt( + selector.getSelectorFromName("collect_fees") + ).toString(); + const collectFeesLeaf = config.leafs.find( + (leaf) => + leaf.selector === collectFeesSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!collectFeesLeaf) { + throw new Error( + "Ekubo collect fees operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, collectFeesLeaf.leaf_hash); + + return { + manageProofs: proofs, + decoderAndSanitizer: collectFeesLeaf.decoder_and_sanitizer, + target: collectFeesLeaf.target, + selector: collectFeesLeaf.selector, + calldata: [], + }; +} + +export function ekuboHarvest( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: EkuboHarvestParams +): MerkleOperation { + const harvestSelector = BigInt( + selector.getSelectorFromName("harvest") + ).toString(); + const harvestLeaf = config.leafs.find( + (leaf) => + leaf.selector === harvestSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!harvestLeaf) { + throw new Error("Ekubo harvest operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, harvestLeaf.leaf_hash); + + return { + manageProofs: proofs, + decoderAndSanitizer: harvestLeaf.decoder_and_sanitizer, + target: harvestLeaf.target, + selector: harvestLeaf.selector, + calldata: [ + params.rewardContract, + params.id.toString(), + params.amount.toString(), + params.proof.length.toString(), + ...params.proof, + params.rewardToken, + ], + }; +} diff --git a/sdk/src/curator/integrations/erc4626.ts b/sdk/src/curator/integrations/erc4626.ts new file mode 100644 index 00000000..f4ef92ef --- /dev/null +++ b/sdk/src/curator/integrations/erc4626.ts @@ -0,0 +1,214 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + DepositParams, + MintParams, + WithdrawParams, + RedeemParams, + RequestRedeemParams, + ClaimRedeemParams, +} from "../types"; + +export function deposit( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: DepositParams +): MerkleOperation { + const depositSelector = BigInt( + selector.getSelectorFromName("deposit") + ).toString(); + const depositLeaf = config.leafs.find( + (leaf) => + leaf.selector === depositSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!depositLeaf) { + throw new Error("Deposit operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, depositLeaf.leaf_hash); + const assetsUint256 = uint256.bnToUint256(params.assets.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: depositLeaf.decoder_and_sanitizer, + target: depositLeaf.target, + selector: depositLeaf.selector, + calldata: [ + assetsUint256.low.toString(), + assetsUint256.high.toString(), + params.receiver, + ], + }; +} + +export function mint( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: MintParams +): MerkleOperation { + const mintSelector = BigInt(selector.getSelectorFromName("mint")).toString(); + const mintLeaf = config.leafs.find( + (leaf) => + leaf.selector === mintSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!mintLeaf) { + throw new Error("Mint operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, mintLeaf.leaf_hash); + const sharesUint256 = uint256.bnToUint256(params.shares.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: mintLeaf.decoder_and_sanitizer, + target: mintLeaf.target, + selector: mintLeaf.selector, + calldata: [ + sharesUint256.low.toString(), + sharesUint256.high.toString(), + params.receiver, + ], + }; +} + +export function withdraw( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: WithdrawParams +): MerkleOperation { + const withdrawSelector = BigInt( + selector.getSelectorFromName("withdraw") + ).toString(); + const withdrawLeaf = config.leafs.find( + (leaf) => + leaf.selector === withdrawSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!withdrawLeaf) { + throw new Error("Withdraw operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, withdrawLeaf.leaf_hash); + const assetsUint256 = uint256.bnToUint256(params.assets.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: withdrawLeaf.decoder_and_sanitizer, + target: withdrawLeaf.target, + selector: withdrawLeaf.selector, + calldata: [ + assetsUint256.low.toString(), + assetsUint256.high.toString(), + params.receiver, + params.owner, + ], + }; +} + +export function redeem( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: RedeemParams +): MerkleOperation { + const redeemSelector = BigInt( + selector.getSelectorFromName("redeem") + ).toString(); + const redeemLeaf = config.leafs.find( + (leaf) => + leaf.selector === redeemSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!redeemLeaf) { + throw new Error("Redeem operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, redeemLeaf.leaf_hash); + const sharesUint256 = uint256.bnToUint256(params.shares.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: redeemLeaf.decoder_and_sanitizer, + target: redeemLeaf.target, + selector: redeemLeaf.selector, + calldata: [ + sharesUint256.low.toString(), + sharesUint256.high.toString(), + params.receiver, + params.owner, + ], + }; +} + +export function requestRedeem( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: RequestRedeemParams +): MerkleOperation { + const requestRedeemSelector = BigInt( + selector.getSelectorFromName("request_redeem") + ).toString(); + const requestRedeemLeaf = config.leafs.find( + (leaf) => + leaf.selector === requestRedeemSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!requestRedeemLeaf) { + throw new Error( + "Request redeem operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, requestRedeemLeaf.leaf_hash); + const sharesUint256 = uint256.bnToUint256(params.shares.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: requestRedeemLeaf.decoder_and_sanitizer, + target: requestRedeemLeaf.target, + selector: requestRedeemLeaf.selector, + calldata: [ + sharesUint256.low.toString(), + sharesUint256.high.toString(), + params.receiver, + params.owner, + ], + }; +} + +export function claimRedeem( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: ClaimRedeemParams +): MerkleOperation { + const claimRedeemSelector = BigInt( + selector.getSelectorFromName("claim_redeem") + ).toString(); + const claimRedeemLeaf = config.leafs.find( + (leaf) => + leaf.selector === claimRedeemSelector && + BigInt(leaf.target) === BigInt(params.target) + ); + + if (!claimRedeemLeaf) { + throw new Error("Claim redeem operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, claimRedeemLeaf.leaf_hash); + const idUint256 = uint256.bnToUint256(params.id.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: claimRedeemLeaf.decoder_and_sanitizer, + target: claimRedeemLeaf.target, + selector: claimRedeemLeaf.selector, + calldata: [idUint256.low.toString(), idUint256.high.toString()], + }; +} diff --git a/sdk/src/curator/integrations/hyperlane.ts b/sdk/src/curator/integrations/hyperlane.ts new file mode 100644 index 00000000..27dd5e16 --- /dev/null +++ b/sdk/src/curator/integrations/hyperlane.ts @@ -0,0 +1,62 @@ +import { uint256 } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + BridgeTokenHyperlaneMiddlewareParams, +} from "../types"; + +// Note: claim_token is permissionless - call directly on middleware contract + +export function bridgeTokenHyperlaneMiddleware( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeTokenHyperlaneMiddlewareParams +): MerkleOperation { + // Convert recipient string to u256 + const recipientUint256 = uint256.bnToUint256(params.recipient.toString()); + + // Convert hex to decimal strings for comparison + const recipientLowDecimal = BigInt(recipientUint256.low).toString(); + const recipientHighDecimal = BigInt(recipientUint256.high).toString(); + + // Find the Hyperlane bridge leaf by matching argument addresses + const hyperlaneLeaf = config.leafs.find( + (leaf) => + leaf.argument_addresses.length >= 5 && + BigInt(leaf.argument_addresses[0]) === BigInt(params.source_token) && + BigInt(leaf.argument_addresses[1]) === BigInt(params.destination_token) && + BigInt(leaf.argument_addresses[2]) === + BigInt(params.destination_domain) && + BigInt(leaf.argument_addresses[3]) === BigInt(recipientLowDecimal) && + BigInt(leaf.argument_addresses[4]) === BigInt(recipientHighDecimal) + ); + + if (!hyperlaneLeaf) { + throw new Error( + "Hyperlane bridge operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, hyperlaneLeaf.leaf_hash); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + const feeUint256 = uint256.bnToUint256(params.strk_fee.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: hyperlaneLeaf.decoder_and_sanitizer, + target: hyperlaneLeaf.target, + selector: hyperlaneLeaf.selector, + calldata: [ + params.source_token, + params.destination_token, + params.destination_domain.toString(), + recipientLowDecimal, + recipientHighDecimal, + amountUint256.low.toString(), + amountUint256.high.toString(), + feeUint256.low.toString(), + feeUint256.high.toString(), + ], + }; +} diff --git a/sdk/src/curator/integrations/lz.ts b/sdk/src/curator/integrations/lz.ts new file mode 100644 index 00000000..4ee29ab9 --- /dev/null +++ b/sdk/src/curator/integrations/lz.ts @@ -0,0 +1,180 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + BridgeLZParams, + BridgeLZMiddlewareParams, +} from "../types"; + +// Helper to serialize ByteArray (empty or provided hex string) +function serializeByteArray(hexString?: string): string[] { + if (!hexString || hexString === "" || hexString === "0x") { + // Empty ByteArray: data_len=0, pending_word=0, pending_word_len=0 + return ["0", "0", "0"]; + } + // For non-empty ByteArray, we'd need proper serialization + // For now, assume empty - most LZ operations use empty extra_options/compose_msg/oft_cmd + return ["0", "0", "0"]; +} + +export function bridgeLZ( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeLZParams +): MerkleOperation { + // Convert to string to u256 + const toUint256 = uint256.bnToUint256(params.to.toString()); + const toLowDecimal = BigInt(toUint256.low).toString(); + const toHighDecimal = BigInt(toUint256.high).toString(); + + // vault_allocator is used as refund_address + const vaultAllocator = config.metadata.vault_allocator; + + const sendSelector = BigInt(selector.getSelectorFromName("send")).toString(); + + // Find the LZ send leaf by matching argument addresses + // Args: dst_eid, to (u256), refund_address + const lzLeaf = config.leafs.find( + (leaf) => + leaf.selector === sendSelector && + leaf.target === params.oft && + leaf.argument_addresses.length >= 4 && + BigInt(leaf.argument_addresses[0]) === BigInt(params.dst_eid) && + BigInt(leaf.argument_addresses[1]) === BigInt(toLowDecimal) && + BigInt(leaf.argument_addresses[2]) === BigInt(toHighDecimal) && + BigInt(leaf.argument_addresses[3]) === BigInt(vaultAllocator) + ); + + if (!lzLeaf) { + throw new Error("LZ send operation not found in vault configuration"); + } + + const proofs = getManageProofs(config.tree, lzLeaf.leaf_hash); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + const minAmountUint256 = uint256.bnToUint256(params.min_amount.toString()); + const nativeFeeUint256 = uint256.bnToUint256(params.native_fee.toString()); + const lzTokenFeeUint256 = uint256.bnToUint256( + (params.lz_token_fee || "0").toString() + ); + + // Build SendParam calldata + const sendParamCalldata = [ + params.dst_eid.toString(), // dst_eid + toLowDecimal, // to.low + toHighDecimal, // to.high + amountUint256.low.toString(), // amount_ld.low + amountUint256.high.toString(), // amount_ld.high + minAmountUint256.low.toString(), // min_amount_ld.low + minAmountUint256.high.toString(), // min_amount_ld.high + ...serializeByteArray(params.extra_options), // extra_options + ...serializeByteArray(params.compose_msg), // compose_msg + ...serializeByteArray(params.oft_cmd), // oft_cmd + ]; + + // Build MessagingFee calldata + const feeCalldata = [ + nativeFeeUint256.low.toString(), // native_fee.low + nativeFeeUint256.high.toString(), // native_fee.high + lzTokenFeeUint256.low.toString(), // lz_token_fee.low + lzTokenFeeUint256.high.toString(), // lz_token_fee.high + ]; + + return { + manageProofs: proofs, + decoderAndSanitizer: lzLeaf.decoder_and_sanitizer, + target: lzLeaf.target, + selector: lzLeaf.selector, + calldata: [ + ...sendParamCalldata, + ...feeCalldata, + vaultAllocator, // refund_address + ], + }; +} + +export function bridgeLZMiddleware( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeLZMiddlewareParams +): MerkleOperation { + // Convert to string to u256 + const toUint256 = uint256.bnToUint256(params.to.toString()); + const toLowDecimal = BigInt(toUint256.low).toString(); + const toHighDecimal = BigInt(toUint256.high).toString(); + + // vault_allocator is used as refund_address + const vaultAllocator = config.metadata.vault_allocator; + + const sendSelector = BigInt(selector.getSelectorFromName("send")).toString(); + + // Find the LZ middleware send leaf by matching argument addresses + // Args: oft, underlying_token, token_to_claim, dst_eid, to (u256), refund_address + const lzLeaf = config.leafs.find( + (leaf) => + leaf.selector === sendSelector && + leaf.argument_addresses.length >= 7 && + BigInt(leaf.argument_addresses[0]) === BigInt(params.oft) && + BigInt(leaf.argument_addresses[1]) === BigInt(params.underlying_token) && + BigInt(leaf.argument_addresses[2]) === BigInt(params.token_to_claim) && + BigInt(leaf.argument_addresses[3]) === BigInt(params.dst_eid) && + BigInt(leaf.argument_addresses[4]) === BigInt(toLowDecimal) && + BigInt(leaf.argument_addresses[5]) === BigInt(toHighDecimal) && + BigInt(leaf.argument_addresses[6]) === BigInt(vaultAllocator) + ); + + if (!lzLeaf) { + throw new Error( + "LZ middleware send operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, lzLeaf.leaf_hash); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + const minAmountUint256 = uint256.bnToUint256(params.min_amount.toString()); + const nativeFeeUint256 = uint256.bnToUint256(params.native_fee.toString()); + const lzTokenFeeUint256 = uint256.bnToUint256( + (params.lz_token_fee || "0").toString() + ); + + // Build SendParam calldata + const sendParamCalldata = [ + params.dst_eid.toString(), // dst_eid + toLowDecimal, // to.low + toHighDecimal, // to.high + amountUint256.low.toString(), // amount_ld.low + amountUint256.high.toString(), // amount_ld.high + minAmountUint256.low.toString(), // min_amount_ld.low + minAmountUint256.high.toString(), // min_amount_ld.high + ...serializeByteArray(params.extra_options), // extra_options + ...serializeByteArray(params.compose_msg), // compose_msg + ...serializeByteArray(params.oft_cmd), // oft_cmd + ]; + + // Build MessagingFee calldata + const feeCalldata = [ + nativeFeeUint256.low.toString(), // native_fee.low + nativeFeeUint256.high.toString(), // native_fee.high + lzTokenFeeUint256.low.toString(), // lz_token_fee.low + lzTokenFeeUint256.high.toString(), // lz_token_fee.high + ]; + + return { + manageProofs: proofs, + decoderAndSanitizer: lzLeaf.decoder_and_sanitizer, + target: lzLeaf.target, + selector: lzLeaf.selector, + calldata: [ + params.oft, // oft + params.underlying_token, // underlying_token + params.token_to_claim, // token_to_claim + ...sendParamCalldata, + ...feeCalldata, + vaultAllocator, // refund_address + ], + }; +} + +// Note: claim_token is permissionless - anyone can call it directly on the middleware +// No SDK function needed as it doesn't require merkle verification diff --git a/sdk/src/curator/integrations/starkgate.ts b/sdk/src/curator/integrations/starkgate.ts new file mode 100644 index 00000000..a73c27fd --- /dev/null +++ b/sdk/src/curator/integrations/starkgate.ts @@ -0,0 +1,111 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + BridgeTokenStarkgateParams, + BridgeTokenStarkgateMiddlewareParams, +} from "../types"; + +// Note: claim_token_bridged_back is permissionless - call directly on middleware contract + +export function bridgeTokenStarkgate( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeTokenStarkgateParams +): MerkleOperation { + const initiateTokenWithdrawSelector = BigInt( + selector.getSelectorFromName("initiate_token_withdraw") + ).toString(); + const initiateTokenWithdrawLeaf = config.leafs.find( + (leaf) => + leaf.selector === initiateTokenWithdrawSelector && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.l1_token) + ) && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.l1_recipient) + ) + ); + + if (!initiateTokenWithdrawLeaf) { + throw new Error( + "Initiate token withdraw operation not found in vault configuration" + ); + } + + const proofs = getManageProofs( + config.tree, + initiateTokenWithdrawLeaf.leaf_hash + ); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: initiateTokenWithdrawLeaf.decoder_and_sanitizer, + target: initiateTokenWithdrawLeaf.target, + selector: initiateTokenWithdrawLeaf.selector, + calldata: [ + params.l1_token, + params.l1_recipient, + amountUint256.low.toString(), + amountUint256.high.toString(), + ], + }; +} + +export function bridgeTokenStarkgateMiddleware( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: BridgeTokenStarkgateMiddlewareParams +): MerkleOperation { + const initiateTokenWithdrawSelector = BigInt( + selector.getSelectorFromName("initiate_token_withdraw") + ).toString(); + + // Find the leaf that matches the middleware interface (has starkgate_token_bridge in argument_addresses) + const initiateTokenWithdrawLeaf = config.leafs.find( + (leaf) => + leaf.selector === initiateTokenWithdrawSelector && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.starkgate_token_bridge) + ) && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.l1_token) + ) && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.l1_recipient) + ) && + leaf.argument_addresses.some( + (addr) => BigInt(addr) === BigInt(params.token_to_claim) + ) + ); + + if (!initiateTokenWithdrawLeaf) { + throw new Error( + "Initiate token withdraw (middleware) operation not found in vault configuration" + ); + } + + const proofs = getManageProofs( + config.tree, + initiateTokenWithdrawLeaf.leaf_hash + ); + + const amountUint256 = uint256.bnToUint256(params.amount.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: initiateTokenWithdrawLeaf.decoder_and_sanitizer, + target: initiateTokenWithdrawLeaf.target, + selector: initiateTokenWithdrawLeaf.selector, + calldata: [ + params.starkgate_token_bridge, + params.l1_token, + params.l1_recipient, + amountUint256.low.toString(), + amountUint256.high.toString(), + params.token_to_claim, + ], + }; +} diff --git a/sdk/src/curator/integrations/vesu.ts b/sdk/src/curator/integrations/vesu.ts new file mode 100644 index 00000000..aa403d62 --- /dev/null +++ b/sdk/src/curator/integrations/vesu.ts @@ -0,0 +1,59 @@ +import { uint256, selector } from "starknet"; +import { + VaultConfigData, + MerkleOperation, + ModifyPositionParamsV2, +} from "../types"; + +export function modifyPositionV2( + config: VaultConfigData, + getManageProofs: (tree: Array, leafHash: string) => string[], + params: ModifyPositionParamsV2 +): MerkleOperation { + const modifyPositionSelector = BigInt( + selector.getSelectorFromName("modify_position") + ).toString(); + const modifyPositionLeaf = config.leafs.find( + (leaf) => + BigInt(leaf.target) === BigInt(params.target) && + BigInt(leaf.selector) === BigInt(modifyPositionSelector) && + leaf.argument_addresses.length === 3 && + BigInt(leaf.argument_addresses[0]) === BigInt(params.collateral_asset) && + BigInt(leaf.argument_addresses[1]) === BigInt(params.debt_asset) && + BigInt(leaf.argument_addresses[2]) === BigInt(params.user) + ); + + if (!modifyPositionLeaf) { + throw new Error( + "Modify position V2 operation not found in vault configuration" + ); + } + + const proofs = getManageProofs(config.tree, modifyPositionLeaf.leaf_hash); + + // Serialize ModifyPositionParamsV2 according to Cairo implementation + const collateralAbsUint256 = uint256.bnToUint256( + params.collateral.value.abs.toString() + ); + const debtAbsUint256 = uint256.bnToUint256(params.debt.value.abs.toString()); + + return { + manageProofs: proofs, + decoderAndSanitizer: modifyPositionLeaf.decoder_and_sanitizer, + target: modifyPositionLeaf.target, + selector: modifyPositionLeaf.selector, + calldata: [ + params.collateral_asset, + params.debt_asset, + params.user, + params.collateral.denomination === "Native" ? "0" : "1", + collateralAbsUint256.low.toString(), + collateralAbsUint256.high.toString(), + params.collateral.value.is_negative ? "1" : "0", + params.debt.denomination === "Native" ? "0" : "1", + debtAbsUint256.low.toString(), + debtAbsUint256.high.toString(), + params.debt.value.is_negative ? "1" : "0", + ], + }; +} diff --git a/sdk/src/curator/types.ts b/sdk/src/curator/types.ts new file mode 100644 index 00000000..6fb88770 --- /dev/null +++ b/sdk/src/curator/types.ts @@ -0,0 +1,232 @@ +import { BigNumberish } from "starknet"; + +export interface VaultConfigData { + metadata: { + vault: string; + underlying_asset: string; + vault_allocator: string; + manager: string; + root: string; + tree_capacity: number; + leaf_used: number; + }; + leafs: Array<{ + decoder_and_sanitizer: string; + target: string; + selector: string; + argument_addresses: string[]; + description: string; + leaf_index: number; + leaf_hash: string; + }>; + tree: Array; +} + +export interface MerkleOperation { + manageProofs: string[]; + decoderAndSanitizer: string; + target: string; + selector: string; + calldata: string[]; +} + +export interface BringLiquidityParams { + amount: BigNumberish; +} + +export interface ApproveParams { + target: string; + spender: string; + amount: BigNumberish; +} + +// ERC4626 operations +export interface DepositParams { + target: string; + assets: BigNumberish; + receiver: string; +} + +export interface MintParams { + target: string; + shares: BigNumberish; + receiver: string; +} + +export interface WithdrawParams { + target: string; + assets: BigNumberish; + receiver: string; + owner: string; +} + +export interface RedeemParams { + target: string; + shares: BigNumberish; + receiver: string; + owner: string; +} + +// AVNU swap +export interface Route { + sell_token: string; + buy_token: string; + exchange_address: string; + percent: BigNumberish; + additional_swap_params: string[]; +} + +export interface MultiRouteSwapParamsInput { + target: string; + sell_token_address: string; + sell_token_amount: BigNumberish; + buy_token_address: string; + buy_token_amount: BigNumberish; + buy_token_min_amount: BigNumberish; + integrator_fee_amount_bps: BigNumberish; + integrator_fee_recipient: string; + routes: Route[]; +} + +export interface MultiRouteSwapParams extends MultiRouteSwapParamsInput { + beneficiary: string; +} + +// Async redemption +export interface RequestRedeemParams { + target: string; + shares: BigNumberish; + receiver: string; + owner: string; +} + +export interface ClaimRedeemParams { + target: string; + id: BigNumberish; +} + +// Starkgate bridge +export interface BridgeTokenStarkgateParams { + l1_token: string; + l1_recipient: string; + amount: BigNumberish; +} + +// Starkgate middleware bridge +export interface BridgeTokenStarkgateMiddlewareParams { + starkgate_token_bridge: string; + l1_token: string; + l1_recipient: string; + amount: BigNumberish; + token_to_claim: string; +} + +// Note: claim_token_bridged_back is permissionless - no type needed + +// Hyperlane middleware bridge +export interface BridgeTokenHyperlaneMiddlewareParams { + source_token: string; + destination_token: string; + amount: BigNumberish; + destination_domain: BigNumberish; + recipient: string; + strk_fee: BigNumberish; +} + +// Note: claim_token for Hyperlane middleware is permissionless - no type needed + +// CCTP middleware bridge +export interface BridgeTokenCctpMiddlewareParams { + burn_token: string; + token_to_claim: string; + amount: BigNumberish; + destination_domain: BigNumberish; + mint_recipient: string; + destination_caller: string; + max_fee: BigNumberish; + min_finality_threshold: BigNumberish; +} + +// Note: claim_token for CCTP middleware is permissionless - no type needed + +// LayerZero direct OFT +export interface BridgeLZParams { + oft: string; + dst_eid: BigNumberish; + to: string; // u256 recipient address + amount: BigNumberish; + min_amount: BigNumberish; + native_fee: BigNumberish; // STRK fee + lz_token_fee?: BigNumberish; // Optional, defaults to 0 + extra_options?: string; // Optional ByteArray hex + compose_msg?: string; // Optional ByteArray hex + oft_cmd?: string; // Optional ByteArray hex +} + +// LayerZero middleware bridge +export interface BridgeLZMiddlewareParams { + oft: string; + underlying_token: string; + token_to_claim: string; + dst_eid: BigNumberish; + to: string; // u256 recipient address + amount: BigNumberish; + min_amount: BigNumberish; + native_fee: BigNumberish; // STRK fee + lz_token_fee?: BigNumberish; // Optional, defaults to 0 + extra_options?: string; // Optional ByteArray hex + compose_msg?: string; // Optional ByteArray hex + oft_cmd?: string; // Optional ByteArray hex +} + +// Note: claim_token for LZ middleware is permissionless - no type needed + +// Vesu V2 +export interface i257 { + abs: BigNumberish; + is_negative: boolean; +} + +export interface AmountV2 { + denomination: "Native" | "Assets"; + value: i257; +} + +export interface ModifyPositionParamsV2Input { + target: string; + collateral_asset: string; + debt_asset: string; + collateral: AmountV2; + debt: AmountV2; +} + +export interface ModifyPositionParamsV2 extends ModifyPositionParamsV2Input { + user: string; +} + +// Ekubo +export interface EkuboDepositLiquidityParams { + target: string; + amount0: BigNumberish; + amount1: BigNumberish; +} + +export interface EkuboWithdrawLiquidityParams { + target: string; + ratioWad: BigNumberish; + minToken0: BigNumberish; + minToken1: BigNumberish; +} + +export interface EkuboCollectFeesParams { + target: string; +} + +export interface EkuboHarvestParams { + target: string; + rewardContract: string; + id: BigNumberish; + amount: BigNumberish; + proof: string[]; + rewardToken: string; +} diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 3c737a92..a733e545 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -10,6 +10,12 @@ export type { MintParams, RequestRedeemParams, ClaimRedeemParams, + BridgeTokenStarkgateParams, + ClaimTokenStarkgateParams, + BridgeTokenHyperlaneParams, + ClaimTokenHyperlaneParams, + BridgeTokenCctpParams, + ClaimTokenCctpParams, VaultState, FeesConfig, ReportParams, diff --git a/sdk/src/types/index.ts b/sdk/src/types/index.ts index 8733e2fe..bc7f4219 100644 --- a/sdk/src/types/index.ts +++ b/sdk/src/types/index.ts @@ -26,6 +26,46 @@ export interface ClaimRedeemParams { id: BigNumberish; } +export interface BridgeTokenStarkgateParams { + l1_token: string; + l1_recipient: string; + amount: BigNumberish; +} + +export interface ClaimTokenStarkgateParams {} + +export interface BridgeTokenHyperlaneParams { + source_token: string; + destination_token: string; + amount: BigNumberish; + destination_domain: BigNumberish; + recipient: string; + strk_fee: BigNumberish; +} + +export interface ClaimTokenHyperlaneParams { + token_to_bridge: string; + token_to_claim: string; + destination_domain: BigNumberish; +} + +export interface BridgeTokenCctpParams { + burn_token: string; + token_to_claim: string; + amount: BigNumberish; + destination_domain: BigNumberish; + mint_recipient: string; + destination_caller: string; + max_fee: BigNumberish; + min_finality_threshold: BigNumberish; +} + +export interface ClaimTokenCctpParams { + burn_token: string; + token_to_claim: string; + destination_domain: BigNumberish; +} + export interface VaultState { epoch: bigint; handledEpochLen: bigint; diff --git a/sdk/src/utils/calldata.ts b/sdk/src/utils/calldata.ts index 77cfac2d..9a3e2f72 100644 --- a/sdk/src/utils/calldata.ts +++ b/sdk/src/utils/calldata.ts @@ -223,4 +223,34 @@ export class CalldataBuilder { calldata, }; } + + /** + * Format calldata array for block explorer (Voyager, Starkscan, etc.) + * Converts all values to hex format with one value per line, no commas or quotes + * + * @param calldata - Array of calldata values (can be strings, numbers, or BigNumberish) + * @returns Formatted string ready to paste into explorer + * + * @example + * const calldata = ["1", "255", "0x123"]; + * const formatted = CalldataBuilder.formatCalldataForExplorer(calldata); + * // Returns: + * // 0x1 + * // 0xff + * // 0x123 + */ + static formatCalldataForExplorer(calldata: (string | number | BigNumberish)[]): string { + return calldata + .map((item) => { + try { + // Convert to BigInt and then to hex + const num = BigInt(item.toString()); + return '0x' + num.toString(16); + } catch { + // If conversion fails, return as-is + return item.toString(); + } + }) + .join('\n'); + } } \ No newline at end of file diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json index 8702215c..1e00b982 100644 --- a/sdk/tsconfig.json +++ b/sdk/tsconfig.json @@ -5,7 +5,7 @@ "module": "commonjs", "declaration": true, "outDir": "./dist", - "rootDir": "./src", + "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -15,7 +15,8 @@ "allowSyntheticDefaultImports": true }, "include": [ - "src/**/*" + "src/**/*", + "examples/**/*" ], "exclude": [ "node_modules", diff --git a/sdk/yarn.lock b/sdk/yarn.lock new file mode 100644 index 00000000..0daef4af --- /dev/null +++ b/sdk/yarn.lock @@ -0,0 +1,2967 @@ +# 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.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" + 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.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz" + 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.npmjs.org/@babel/core/-/core-7.28.4.tgz" + 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.npmjs.org/@babel/generator/-/generator-7.28.3.tgz" + 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.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz" + 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.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" + 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.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz" + 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.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz" + 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.npmjs.org/@babel/parser/-/parser-7.28.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + 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.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + 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.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz" + 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.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz" + 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.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + 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.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + 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.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + 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.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + 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.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz" + 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.npmjs.org/@babel/template/-/template-7.27.2.tgz" + 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.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz" + 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.npmjs.org/@babel/types/-/types-7.28.4.tgz" + 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.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + 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.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz" + 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.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" + 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.npmjs.org/@eslint/js/-/js-8.57.1.tgz" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz" + 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.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + 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.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz" + 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.npmjs.org/@jest/core/-/core-29.7.0.tgz" + 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.npmjs.org/@jest/environment/-/environment-29.7.0.tgz" + 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.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz" + 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.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz" + 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.npmjs.org/@jest/globals/-/globals-29.7.0.tgz" + 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.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz" + 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.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" + 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.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz" + 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.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz" + 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.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz" + 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.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" + 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.npmjs.org/@jest/types/-/types-29.6.3.tgz" + 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.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + 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.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + 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.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + 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.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + 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.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + 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.npmjs.org/@noble/curves/-/curves-1.7.0.tgz" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + +"@noble/hashes@1.6.0", "@noble/hashes@~1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + 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.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + 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.npmjs.org/@scure/base/-/base-1.2.1.tgz" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + +"@scure/starknet@1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@scure/starknet/-/starknet-1.1.0.tgz" + 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.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" + 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.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz" + integrity sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w== + +"@starknet-io/starknet-types-08@npm:@starknet-io/types-js@~0.8.4": + version "0.8.4" + resolved "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.8.4.tgz" + integrity sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" + 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.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz" + 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.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz" + 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.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" + 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.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" + 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.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.0.0": + version "29.5.14" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" + 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.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*", "@types/node@^20.0.0": + version "20.19.17" + resolved "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz" + integrity sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ== + dependencies: + undici-types "~6.21.0" + +"@types/semver@^7.5.0": + version "7.7.1" + resolved "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.0.0": + version "6.21.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz" + 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.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz" + 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.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +abi-wan-kanabi@2.2.4: + version "2.2.4" + resolved "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-2.2.4.tgz" + 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.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" + 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.npmjs.org/acorn/-/acorn-8.15.0.tgz" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + 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.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + 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.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansicolors@^0.3.2, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + 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.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" + 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.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + 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.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz" + 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.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz" + 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.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz" + 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.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.8.3: + version "2.8.5" + resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.5.tgz" + integrity sha512-TiU4qUT9jdCuh4aVOG7H1QozyeI2sZRqoRPdqBIaslfNt4WUSanRBueAwl2x5jt4rXBMim3lIN2x6yT8PDi24Q== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" + 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.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.26.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz" + 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.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + 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.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001741: + version "1.0.30001743" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz" + integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + 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.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + 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.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + 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.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" + 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.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" + 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.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.7.0" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz" + integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^17.2.2: + version "17.2.2" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz" + integrity sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q== + +electron-to-chromium@^1.5.218: + version "1.5.221" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.221.tgz" + integrity sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz" + 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.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" + 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.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.0.0: + version "8.57.1" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" + 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.npmjs.org/espree/-/espree-9.6.1.tgz" + 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.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + 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.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + 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.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" + 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.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" + 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.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + 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.npmjs.org/find-up/-/find-up-4.1.0.tgz" + 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.npmjs.org/find-up/-/find-up-5.0.0.tgz" + 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.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz" + 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.npmjs.org/flatted/-/flatted-3.3.3.tgz" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" + 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.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + 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.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + 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.npmjs.org/glob/-/glob-7.2.3.tgz" + 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.npmjs.org/globals/-/globals-13.24.0.tgz" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + 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.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" + 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.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" + 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.npmjs.org/import-local/-/import-local-3.2.0.tgz" + 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.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + 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.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" + 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.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz" + 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.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" + 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.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + 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.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz" + 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.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz" + 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.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz" + 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.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz" + 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.npmjs.org/jest-config/-/jest-config-29.7.0.tgz" + 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.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz" + 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.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz" + 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.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz" + 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.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz" + 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.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz" + 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.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz" + 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.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz" + 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.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz" + 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.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz" + 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.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" + 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.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz" + 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.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz" + 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.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz" + 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.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" + 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.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz" + 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.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz" + 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.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz" + 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.npmjs.org/jest/-/jest-29.7.0.tgz" + 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.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + 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.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz" + 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.npmjs.org/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + 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.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lossless-json@^4.0.1: + version "4.2.0" + resolved "https://registry.npmjs.org/lossless-json/-/lossless-json-4.2.0.tgz" + integrity sha512-bsHH3x+7acZfqokfn9Ks/ej96yF/z6oGGw1aBmXesq4r3fAjhdG4uYuqzDgZMk5g1CZUd5w3kwwIp9K1LOYUiA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" + 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.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + 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.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" + 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.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" + 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.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + 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.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + 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.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + 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.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + 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.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + 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.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + 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.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + 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.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" + 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.npmjs.org/prompts/-/prompts-2.4.2.tgz" + 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.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + 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.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" + 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.npmjs.org/reusify/-/reusify-1.1.0.tgz" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + 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.npmjs.org/semver/-/semver-6.3.1.tgz" + 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.npmjs.org/semver/-/semver-7.7.2.tgz" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + 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.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +starknet@7.6.4: + version "7.6.4" + resolved "https://registry.npmjs.org/starknet/-/starknet-7.6.4.tgz" + 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.npmjs.org/string-length/-/string-length-4.0.2.tgz" + 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.npmjs.org/string-width/-/string-width-4.2.3.tgz" + 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.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + 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.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + 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.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + 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.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.0.0: + version "29.4.3" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.3.tgz" + 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.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" + 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.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typescript@^5.0.0: + version "5.9.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz" + 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.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" + 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.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + 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.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + 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.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + 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.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==