From 6c0169039989c82e11899fca4d49ee5fbbbf0125 Mon Sep 17 00:00:00 2001
From: pisuthd <pisuth.dae@gmail.com>
Date: Mon, 3 Jun 2024 13:05:27 +0900
Subject: [PATCH 1/4] init aptos project

---
 package.json                      |  3 ++-
 packages/aptos/.gitignore         |  2 ++
 packages/aptos/Move.toml          | 17 +++++++++++++++++
 packages/aptos/package.json       | 14 ++++++++++++++
 packages/aptos/sources/vault.move | 11 +++++++++++
 5 files changed, 46 insertions(+), 1 deletion(-)
 create mode 100644 packages/aptos/.gitignore
 create mode 100644 packages/aptos/Move.toml
 create mode 100644 packages/aptos/package.json
 create mode 100644 packages/aptos/sources/vault.move

diff --git a/package.json b/package.json
index fa8b324..06dea15 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
     "bootstrap": "npm install; lerna bootstrap;",
     "start": "run-p --print-label package:*",
     "build": "lerna run --parallel build",
-    "test": "lerna run test --concurrency 1 --stream --scope sui"
+    "test": "lerna run test --concurrency 1 --stream --scope sui",
+    "test-aptos": "lerna run test --concurrency 1 --stream --scope aptos"
   },
   "devDependencies": {
     "lerna": "^6.6.2",
diff --git a/packages/aptos/.gitignore b/packages/aptos/.gitignore
new file mode 100644
index 0000000..243f545
--- /dev/null
+++ b/packages/aptos/.gitignore
@@ -0,0 +1,2 @@
+.aptos/
+build/
\ No newline at end of file
diff --git a/packages/aptos/Move.toml b/packages/aptos/Move.toml
new file mode 100644
index 0000000..27e36fe
--- /dev/null
+++ b/packages/aptos/Move.toml
@@ -0,0 +1,17 @@
+[package]
+name = "legato"
+version = "0.1.0"
+authors = []
+
+[addresses]
+legato_addr = "f586bbdd187df4e3bd7cd5b8af8a7018d8b8b5f3ec24a95cd5ac79d94ebca981"
+aptos_framework = "0x1"
+
+[dev-addresses]
+
+[dependencies.AptosFramework]
+git = "https://github.com/aptos-labs/aptos-core.git"
+rev = "mainnet"
+subdir = "aptos-move/framework/aptos-framework"
+
+[dev-dependencies]
diff --git a/packages/aptos/package.json b/packages/aptos/package.json
new file mode 100644
index 0000000..f763524
--- /dev/null
+++ b/packages/aptos/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "aptos",
+  "version": "0.1.0",
+  "description": "",
+  "main": "index.js",
+  "directories": {
+    "test": "tests"
+  },
+  "scripts": {
+    "test": "aptos move test"
+  },
+  "author": "",
+  "license": "MIT"
+}
diff --git a/packages/aptos/sources/vault.move b/packages/aptos/sources/vault.move
new file mode 100644
index 0000000..a47d4b6
--- /dev/null
+++ b/packages/aptos/sources/vault.move
@@ -0,0 +1,11 @@
+// Copyright (c) Tamago Blockchain Labs, Inc.
+// SPDX-License-Identifier: MIT
+
+// A timelock vault to convert APT into future value including yield tokenization for general purposes. 
+// Each vault maintains its own tokens and adheres to a quarterly expiration schedule.
+
+module legato_addr::vault {
+
+    
+
+}
\ No newline at end of file

From 767d4addf6bf31c0ef98eb8e2db026c6a1f80bdf Mon Sep 17 00:00:00 2001
From: pisuthd <pisuth.dae@gmail.com>
Date: Mon, 3 Jun 2024 19:15:40 +0900
Subject: [PATCH 2/4] add base fa and mock tokens

---
 .../sources/utils/base_fungible_asset.move    | 185 ++++++++++++++++++
 .../aptos/sources/utils/mock_legato..move     |  70 +++++++
 packages/aptos/sources/utils/mock_usdc.move   |  47 +++++
 3 files changed, 302 insertions(+)
 create mode 100644 packages/aptos/sources/utils/base_fungible_asset.move
 create mode 100644 packages/aptos/sources/utils/mock_legato..move
 create mode 100644 packages/aptos/sources/utils/mock_usdc.move

diff --git a/packages/aptos/sources/utils/base_fungible_asset.move b/packages/aptos/sources/utils/base_fungible_asset.move
new file mode 100644
index 0000000..9162ced
--- /dev/null
+++ b/packages/aptos/sources/utils/base_fungible_asset.move
@@ -0,0 +1,185 @@
+// A base fungible asset module that allows anyone to mint and burn coins
+
+module legato_addr::base_fungible_asset {
+
+    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleStore, FungibleAsset};
+    use aptos_framework::object::{Self, Object, ConstructorRef};
+    use aptos_framework::primary_fungible_store;
+    use std::error;
+    use std::signer;
+    use std::string::{String, utf8};
+    use std::option;
+    use std::vector;
+
+    /// Only fungible asset metadata owner can make changes.
+    const ERR_NOT_OWNER: u64 = 1;
+    /// The length of ref_flags is not 3.
+    const ERR_INVALID_REF_FLAGS_LENGTH: u64 = 2;
+    /// The lengths of two vector do not equal.
+    const ERR_VECTORS_LENGTH_MISMATCH: u64 = 3;
+    /// MintRef error.
+    const ERR_MINT_REF: u64 = 4;
+    /// TransferRef error.
+    const ERR_TRANSFER_REF: u64 = 5;
+    /// BurnRef error.
+    const ERR_BURN_REF: u64 = 6;
+
+    #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
+    /// Hold refs to control the minting, transfer and burning of fungible assets.
+    struct ManagingRefs has key {
+        mint_ref: MintRef,
+        transfer_ref: TransferRef,
+        burn_ref: BurnRef,
+    }
+
+    /// Initialize metadata object
+    public fun initialize(
+        constructor_ref: &ConstructorRef,
+        maximum_supply: u128,
+        name: String,
+        symbol: String,
+        decimals: u8,
+        icon_uri: String,
+        project_uri: String
+    ) {
+        let supply = if (maximum_supply != 0) {
+            option::some(maximum_supply)
+        } else {
+            option::none()
+        };
+        primary_fungible_store::create_primary_store_enabled_fungible_asset(
+            constructor_ref,
+            supply,
+            name,
+            symbol,
+            decimals,
+            icon_uri,
+            project_uri,
+        );
+        let metadata_object_signer = object::generate_signer(constructor_ref);
+        move_to(
+            &metadata_object_signer,
+            ManagingRefs { 
+                mint_ref: fungible_asset::generate_mint_ref(constructor_ref), 
+                transfer_ref: fungible_asset::generate_transfer_ref(constructor_ref), 
+                burn_ref: fungible_asset::generate_burn_ref(constructor_ref)
+            }
+        )
+
+    }
+
+    /// Mint to the primary fungible stores of the accounts with amounts of FAs.
+    public entry fun mint_to_primary_stores( 
+        asset: Object<Metadata>,
+        to: vector<address>,
+        amounts: vector<u64>
+    ) acquires ManagingRefs {
+        let receiver_primary_stores = vector::map(
+            to,
+            |addr| primary_fungible_store::ensure_primary_store_exists(addr, asset)
+        );
+        mint(  asset, receiver_primary_stores, amounts);
+    }
+
+    /// Mint to multiple fungible stores with amounts of FAs.
+    public entry fun mint( 
+        asset: Object<Metadata>,
+        stores: vector<Object<FungibleStore>>,
+        amounts: vector<u64>,
+    ) acquires ManagingRefs {
+        let length = vector::length(&stores);
+        assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH));
+        let mint_ref = borrow_mint_ref(asset);
+        let i = 0;
+        while (i < length) {
+            fungible_asset::mint_to(mint_ref, *vector::borrow(&stores, i), *vector::borrow(&amounts, i));
+            i = i + 1;
+        }
+    }
+
+    /// Burn fungible assets from the primary stores of accounts.
+    public entry fun burn_from_primary_stores( 
+        asset: Object<Metadata>,
+        from: vector<address>,
+        amounts: vector<u64>
+    ) acquires ManagingRefs {
+        let primary_stores = vector::map(
+            from,
+            |addr| primary_fungible_store::primary_store(addr, asset)
+        );
+        burn( asset, primary_stores, amounts);
+    }
+ 
+
+    /// Burn fungible assets from fungible stores.
+    public entry fun burn( 
+        asset: Object<Metadata>,
+        stores: vector<Object<FungibleStore>>,
+        amounts: vector<u64>
+    ) acquires ManagingRefs {
+        let length = vector::length(&stores);
+        assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH));
+        let burn_ref = borrow_burn_ref(  asset);
+        let i = 0;
+        while (i < length) {
+            fungible_asset::burn_from(burn_ref, *vector::borrow(&stores, i), *vector::borrow(&amounts, i));
+            i = i + 1;
+        };
+    }
+
+    inline fun borrow_mint_ref(
+        asset: Object<Metadata>,
+    ): &MintRef acquires ManagingRefs {
+        let refs = borrow_global<ManagingRefs>(object::object_address(&asset));
+        &refs.mint_ref
+    }
+
+    inline fun borrow_burn_ref(
+        asset: Object<Metadata>,
+    ): &BurnRef acquires ManagingRefs {
+        let refs = borrow_global<ManagingRefs>(object::object_address(&asset));
+        &refs.burn_ref
+    }
+
+    #[test_only]
+    use aptos_framework::object::object_from_constructor_ref;
+    
+    #[test_only]
+    fun create_test_mfa(creator: &signer): Object<Metadata> {
+        let constructor_ref = &object::create_named_object(creator, b"APT");
+        initialize(
+            constructor_ref,
+            0,
+            utf8(b"Aptos Token"), /* name */
+            utf8(b"APT"), /* symbol */
+            8, /* decimals */
+            utf8(b"http://example.com/favicon.ico"), /* icon */
+            utf8(b"http://example.com"), /* project */
+        );
+        object_from_constructor_ref<Metadata>(constructor_ref)
+    }
+
+    #[test(creator = @legato_addr, alice = @0xface)]
+    fun test_basic_flow(
+        creator: &signer,
+        alice: &signer
+    ) acquires ManagingRefs {
+        let metadata = create_test_mfa(creator);
+        let creator_address = signer::address_of(creator);
+        let alice_address = signer::address_of(alice);
+
+        mint_to_primary_stores(  metadata, vector[creator_address, alice_address], vector[100, 50]);
+        assert!(primary_fungible_store::balance(creator_address, metadata) == 100, 1);
+        assert!(primary_fungible_store::balance(alice_address, metadata) == 50, 2);
+
+        primary_fungible_store::transfer(creator, metadata, alice_address, 5);
+
+        assert!(primary_fungible_store::balance(creator_address, metadata) == 95, 3);
+        assert!(primary_fungible_store::balance(alice_address, metadata) == 55, 4);
+
+        burn_from_primary_stores(  metadata, vector[creator_address, alice_address], vector[95, 55]);
+        assert!(primary_fungible_store::balance(creator_address, metadata) == 0, 5);
+        assert!(primary_fungible_store::balance(alice_address, metadata) == 0, 6);
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_legato..move b/packages/aptos/sources/utils/mock_legato..move
new file mode 100644
index 0000000..dc308d9
--- /dev/null
+++ b/packages/aptos/sources/utils/mock_legato..move
@@ -0,0 +1,70 @@
+
+// Mock Legato coins in fungible asset
+
+module legato_addr::mock_legato {
+    
+    use aptos_framework::object;
+    use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset};
+    use aptos_framework::object::Object;
+    use legato_addr::base_fungible_asset;
+    use std::string::utf8;
+
+    const ASSET_SYMBOL: vector<u8> = b"LEGATO";
+
+    /// Initialize metadata object and store the refs.
+    fun init_module(admin: &signer) {
+        let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
+        base_fungible_asset::initialize(
+            constructor_ref,
+            0, /* maximum_supply. 0 means no maximum */
+            utf8(b"Mock Legato Tokens"), /* name */
+            utf8(ASSET_SYMBOL), /* symbol */
+            8, /* decimals */
+            utf8(b"http://example.com/favicon.ico"), /* icon */
+            utf8(b"http://example.com"), /* project */
+        );
+    }
+
+    #[view]
+    /// Return the address of the metadata that's created when this module is deployed.
+    public fun get_metadata(): Object<Metadata> {
+        let metadata_address = object::create_object_address(&@legato_addr, ASSET_SYMBOL);
+        object::address_to_object<Metadata>(metadata_address)
+    }
+
+    /// Mint as the owner of metadata object.
+    public entry fun mint(   to: address, amount: u64) {
+        base_fungible_asset::mint_to_primary_stores( get_metadata(), vector[to], vector[amount]);
+    }
+
+
+    /// Burn fungible assets as the owner of metadata object.
+    public entry fun burn( from: address, amount: u64) {
+        base_fungible_asset::burn_from_primary_stores(  get_metadata(), vector[from], vector[amount]);
+    }
+
+    #[test_only]
+    use aptos_framework::primary_fungible_store;
+    #[test_only]
+    use std::signer;
+
+    #[test(creator = @legato_addr, alice = @0xface)]
+    fun test_basic_flow(creator: &signer, alice: &signer) {
+        init_module(creator);
+        let creator_address = signer::address_of(creator);
+        let alice_address = signer::address_of(alice); 
+
+        mint( creator_address, 100);
+        let metadata = get_metadata();
+        assert!(primary_fungible_store::balance(creator_address, metadata) == 100, 1); 
+
+        primary_fungible_store::transfer(creator, metadata, alice_address, 5);
+        assert!(primary_fungible_store::balance(alice_address, metadata) == 5, 2); 
+
+        burn( creator_address, 95);
+        burn( alice_address, 5);
+        assert!(primary_fungible_store::balance(creator_address, metadata) == 0, 3); 
+        assert!(primary_fungible_store::balance(alice_address, metadata) == 0, 4); 
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_usdc.move b/packages/aptos/sources/utils/mock_usdc.move
new file mode 100644
index 0000000..7e7cdb6
--- /dev/null
+++ b/packages/aptos/sources/utils/mock_usdc.move
@@ -0,0 +1,47 @@
+
+
+// Mock USDC coins in fungible asset
+
+module legato_addr::mock_usdc {
+
+    use aptos_framework::object;
+    use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset};
+    use aptos_framework::object::Object;
+    use legato_addr::base_fungible_asset;
+    use std::string::utf8;
+
+    const ASSET_SYMBOL: vector<u8> = b"USDC";
+
+    /// Initialize metadata object and store the refs.
+    fun init_module(admin: &signer) {
+        let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
+        base_fungible_asset::initialize(
+            constructor_ref,
+            0, /* maximum_supply. 0 means no maximum */
+            utf8(b"Mock USDC Tokens"), /* name */
+            utf8(ASSET_SYMBOL), /* symbol */
+            6, /* decimals */
+            utf8(b"http://example.com/favicon.ico"), /* icon */
+            utf8(b"http://example.com"), /* project */
+        );
+    }
+
+    #[view]
+    /// Return the address of the metadata that's created when this module is deployed.
+    public fun get_metadata(): Object<Metadata> {
+        let metadata_address = object::create_object_address(&@legato_addr, ASSET_SYMBOL);
+        object::address_to_object<Metadata>(metadata_address)
+    }
+
+    /// Mint as the owner of metadata object.
+    public entry fun mint(   to: address, amount: u64) {
+        base_fungible_asset::mint_to_primary_stores( get_metadata(), vector[to], vector[amount]);
+    }
+
+
+    /// Burn fungible assets as the owner of metadata object.
+    public entry fun burn( from: address, amount: u64) {
+        base_fungible_asset::burn_from_primary_stores(  get_metadata(), vector[from], vector[amount]);
+    }
+
+}
\ No newline at end of file

From 63fdecb082f93c22896ac39b1ec2282840a4d40c Mon Sep 17 00:00:00 2001
From: pisuthd <pisuth.dae@gmail.com>
Date: Wed, 5 Jun 2024 21:30:09 +0900
Subject: [PATCH 3/4] add legacy mock coins / base contracts for amm & lbp

---
 .../sources/utils/base_fungible_asset.move    |   2 +-
 packages/aptos/sources/utils/mock_legato.move |  74 ++++
 ...{mock_legato..move => mock_legato_fa.move} |  11 +-
 packages/aptos/sources/utils/mock_usdc.move   |  80 ++--
 .../aptos/sources/utils/mock_usdc_fa.move     |  54 +++
 packages/aptos/sources/utils/stable_math.move |  66 +++
 .../aptos/sources/utils/vault_token_name.move |  44 ++
 packages/aptos/sources/utils/weight_math.move | 385 ++++++++++++++++++
 packages/sui/sources/amm.move                 |   4 +-
 9 files changed, 682 insertions(+), 38 deletions(-)
 create mode 100644 packages/aptos/sources/utils/mock_legato.move
 rename packages/aptos/sources/utils/{mock_legato..move => mock_legato_fa.move} (90%)
 create mode 100644 packages/aptos/sources/utils/mock_usdc_fa.move
 create mode 100644 packages/aptos/sources/utils/stable_math.move
 create mode 100644 packages/aptos/sources/utils/vault_token_name.move
 create mode 100644 packages/aptos/sources/utils/weight_math.move

diff --git a/packages/aptos/sources/utils/base_fungible_asset.move b/packages/aptos/sources/utils/base_fungible_asset.move
index 9162ced..cc245ce 100644
--- a/packages/aptos/sources/utils/base_fungible_asset.move
+++ b/packages/aptos/sources/utils/base_fungible_asset.move
@@ -2,7 +2,7 @@
 
 module legato_addr::base_fungible_asset {
 
-    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleStore, FungibleAsset};
+    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleStore};
     use aptos_framework::object::{Self, Object, ConstructorRef};
     use aptos_framework::primary_fungible_store;
     use std::error;
diff --git a/packages/aptos/sources/utils/mock_legato.move b/packages/aptos/sources/utils/mock_legato.move
new file mode 100644
index 0000000..a07227c
--- /dev/null
+++ b/packages/aptos/sources/utils/mock_legato.move
@@ -0,0 +1,74 @@
+
+// Mock Legato coins 
+
+module legato_addr::mock_legato {
+
+    use std::signer;
+    use std::string::{Self, String };  
+
+    use aptos_framework::account;
+    use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability}; 
+
+    const TOKEN_NAME: vector<u8> = b"Legato Token";
+
+    struct LEGATO_TOKEN has drop, store {}
+
+    struct MockManager has key {
+        mint_cap: MintCapability<LEGATO_TOKEN>,
+        burn_cap: BurnCapability<LEGATO_TOKEN>
+    }
+
+    fun init_module(sender: &signer) {
+
+        let token_name = string::utf8(b"Legato Token");
+        let token_symbol = string::utf8(b"LEGATO");
+
+        let (burn_cap, freeze_cap, mint_cap) = coin::initialize<LEGATO_TOKEN>(sender, token_name, token_symbol, 8, true);
+        coin::destroy_freeze_cap(freeze_cap);
+
+        move_to( sender, MockManager { 
+            mint_cap,
+            burn_cap
+        });
+    }
+
+    public entry fun mint(sender: &signer , amount: u64) acquires MockManager {
+        let mock_manager = borrow_global_mut<MockManager>(@legato_addr);
+        let coins = coin::mint<LEGATO_TOKEN>(amount, &mock_manager.mint_cap );
+
+        let sender_address = signer::address_of(sender);
+
+        if (!coin::is_account_registered<LEGATO_TOKEN>(sender_address)) {
+            coin::register<LEGATO_TOKEN>(sender);
+        };
+        coin::deposit(sender_address, coins);
+    }
+
+    public entry fun burn(sender: &signer, amount: u64)  acquires MockManager {
+        let mock_manager = borrow_global_mut<MockManager>(@legato_addr);
+
+        let burn_coin = coin::withdraw<LEGATO_TOKEN>(sender, amount);
+        coin::burn(burn_coin, &mock_manager.burn_cap);
+    }
+
+    // ======== Test-related Functions =========
+
+    #[test_only] 
+    public fun init_module_for_testing(sender: &signer) {
+        init_module(sender)
+    }
+
+    // #[test(deployer = @legato_addr, alice = @0x1234)]
+    // public fun test_basic_flow(deployer: &signer, alice: &signer) {
+    //     init_module(deployer);
+
+    //     let alice_address = signer::address_of(alice); 
+
+    //     account::create_account_for_test(alice_address);   
+
+    //     mint(alice, 1000);
+    //     assert!(coin::balance<LEGATO_TOKEN>(alice_address) == 1000 , 0);
+
+    // }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_legato..move b/packages/aptos/sources/utils/mock_legato_fa.move
similarity index 90%
rename from packages/aptos/sources/utils/mock_legato..move
rename to packages/aptos/sources/utils/mock_legato_fa.move
index dc308d9..84f42fc 100644
--- a/packages/aptos/sources/utils/mock_legato..move
+++ b/packages/aptos/sources/utils/mock_legato_fa.move
@@ -1,10 +1,10 @@
 
 // Mock Legato coins in fungible asset
 
-module legato_addr::mock_legato {
+module legato_addr::mock_legato_fa {
     
     use aptos_framework::object;
-    use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset};
+    use aptos_framework::fungible_asset::{  Metadata};
     use aptos_framework::object::Object;
     use legato_addr::base_fungible_asset;
     use std::string::utf8;
@@ -67,4 +67,11 @@ module legato_addr::mock_legato {
         assert!(primary_fungible_store::balance(alice_address, metadata) == 0, 4); 
     }
 
+    // ======== Test-related Functions =========
+
+    #[test_only] 
+    public fun init_module_for_testing(deployer: &signer) {
+        init_module(deployer)
+    }
+
 }
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_usdc.move b/packages/aptos/sources/utils/mock_usdc.move
index 7e7cdb6..49d6711 100644
--- a/packages/aptos/sources/utils/mock_usdc.move
+++ b/packages/aptos/sources/utils/mock_usdc.move
@@ -1,47 +1,61 @@
 
-
-// Mock USDC coins in fungible asset
+// Mock Legato coins 
 
 module legato_addr::mock_usdc {
 
-    use aptos_framework::object;
-    use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset};
-    use aptos_framework::object::Object;
-    use legato_addr::base_fungible_asset;
-    use std::string::utf8;
-
-    const ASSET_SYMBOL: vector<u8> = b"USDC";
-
-    /// Initialize metadata object and store the refs.
-    fun init_module(admin: &signer) {
-        let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
-        base_fungible_asset::initialize(
-            constructor_ref,
-            0, /* maximum_supply. 0 means no maximum */
-            utf8(b"Mock USDC Tokens"), /* name */
-            utf8(ASSET_SYMBOL), /* symbol */
-            6, /* decimals */
-            utf8(b"http://example.com/favicon.ico"), /* icon */
-            utf8(b"http://example.com"), /* project */
-        );
+    use std::signer;
+    use std::string::{Self, String };  
+
+    use aptos_framework::account;
+    use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability}; 
+
+    const TOKEN_NAME: vector<u8> = b"USDC Token";
+
+    struct USDC_TOKEN has drop, store {}
+
+    struct MockManager has key {
+        mint_cap: MintCapability<USDC_TOKEN>,
+        burn_cap: BurnCapability<USDC_TOKEN>
     }
 
-    #[view]
-    /// Return the address of the metadata that's created when this module is deployed.
-    public fun get_metadata(): Object<Metadata> {
-        let metadata_address = object::create_object_address(&@legato_addr, ASSET_SYMBOL);
-        object::address_to_object<Metadata>(metadata_address)
+    fun init_module(sender: &signer) {
+
+        let token_name = string::utf8(b"USDC Token");
+        let token_symbol = string::utf8(b"USDC");
+
+        let (burn_cap, freeze_cap, mint_cap) = coin::initialize<USDC_TOKEN>(sender, token_name, token_symbol, 6, true);
+        coin::destroy_freeze_cap(freeze_cap);
+
+        move_to( sender, MockManager { 
+            mint_cap,
+            burn_cap
+        });
+    }
+
+    public entry fun mint(sender: &signer , amount: u64) acquires MockManager {
+        let mock_manager = borrow_global_mut<MockManager>(@legato_addr);
+        let coins = coin::mint<USDC_TOKEN>(amount, &mock_manager.mint_cap );
+
+        let sender_address = signer::address_of(sender);
+
+        if (!coin::is_account_registered<USDC_TOKEN>(sender_address)) {
+            coin::register<USDC_TOKEN>(sender);
+        };
+        coin::deposit(sender_address, coins);
     }
 
-    /// Mint as the owner of metadata object.
-    public entry fun mint(   to: address, amount: u64) {
-        base_fungible_asset::mint_to_primary_stores( get_metadata(), vector[to], vector[amount]);
+    public entry fun burn(sender: &signer, amount: u64)  acquires MockManager {
+        let mock_manager = borrow_global_mut<MockManager>(@legato_addr);
+
+        let burn_coin = coin::withdraw<USDC_TOKEN>(sender, amount);
+        coin::burn(burn_coin, &mock_manager.burn_cap);
     }
 
+    // ======== Test-related Functions =========
 
-    /// Burn fungible assets as the owner of metadata object.
-    public entry fun burn( from: address, amount: u64) {
-        base_fungible_asset::burn_from_primary_stores(  get_metadata(), vector[from], vector[amount]);
+    #[test_only] 
+    public fun init_module_for_testing(sender: &signer) {
+        init_module(sender)
     }
 
 }
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_usdc_fa.move b/packages/aptos/sources/utils/mock_usdc_fa.move
new file mode 100644
index 0000000..0060e19
--- /dev/null
+++ b/packages/aptos/sources/utils/mock_usdc_fa.move
@@ -0,0 +1,54 @@
+
+
+// Mock USDC coins in fungible asset
+
+module legato_addr::mock_usdc_fa {
+
+    use aptos_framework::object;
+    use aptos_framework::fungible_asset::{Self, Metadata};
+    use aptos_framework::object::Object;
+    use legato_addr::base_fungible_asset;
+    use std::string::utf8;
+
+    const ASSET_SYMBOL: vector<u8> = b"USDC";
+
+    /// Initialize metadata object and store the refs.
+    fun init_module(admin: &signer) {
+        let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
+        base_fungible_asset::initialize(
+            constructor_ref,
+            0, /* maximum_supply. 0 means no maximum */
+            utf8(b"Mock USDC Tokens"), /* name */
+            utf8(ASSET_SYMBOL), /* symbol */
+            6, /* decimals */
+            utf8(b"http://example.com/favicon.ico"), /* icon */
+            utf8(b"http://example.com"), /* project */
+        );
+    }
+
+    #[view]
+    /// Return the address of the metadata that's created when this module is deployed.
+    public fun get_metadata(): Object<Metadata> {
+        let metadata_address = object::create_object_address(&@legato_addr, ASSET_SYMBOL);
+        object::address_to_object<Metadata>(metadata_address)
+    }
+
+    /// Mint as the owner of metadata object.
+    public entry fun mint(   to: address, amount: u64) {
+        base_fungible_asset::mint_to_primary_stores( get_metadata(), vector[to], vector[amount]);
+    }
+
+
+    /// Burn fungible assets as the owner of metadata object.
+    public entry fun burn( from: address, amount: u64) {
+        base_fungible_asset::burn_from_primary_stores(  get_metadata(), vector[from], vector[amount]);
+    }
+
+    // ======== Test-related Functions =========
+
+    #[test_only] 
+    public fun init_module_for_testing(deployer: &signer) {
+        init_module(deployer)
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/stable_math.move b/packages/aptos/sources/utils/stable_math.move
new file mode 100644
index 0000000..536f7a2
--- /dev/null
+++ b/packages/aptos/sources/utils/stable_math.move
@@ -0,0 +1,66 @@
+/// Necessary library for setting up and calculating a stablecoin pool.
+/// The pool must have equal weights and relies on the formula k = x^3 * y + x * y^3, borrowed from
+/// https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/move-examples/swap/sources/liquidity_pool.move
+
+
+module legato_addr::stable_math {
+
+    use aptos_std::math128;
+
+    // Computes initial LP amount using the formula - total_share = sqrt( amount_x * amount_y )
+    public fun compute_initial_lp( 
+        amount_x: u64,
+        amount_y: u64
+    ): u64 {
+        ( math128::sqrt(( amount_x  as u128) * ( amount_y as u128) ) as u64 )
+    }
+
+    // Calculate the output amount using k = x^3 * y + x * y^3
+    public fun get_amount_out(
+        amount_in: u64,
+        reserve_in: u64, 
+        reserve_out: u64, 
+    ) : u64 {
+        let k = calculate_constant_k((reserve_in as u256), (reserve_out as u256));
+        (((reserve_out as u256) - get_y(((amount_in + reserve_in) as u256), k, (reserve_out as u256))) as u64)
+    }
+
+    fun calculate_constant_k(r1: u256, r2: u256): u256 {
+        (r1 * r1 * r1 * r2 + r2 * r2 * r2 * r1)
+    }
+
+    fun get_y(x0: u256, xy: u256, y: u256): u256 {
+        let i = 0;
+        while (i < 255) {
+            let y_prev = y;
+            let k = f(x0, y);
+            if (k < xy) {
+                let dy = (xy - k) / d(x0, y);
+                y = y + dy;
+            } else {
+                let dy = (k - xy) / d(x0, y);
+                y = y - dy;
+            };
+            if (y > y_prev) {
+                if (y - y_prev <= 1) {
+                    return y
+                }
+            } else {
+                if (y_prev - y <= 1) {
+                    return y
+                }
+            };
+            i = i + 1;
+        };
+        y
+    }
+
+    fun f(x0: u256, y: u256): u256 {
+        x0 * (y * y * y) + (x0 * x0 * x0) * y
+    }
+
+    fun d(x0: u256, y: u256): u256 {
+        3 * x0 * (y * y) + (x0 * x0 * x0)
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/vault_token_name.move b/packages/aptos/sources/utils/vault_token_name.move
new file mode 100644
index 0000000..cd6eb11
--- /dev/null
+++ b/packages/aptos/sources/utils/vault_token_name.move
@@ -0,0 +1,44 @@
+
+
+
+// Each vault has it owned tokens and follows a quarterly expiration
+
+module legato_addr::vault_token_name {
+
+    // Define structs for March, June, September, and December from 2024 to 2030
+    struct MAR_2024 has drop {}  // March 2024
+    struct JUN_2024 has drop {}  // June 2024
+    struct SEP_2024 has drop {}  // September 2024
+    struct DEC_2024 has drop {}  // December 2024
+    
+    struct MAR_2025 has drop {}  // March 2025
+    struct JUN_2025 has drop {}  // June 2025
+    struct SEP_2025 has drop {}  // September 2025
+    struct DEC_2025 has drop {}  // December 2025
+    
+    struct MAR_2026 has drop {}  // March 2026
+    struct JUN_2026 has drop {}  // June 2026
+    struct SEP_2026 has drop {}  // September 2026
+    struct DEC_2026 has drop {}  // December 2026
+    
+    struct MAR_2027 has drop {}  // March 2027
+    struct JUN_2027 has drop {}  // June 2027
+    struct SEP_2027 has drop {}  // September 2027
+    struct DEC_2027 has drop {}  // December 2027
+    
+    struct MAR_2028 has drop {}  // March 2028
+    struct JUN_2028 has drop {}  // June 2028
+    struct SEP_2028 has drop {}  // September 2028
+    struct DEC_2028 has drop {}  // December 2028
+    
+    struct MAR_2029 has drop {}  // March 2029
+    struct JUN_2029 has drop {}  // June 2029
+    struct SEP_2029 has drop {}  // September 2029
+    struct DEC_2029 has drop {}  // December 2029
+    
+    struct MAR_2030 has drop {}  // March 2030
+    struct JUN_2030 has drop {}  // June 2030
+    struct SEP_2030 has drop {}  // September 2030
+    struct DEC_2030 has drop {}  // December 2030
+    
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/weight_math.move b/packages/aptos/sources/utils/weight_math.move
new file mode 100644
index 0000000..a781902
--- /dev/null
+++ b/packages/aptos/sources/utils/weight_math.move
@@ -0,0 +1,385 @@
+
+
+// Module for weighted math operations, the formulas are borrowed from Balancer V2 Lite project.
+// https://github.com/icmoore/balancer-v2-lite
+
+
+module legato_addr::weighted_math {
+  
+    use aptos_std::fixed_point64::{Self, FixedPoint64}; 
+    use aptos_std::math_fixed64; 
+
+    const WEIGHT_SCALE: u64 = 10000;
+
+    const LOG_2_E: u128 = 26613026195707766742;
+
+    // Maximum values for u64 and u128
+    const MAX_U64: u128 = 18446744073709551615;
+    const MAX_U128: u256 = 340282366920938463463374607431768211455;
+
+    const ERR_INCORRECT_SWAP: u64 = 1; 
+
+    // Computes the optimal value for adding liquidity
+    public fun compute_optimal_value(
+        amount_out: u64,
+        reserve_in: u64, 
+        weight_in: u64,
+        reserve_out: u64,
+        weight_out: u64,
+    ) : u64 {
+        
+        get_amount_in(
+            amount_out,
+            reserve_in,
+            weight_in,
+            reserve_out,
+            weight_out
+        )
+    }
+
+    // Calculate the output amount according to the pool weight
+    // - amountOut = balanceOut * (1 - ((balanceIn / (balanceIn + amountIn)) ^ (wI / wO)))
+    public fun get_amount_out(
+        amount_in: u64,
+        reserve_in: u64,
+        weight_in: u64, 
+        reserve_out: u64,
+        weight_out: u64, 
+    ) : u64 {
+
+        // Scale the amount to adjust for the provided scaling factor of the asset
+        let amount_in_after_scaled = (amount_in as u128);
+        let reserve_in_after_scaled = (reserve_in as u128);
+        let reserve_out_after_scaled  = (reserve_out as u128);
+
+        if (weight_in == weight_out) {
+            let denominator = reserve_in_after_scaled+amount_in_after_scaled; 
+            let base = fixed_point64::create_from_rational(reserve_in_after_scaled , denominator);
+            let amount_out = fixed_point64::multiply_u128( reserve_out_after_scaled, fixed_point64::sub(fixed_point64::create_from_u128(1), base ) );
+
+            (amount_out as u64)
+        }  else {
+
+            let denominator = reserve_in_after_scaled+amount_in_after_scaled; 
+            let base = fixed_point64::create_from_rational(reserve_in_after_scaled , denominator);
+            let exponent = fixed_point64::create_from_rational((weight_in as u128), (weight_out as u128));
+ 
+            let power = power(base, exponent);
+            let amount_out = fixed_point64::multiply_u128( reserve_out_after_scaled , fixed_point64::sub( fixed_point64::create_from_u128(1), power   )  );
+
+            (amount_out as u64)
+        }
+ 
+    }
+
+    // Calculate the output amount according to the pool weight 
+    // - amountIn = balanceIn * ((( balanceOut / (balanceOut - amountOut) ) ^ (wO/wI))-1)
+    public fun get_amount_in(
+        amount_out: u64,
+        reserve_in: u64,
+        weight_in: u64, 
+        reserve_out: u64,
+        weight_out: u64, 
+    ) : u64 {
+
+        let amount_out_after_scaled = (amount_out as u128);
+        let reserve_in_after_scaled = (reserve_in as u128);
+        let reserve_out_after_scaled  = (reserve_out as u128);
+
+        if (weight_in == weight_out) {
+            let denominator = reserve_out_after_scaled-amount_out_after_scaled;
+            let base = fixed_point64::create_from_rational(reserve_out_after_scaled , denominator);
+            let amount_out = fixed_point64::multiply_u128( reserve_in_after_scaled, fixed_point64::sub(base, fixed_point64::create_from_u128(1))  );
+
+            (amount_out as u64)
+        } else {
+
+            let denominator = reserve_out_after_scaled-amount_out_after_scaled;
+            let base = fixed_point64::create_from_rational(reserve_out_after_scaled , denominator);
+            let exponent = fixed_point64::create_from_rational((weight_out as u128), (weight_in as u128));
+ 
+            let power = power(base, exponent);
+            let amount_out = fixed_point64::multiply_u128( reserve_in_after_scaled, fixed_point64::sub(power, fixed_point64::create_from_u128(1))  );
+
+            (amount_out as u64)
+        }
+
+    }
+
+    // Computes initial LP amount using the formula - total_share = (amount_x^weight_x) * (amount_y^weight_y)
+    public fun compute_initial_lp(
+        weight_x: u64,
+        weight_y: u64, 
+        amount_x: u64,
+        amount_y: u64
+    ): u64 {
+
+        let amount_x_after_weighted = power( fixed_point64::create_from_u128( (amount_x as u128)), fixed_point64::create_from_rational( (weight_x as u128), (WEIGHT_SCALE as u128) ));
+        let amount_y_after_weighted = power( fixed_point64::create_from_u128( (amount_y as u128)), fixed_point64::create_from_rational( (weight_y as u128), (WEIGHT_SCALE as u128) ));
+
+        let sum = math_fixed64::mul_div( amount_x_after_weighted, amount_y_after_weighted, fixed_point64::create_from_u128(1) );
+
+        (fixed_point64::round( sum ) as u64)
+    }
+
+    // Computes LP when it's set
+    public fun compute_derive_lp(
+        amount_x: u64,
+        amount_y: u64,
+        weight_x: u64,
+        weight_y: u64,
+        reserve_x: u64,
+        reserve_y: u64,
+        lp_supply: u64
+    ): (u64) {
+
+        let for_x = token_for_lp(amount_x, reserve_x, weight_x, lp_supply );
+        let for_y = token_for_lp(amount_y, reserve_y, weight_y, lp_supply );
+
+        (fixed_point64::round( fixed_point64::add(for_x, for_y ) ) as u64)
+    }
+
+    // Computes coins to be sent out when withdrawing liquidity
+    public fun compute_withdrawn_coins(
+        lp_amount: u64,
+        lp_supply: u64,
+        reserve_x: u64,
+        reserve_y: u64,
+        weight_x: u64,
+        weight_y: u64
+    ): (u64, u64) {
+ 
+        let amount_x = lp_for_token( (lp_amount/2 as u128) , lp_supply, reserve_x, weight_x );
+        let amount_y =  lp_for_token( (lp_amount/2 as u128)  , lp_supply, reserve_y, weight_y );
+
+        ((amount_x as u64),(amount_y as u64))
+    }
+
+    // Calculates the amount of output coins to receive from a given LP amount 
+    // - output_amount = reserve * (1 - ((lp_supply - lp_amount) / lp_supply) ^ (1 / weight))
+    public fun lp_for_token(
+        lp_amount: u128,
+        lp_supply: u64, 
+        reserve: u64, 
+        weight: u64
+    ) : u64 {
+
+        let base = fixed_point64::create_from_rational( ((lp_supply-(lp_amount as u64)) as u128), (lp_supply as u128) );
+        let power = power(base, fixed_point64::create_from_rational( (WEIGHT_SCALE as u128), (weight as u128) ) );
+
+        ( (fixed_point64::multiply_u128( (reserve as u128) ,  fixed_point64::sub( fixed_point64::create_from_u128(1), power )  )) as u64)
+    }
+
+    // Calculates the amount of LP tokens to receive from a given coins.
+    // - lp_out = lp_supply * ((reserve + amount) / reserve) ^ (weight / WEIGHT_SCALE) - 1
+    public fun token_for_lp(
+        amount: u64,
+        reserve: u64,
+        weight: u64,
+        lp_supply: u64
+    ) : FixedPoint64 {
+
+        let base = fixed_point64::create_from_rational( ( (reserve+amount) as u128 ), (reserve as u128) );
+        let power = power(base,  fixed_point64::create_from_rational( (weight as u128), (WEIGHT_SCALE as u128) ) ); 
+
+        // fixed_point64::multiply_u128( (lp_supply as u128) ,  fixed_point64::sub( power,  fixed_point64::create_from_u128(1) )  )
+        math_fixed64::mul_div(  fixed_point64::create_from_u128(  (lp_supply as u128) ), fixed_point64::sub( power,  fixed_point64::create_from_u128(1) ), fixed_point64::create_from_u128(1)  )
+    } 
+
+    // Helper function to calculate the power of a FixedPoint64 number to a FixedPoint64 exponent
+    // - When `n` is > 1, it uses the formula `exp(y * ln(x))` instead of `x^y`.
+    // - When `n` is < 1, it employs the Newton-Raphson method.
+    public fun power(n: FixedPoint64, e: FixedPoint64) : FixedPoint64 {
+        // Check if the exponent is 0, return 1 if it is
+        if (fixed_point64::equal(e, fixed_point64::create_from_u128(0)) ) {
+            fixed_point64::create_from_u128(1)
+        } else if (fixed_point64::equal(e, fixed_point64::create_from_u128(1))) {
+            // If the exponent is 1, return the base value n 
+            n
+        } else if (fixed_point64::less(n, fixed_point64::create_from_u128(1))) {
+
+            // Split the exponent into integer and fractional parts
+            let integerPart = fixed_point64::floor( e );
+            let fractionalPart = fixed_point64::sub(e, fixed_point64::create_from_u128(integerPart));
+
+            // Calculate the integer power using math_fixed64 power function
+            let result = math_fixed64::pow( n, (integerPart as u64) );
+
+            if ( fixed_point64::equal( fractionalPart, fixed_point64::create_from_u128(0) ) ) {
+                // If the fractional part is zero, return the integer result
+                result
+            } else {
+                // Calculate the fractional using internal nth root function
+                let nth = math_fixed64::mul_div( fixed_point64::create_from_u128(1), fixed_point64::create_from_u128(1), fractionalPart );
+
+                let nth_rounded = fixed_point64::round(nth); 
+
+                let fractionalResult =  nth_root( n , (nth_rounded as u64) );
+                
+                // Combine the integer and fractional powers using multiplication
+                math_fixed64::mul_div( result, fractionalResult,  fixed_point64::create_from_u128(1)  )
+            }
+
+        } else {
+
+            // Calculate ln(n) times e
+            let ln_x_times_y = math_fixed64::mul_div(  e , ln(n), fixed_point64::create_from_u128(1) );
+            // Compute exp(ln(x) * y) to get the result of x^y
+            math_fixed64::exp(ln_x_times_y)
+        }
+
+    }
+
+    // Helper function to approximate the n-th root of a number using the Newton-Raphson method when x < 1.
+    public fun nth_root( x: FixedPoint64, n: u64): FixedPoint64 {
+        if ( n == 0 ) {
+            fixed_point64::create_from_u128(1)
+        } else {
+            
+            // Initialize guess 
+            let guess = fixed_point64::create_from_rational(1, 2);
+
+            // Define the epsilon value for determining convergence
+            let epsilon = fixed_point64::create_from_rational( 1, 1000 );
+
+            let delta = fixed_point64::create_from_rational( MAX_U64, 1 );
+
+            // Perform Newton-Raphson iterations until convergence
+            while ( fixed_point64::greater( delta ,  epsilon )) {
+                
+                let xn = pow_raw( guess,  n); 
+                let derivative = math_fixed64::mul_div( fixed_point64::create_from_u128( (n as u128)), pow_raw( guess,  n-1), fixed_point64::create_from_u128(1) );
+
+                if (fixed_point64::greater_or_equal(xn, x)) { 
+                    delta = math_fixed64::mul_div( fixed_point64::sub(xn, x) , fixed_point64::create_from_u128(1), derivative);
+                    guess = fixed_point64::sub(guess, delta);
+                } else {
+                    delta = math_fixed64::mul_div( fixed_point64::sub(x, xn) , fixed_point64::create_from_u128(1), derivative);
+                    guess = fixed_point64::add(guess, delta);
+                };
+            
+            };
+            // Return the final approximation of the n-th root
+            guess
+        }
+    }
+
+    // Function to calculate the power of a FixedPoint64 number
+    public fun pow_raw(x: FixedPoint64, n: u64): FixedPoint64 {
+        // Get the raw value of x as a 256-bit unsigned integer
+        let raw_value = (fixed_point64::get_raw_value(x) as u256);
+
+        let res: u256 = 1 << 64;
+
+        // Perform exponentiation using bitwise operations
+        while (n != 0) {
+            if (n & 1 != 0) {
+                res = (res * raw_value) >> 64;
+            };
+            n = n >> 1;
+            if ( raw_value <= MAX_U128 ) {
+                raw_value = (raw_value * raw_value) >> 64;
+            } else {
+                raw_value = (raw_value >> 32) * (raw_value >> 32);
+            };
+        };
+
+        fixed_point64::create_from_raw_value((res as u128))
+    }
+
+    // Calculate the natural logarithm of the input using FixedPoint64
+    public fun ln(input : FixedPoint64) : FixedPoint64 {
+        // Define the constant log_2(e)
+        let log_2_e = fixed_point64::create_from_raw_value( LOG_2_E );
+        
+        // Calculate the base-2 logarithm of the input
+        let after_log2 = (math_fixed64::log2_plus_64( input ));
+
+        let fixed_2 = fixed_point64::create_from_u128(64);
+
+        // Subtract 64 to adjust the result back 
+        let (after_subtracted, _) = absolute( after_log2, fixed_2 );
+        math_fixed64::mul_div( after_subtracted, fixed_point64::create_from_u128(1) , log_2_e)
+    }
+
+    fun absolute( a: FixedPoint64, b:  FixedPoint64 ) : (FixedPoint64, bool) {
+        if (fixed_point64::greater_or_equal(a, b)) { 
+            (fixed_point64::sub(a, b), false)
+        } else {
+            (fixed_point64::sub(b, a), true)
+        }
+    }
+
+    // calculate fee to treasury
+    public fun get_fee_to_treasury(current_fee: FixedPoint64, input: u64): (u64,u64) { 
+        let fee = (fixed_point64::multiply_u128( (input as u128) , current_fee) as u64);
+        return ( input-fee,fee)
+    }
+
+    #[test]
+    public fun test_ln() {
+
+        let output_1 = ln( fixed_point64::create_from_u128(10) ); 
+        assert!( fixed_point64::almost_equal( output_1, fixed_point64::create_from_rational( 230258509299, 100000000000  ), fixed_point64::create_from_u128(1)) , 0 ); // 2.30258509299
+
+        let output_2 = ln( fixed_point64::create_from_u128(100) ); 
+        assert!( fixed_point64::almost_equal( output_2, fixed_point64::create_from_rational( 460517018599 , 100000000000  ), fixed_point64::create_from_u128(1)) , 1 ); // 4.60517018599
+
+        let output_3 = ln( fixed_point64::create_from_u128(500) ); 
+        assert!( fixed_point64::almost_equal( output_3, fixed_point64::create_from_rational( 621460809842 , 100000000000  ), fixed_point64::create_from_u128(1)) , 2 ); // 6.21460809842
+        
+        // return absolute value when input < 1
+        let output_4 = ln( fixed_point64::create_from_rational(1, 2) ); 
+        assert!( fixed_point64::almost_equal( output_4, fixed_point64::create_from_rational( 693147181 , 1000000000  ), fixed_point64::create_from_u128(1)) , 2 ); // 0.693147181
+
+    }
+
+    #[test]
+    public fun test_power() {
+
+        // Asserts that 2^3 = 8
+        let output_1 = power(  fixed_point64::create_from_u128(2), fixed_point64::create_from_u128(3) );
+        assert!( fixed_point64::round(output_1) == 8, 0 );
+
+        // Asserts that 200^3 = 8000000
+        let output_2 = power(  fixed_point64::create_from_u128(200), fixed_point64::create_from_u128(3) );
+        assert!( fixed_point64::round(output_2) == 8000000, 1 );
+
+        // Asserts that 30^5 = 24300000
+        let output_3 = power(  fixed_point64::create_from_u128(30), fixed_point64::create_from_u128(5) );
+        assert!( fixed_point64::round(output_3) == 24300000, 2 ); // 30^5 = 24300000
+
+        // tests for nth-root calculations
+
+        // Asserts that the square root of 16 is approximately 4.
+        let n_output_1 = power(  fixed_point64::create_from_u128(16), fixed_point64::create_from_rational(1, 2 )  );
+        assert!( fixed_point64::almost_equal( n_output_1, fixed_point64::create_from_rational( 4, 1  ), fixed_point64::create_from_u128(1)) , 3 );
+        // Asserts that the fifth root of 625 is approximately 3.623.
+        let n_output_2 = power(  fixed_point64::create_from_u128(625), fixed_point64::create_from_rational(1, 5 )  );
+        assert!( fixed_point64::almost_equal( n_output_2, fixed_point64::create_from_rational( 3623, 1000 ), fixed_point64::create_from_u128(1)) , 4 );
+        // Asserts that the cube root of 1000 is approximately 9.999999977.
+        let n_output_3 = power(  fixed_point64::create_from_u128(1000), fixed_point64::create_from_rational(1, 3 )  );
+        assert!( fixed_point64::almost_equal( n_output_3, fixed_point64::create_from_rational( 9999, 1000 ), fixed_point64::create_from_u128(1)) , 5 );
+        // Asserts that the cube root of 729 is approximately 8.99999998.
+        let n_output_4 = power(  fixed_point64::create_from_u128(729), fixed_point64::create_from_rational(1, 3 )  );
+        assert!( fixed_point64::almost_equal( n_output_4, fixed_point64::create_from_rational( 8999, 1000 ), fixed_point64::create_from_u128(1)) , 6 );
+        
+        // Asserts that the fourth root of 9/16 is approximately 0.866025404.
+        let n_output_5 = power(  fixed_point64::create_from_rational( 9, 16 ), fixed_point64::create_from_rational( 1, 4 )  );
+        assert!( fixed_point64::almost_equal( n_output_5, fixed_point64::create_from_rational( 866025404, 1000000000 ), fixed_point64::create_from_u128(1)) , 7 ); // 0.866025404
+        
+        // Asserts that the tenth root of 1/2 is approximately 0.420448208.
+        let n_output_6 = power(  fixed_point64::create_from_rational( 1, 2 ), fixed_point64::create_from_rational( 10, 8 )  );
+        assert!( fixed_point64::almost_equal( n_output_6, fixed_point64::create_from_rational( 420448208, 1000000000 ), fixed_point64::create_from_u128(1)) , 8 ); // 0.420448208
+
+        // Asserts that the fifth root of 2/5 is approximately 0.01024.
+        let n_output_7 = power(  fixed_point64::create_from_rational( 2, 5 ), fixed_point64::create_from_rational( 5, 1 )  );
+        assert!( fixed_point64::almost_equal( n_output_7, fixed_point64::create_from_rational( 1024, 100000 ), fixed_point64::create_from_u128(1)) , 9 ); // 0.01024
+
+        // Asserts that the ninth root of 3/5 is approximately 0.566896603.
+        let n_output_8 = power(  fixed_point64::create_from_rational( 3, 5 ), fixed_point64::create_from_rational( 10, 9 )  );
+        assert!( fixed_point64::almost_equal( n_output_8, fixed_point64::create_from_rational( 566896603, 1000000000 ), fixed_point64::create_from_u128(1)) , 10 ); // 0.566896603
+        
+    }
+
+}
\ No newline at end of file
diff --git a/packages/sui/sources/amm.move b/packages/sui/sources/amm.move
index bf95e57..e8e2777 100644
--- a/packages/sui/sources/amm.move
+++ b/packages/sui/sources/amm.move
@@ -470,7 +470,7 @@ module legato::amm {
         proj_on_x: bool, // Indicates whether the project token is on the X or Y side
         start_weight: u64,  // Initial weight of the project token.
         final_weight: u64, // The weight when the pool is stabilized.  
-        is_vault: bool, // Determines the quota unit: false - by settlement collected, true - by staking rewards.
+        is_vault: bool, // false - only common coins, true - coins+staking rewards.
         target_amount: u64, // The target amount required to fully shift the weight.
         ctx: &mut TxContext
     ) {
@@ -1161,7 +1161,7 @@ module legato::amm {
         lbp::set_new_target_amount(  params, new_target_amount );
     }
 
-    // Set a new target amount for LBP
+    // Enable/Disable buy with pair or with vault tokens
     public entry fun lbp_enable_buy_with_pair_and_vault<Y>(global: &mut AMMGlobal, _manager_cap: &mut ManagerCap, enable_pair: bool, enable_vault: bool) {
         assert!( has_registered<SUI, Y>(global)  , ERR_NOT_REGISTERED);
         

From 87f72ef0df53b59e62fddd545ef0271526b4196a Mon Sep 17 00:00:00 2001
From: pisuthd <pisuth.dae@gmail.com>
Date: Thu, 6 Jun 2024 14:36:54 +0900
Subject: [PATCH 4/4] Legato Aptos main contracts

---
 packages/aptos/.gitignore                     |    3 +-
 packages/aptos/sources/amm.move               | 1018 +++++++++++++++++
 packages/aptos/sources/lbp.move               |  177 +++
 packages/aptos/sources/utils/mock_legato.move |    7 +-
 .../aptos/sources/utils/mock_legato_fa.move   |    1 +
 packages/aptos/sources/utils/mock_usdc.move   |    5 +-
 .../aptos/sources/utils/mock_usdc_fa.move     |    2 +-
 packages/aptos/sources/vault.move             |  547 +++++++++
 packages/aptos/tests/amm_tests.move           |  187 +++
 .../aptos_system/delegation_pool_tests.move   |  205 ++++
 packages/aptos/tests/lbp_usdc_tests.move      |  108 ++
 packages/aptos/tests/vault_tests.move         |  296 +++++
 12 files changed, 2547 insertions(+), 9 deletions(-)
 create mode 100644 packages/aptos/sources/amm.move
 create mode 100644 packages/aptos/sources/lbp.move
 create mode 100644 packages/aptos/tests/amm_tests.move
 create mode 100644 packages/aptos/tests/aptos_system/delegation_pool_tests.move
 create mode 100644 packages/aptos/tests/lbp_usdc_tests.move
 create mode 100644 packages/aptos/tests/vault_tests.move

diff --git a/packages/aptos/.gitignore b/packages/aptos/.gitignore
index 243f545..599abe9 100644
--- a/packages/aptos/.gitignore
+++ b/packages/aptos/.gitignore
@@ -1,2 +1,3 @@
 .aptos/
-build/
\ No newline at end of file
+build/
+old/
\ No newline at end of file
diff --git a/packages/aptos/sources/amm.move b/packages/aptos/sources/amm.move
new file mode 100644
index 0000000..d1c0832
--- /dev/null
+++ b/packages/aptos/sources/amm.move
@@ -0,0 +1,1018 @@
+// Copyright (c) Tamago Blockchain Labs, Inc.
+// SPDX-License-Identifier: MIT
+
+// A custom weight DEX for trading tokens to tokens including vault tokens
+// Allows setup of various types of pools - weighted pool, stable pool and LBP pool
+// Forked from OmniBTC AMM Swap and improved with math from Balancer V2 Lite
+// Supports only legacy coins for now
+
+module legato_addr::amm {
+
+    use std::option::{Self, Option};
+    use std::signer;
+    use std::vector; 
+    use std::bcs;
+    use std::string::{Self, String };  
+    
+    use aptos_framework::event;
+    use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability}; 
+    use aptos_framework::object::{Self, ExtendRef, Object, ConstructorRef }; 
+    use aptos_std::smart_vector::{Self, SmartVector}; 
+    use aptos_std::comparator::{Self, Result};
+    use aptos_std::type_info;  
+    use aptos_std::fixed_point64::{Self, FixedPoint64}; 
+
+    use legato_addr::weighted_math;
+    use legato_addr::stable_math;
+    use legato_addr::lbp::{Self, LBPParams};
+
+    // ======== Constants ========
+
+    // Default swap fee of 0.5% in fixed-point
+    const DEFAULT_FEE: u128 = 92233720368547758; 
+    // 0.25% for LBP
+    const LBP_FEE: u128 = 46116860184273879;
+    // 0.1% for stable pools
+    const STABLE_FEE: u128 = 18446744073709551;
+    // Minimal liquidity.
+    const MINIMAL_LIQUIDITY: u64 = 1000; 
+    // Max u64 value.
+    const U64_MAX: u64 = 18446744073709551615;
+    
+    const WEIGHT_SCALE: u64 = 10000; 
+    
+    const SYMBOL_PREFIX_LENGTH: u64 = 4;
+
+    const LP_TOKEN_DECIMALS: u8 = 8;
+    // The max value that can be held in one of the Balances of
+    /// a Pool. U64 MAX / WEIGHT_SCALE
+    const MAX_POOL_VALUE : u64 = 18446744073709551615;
+
+    // ======== Errors ========
+
+    const ERR_NOT_COIN: u64 = 101;
+    const ERR_THE_SAME_COIN: u64 = 102;
+    const ERR_WEIGHTS_SUM: u64 = 103;
+    const ERR_UNAUTHORIZED: u64 = 104;
+    const ERR_MUST_BE_ORDER: u64 = 105;
+    const ERR_POOL_HAS_REGISTERED: u64 = 106;
+    const ERR_INVALID_ADDRESS: u64 = 107;
+    const ERR_POOL_NOT_REGISTER: u64 = 108;
+    const ERR_NOT_LBP: u64 = 109;
+    const ERR_PAUSED: u64 = 110;
+    const ERR_POOL_EXISTS: u64 = 111; 
+    const ERR_COIN_OUT_NUM_LESS_THAN_EXPECTED_MINIMUM: u64 = 112;
+    const ERR_INSUFFICIENT_COIN_X: u64 = 113;
+    const ERR_INSUFFICIENT_COIN_Y: u64 = 114;
+    const ERR_OVERLIMIT: u64 = 115; 
+    const ERR_U64_OVERFLOW: u64 = 116;
+    const ERR_LIQUID_NOT_ENOUGH: u64 = 117;
+    const ERR_POOL_FULL: u64 = 118; 
+
+
+    // ======== Structs =========
+    /// The Pool token that will be used to mark the pool share
+    /// of a liquidity provider. The parameter `X` and `Y` is for the
+    /// coin held in the pool.
+    struct LP<phantom X, phantom Y> has drop, store {}
+
+    // Liquidity pool with custom weighting 
+    struct Pool<phantom X, phantom Y> has key, store {
+        coin_x: Coin<X>,
+        coin_y: Coin<Y>,
+        weight_x: u64, // 50% using 5000
+        weight_y: u64, // 50% using 5000
+        min_liquidity: Coin<LP<X, Y>>,
+        swap_fee: FixedPoint64,
+        lp_mint: MintCapability<LP<X, Y>>,
+        lp_burn: BurnCapability<LP<X, Y>>,
+        lbp_params: Option<LBPParams>, // Params for a LBP pool
+        has_paused: bool,
+        is_stable: bool, // Indicates if the pool is a stable pool
+        is_lbp: bool, // Indicates if the pool is a LBP
+    }
+
+    // Represents the global state of the AMM. 
+    #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
+    struct AMMManager has key { 
+        pool_list: SmartVector<String>, // all pools in the system
+        whitelist: SmartVector<address>, // who can setup a new pool
+        extend_ref: ExtendRef,
+        enable_whitelist: bool,
+        treasury_address: address // where all fees from all pools will be sent for further LP staking
+    }
+
+    #[event]
+    /// Event emitted when a pool is created.
+    struct RegisterPool has drop, store { 
+        pool_name: String,
+        coin_x: String,
+        coin_y: String,
+        weight_x: u64,
+        weight_y: u64,
+        is_stable: bool,
+        is_lbp: bool
+    }
+
+    #[event]
+    struct Swapped has drop, store {
+        pool_name: String,
+        coin_in: String,
+        coin_out: String,
+        amount_in: u64,
+        amount_out: u64
+    }
+
+    #[event]
+    struct AddedLiquidity has drop, store {
+        pool_name: String,
+        coin_x: String,
+        coin_y: String,
+        coin_x_in: u64,
+        coin_y_in: u64,
+        lp_out: u64
+    }
+
+    #[event]
+    struct RemovedLiquidity has drop, store {
+        pool_name: String,
+        coin_x: String,
+        coin_y: String,
+        lp_in: u64,
+        coin_x_out: u64,
+        coin_y_out: u64
+    }
+
+    // Constructor for this module.
+    fun init_module(sender: &signer) {
+        
+        let constructor_ref = object::create_object(signer::address_of(sender));
+        let extend_ref = object::generate_extend_ref(&constructor_ref);
+
+        let whitelist = smart_vector::new();
+        smart_vector::push_back(&mut whitelist, signer::address_of(sender));
+
+        move_to(sender, AMMManager { 
+            whitelist, 
+            pool_list: smart_vector::new(), 
+            extend_ref,
+            enable_whitelist: true,
+            treasury_address: signer::address_of(sender)
+        });
+    }
+
+    // ======== Entry Points =========
+
+    /// Entry point for the `swap` method.
+    /// Sends swapped Coin to the sender.
+    public entry fun swap<X,Y>(sender: &signer, coin_in: u64, coin_out_min: u64) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(coin::is_coin_initialized<X>(), ERR_NOT_COIN);
+        assert!(coin::is_coin_initialized<Y>(), ERR_NOT_COIN);
+
+        if (is_order) {
+            let (reserve_x, reserve_y) = get_reserves_size<X, Y>();
+            swap_out_y<X, Y>(sender, coin_in, coin_out_min, reserve_x, reserve_y);
+        } else {
+            let (reserve_y, reserve_x) = get_reserves_size<Y, X>();
+            swap_out_x<Y, X>(sender, coin_in, coin_out_min, reserve_x, reserve_y);
+        };
+    }
+
+    // Register a new liquidity pool with custom weights
+    public entry fun register_pool<X, Y>(
+        sender: &signer,
+        weight_x: u64,
+        weight_y: u64
+    ) acquires AMMManager {
+        let is_order = is_order<X, Y>();
+        if (!is_order) {
+            register_pool<Y,X>(sender, weight_y, weight_x);
+        } else {
+            assert!(coin::is_coin_initialized<X>(), ERR_NOT_COIN);
+            assert!(coin::is_coin_initialized<Y>(), ERR_NOT_COIN);
+
+            let config = borrow_global_mut<AMMManager>(@legato_addr);
+            let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+            if (config.enable_whitelist) {
+                // Ensure that the caller is on the whitelist
+                assert!( smart_vector::contains(&config.whitelist, &(signer::address_of(sender))) , ERR_UNAUTHORIZED);
+            };
+
+            let (lp_name, lp_symbol) = generate_lp_name_and_symbol<X, Y>();
+
+            assert!( !smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_HAS_REGISTERED);
+
+            let (lp_burn_cap, lp_freeze_cap, lp_mint_cap) = coin::initialize<LP<X, Y>>(sender, lp_name, lp_symbol, 8, true);
+            coin::destroy_freeze_cap(lp_freeze_cap);
+
+            // Registers X and Y if not already registered
+            if (!coin::is_account_registered<X>(signer::address_of(&config_object_signer))) {
+                coin::register<X>(&config_object_signer)
+            };
+
+            if (!coin::is_account_registered<Y>(signer::address_of(&config_object_signer))) {
+                coin::register<Y>(&config_object_signer)
+            };
+
+            let pool = init_pool_params<X,Y>(weight_x, weight_y, fixed_point64::create_from_raw_value( DEFAULT_FEE ), lp_mint_cap, lp_burn_cap, false, false, option::none() );
+
+            move_to(&config_object_signer, pool);
+
+            smart_vector::push_back(&mut config.pool_list, lp_name);
+
+            // Emit an event
+            event::emit(RegisterPool { pool_name: lp_name, coin_x : coin::symbol<X>(), coin_y : coin::symbol<Y>(),  weight_x, weight_y, is_stable: false,  is_lbp: false });
+        };
+    }
+
+    // Register a stable pool, weights are fixed at 50/50
+    public entry fun register_stable_pool<X,Y>(sender: &signer) acquires AMMManager {
+        let is_order = is_order<X, Y>();
+        if (!is_order) {
+            register_stable_pool<Y,X>(sender );
+        } else {
+            assert!(coin::is_coin_initialized<X>(), ERR_NOT_COIN);
+            assert!(coin::is_coin_initialized<Y>(), ERR_NOT_COIN);
+
+            let config = borrow_global_mut<AMMManager>(@legato_addr);
+            let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+            if (config.enable_whitelist) {
+                // Ensure that the caller is on the whitelist
+                assert!( smart_vector::contains(&config.whitelist, &(signer::address_of(sender))) , ERR_UNAUTHORIZED);
+            };
+
+            let (lp_name, lp_symbol) = generate_lp_name_and_symbol<X, Y>();
+
+            assert!( !smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_HAS_REGISTERED);
+
+            let (lp_burn_cap, lp_freeze_cap, lp_mint_cap) = coin::initialize<LP<X, Y>>(sender, lp_name, lp_symbol, 8, true);
+            coin::destroy_freeze_cap(lp_freeze_cap);
+
+            // Registers X and Y if not already registered
+            if (!coin::is_account_registered<X>(signer::address_of(&config_object_signer))) {
+                coin::register<X>(&config_object_signer)
+            };
+
+            if (!coin::is_account_registered<Y>(signer::address_of(&config_object_signer))) {
+                coin::register<Y>(&config_object_signer)
+            };
+
+            let pool = init_pool_params<X,Y>(5000, 5000, fixed_point64::create_from_raw_value( STABLE_FEE ), lp_mint_cap, lp_burn_cap, true, false, option::none() );
+
+            move_to(&config_object_signer, pool);
+
+            smart_vector::push_back(&mut config.pool_list, lp_name);
+
+            // Emit an event
+            event::emit(RegisterPool { pool_name: lp_name, coin_x : coin::symbol<X>(), coin_y : coin::symbol<Y>(),  weight_x: 5000, weight_y: 5000, is_stable: true,  is_lbp: false });
+        };
+
+    }
+
+    // Register an LBP pool, project token weights must be greater than 50%.
+    // is_vault specifies if staking rewards from Legato Vault are accepted
+    public entry fun register_lbp_pool<X,Y>(
+        sender: &signer,
+        proj_on_x: bool, // Indicates whether the project token is on the X or Y side
+        start_weight: u64,  // Initial weight of the project token.
+        final_weight: u64, // The weight when the pool is stabilized. 
+        is_vault: bool, // false - only common coins, true - coins+staking rewards.
+        target_amount: u64, // The target amount required to fully shift the weight.
+    ) acquires AMMManager {
+        
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+
+        assert!(coin::is_coin_initialized<X>(), ERR_NOT_COIN);
+        assert!(coin::is_coin_initialized<Y>(), ERR_NOT_COIN);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        if (config.enable_whitelist) {
+            // Ensure that the caller is on the whitelist
+            assert!( smart_vector::contains(&config.whitelist, &(signer::address_of(sender))) , ERR_UNAUTHORIZED);
+        };
+
+        let (lp_name, lp_symbol) = generate_lp_name_and_symbol<X, Y>();
+
+        assert!( !smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_HAS_REGISTERED);
+
+        let (lp_burn_cap, lp_freeze_cap, lp_mint_cap) = coin::initialize<LP<X, Y>>(sender, lp_name, lp_symbol, 8, true);
+        coin::destroy_freeze_cap(lp_freeze_cap);
+
+        // Registers X and Y if not already registered
+        if (!coin::is_account_registered<X>(signer::address_of(&config_object_signer))) {
+            coin::register<X>(&config_object_signer)
+        };
+
+        if (!coin::is_account_registered<Y>(signer::address_of(&config_object_signer))) {
+            coin::register<Y>(&config_object_signer)
+        };
+
+        let params = lbp::construct_init_params(
+            proj_on_x,
+            start_weight,
+            final_weight, 
+            is_vault,
+            target_amount
+        );
+
+        let pool = init_pool_params<X,Y>(0, 0, fixed_point64::create_from_raw_value( LBP_FEE ), lp_mint_cap, lp_burn_cap, false, true, option::some<LBPParams>(params) );
+
+        move_to(&config_object_signer, pool);
+
+        smart_vector::push_back(&mut config.pool_list, lp_name);
+
+        // Emit an event
+        event::emit(RegisterPool { pool_name: lp_name, coin_x : coin::symbol<X>(), coin_y : coin::symbol<Y>(),  weight_x: start_weight, weight_y: final_weight, is_stable: false,  is_lbp: true });
+
+    }
+
+    /// Entrypoint for the `add_liquidity` method.
+    /// Sends `LP<X,Y>` to the transaction sender.
+    public entry fun add_liquidity<X,Y>(
+        lp_provider: &signer, 
+        coin_x_amount: u64,
+        coin_x_min: u64,
+        coin_y_amount: u64,
+        coin_y_min: u64
+    ) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+
+        assert!(coin::is_coin_initialized<X>(), ERR_NOT_COIN);
+        assert!(coin::is_coin_initialized<Y>(), ERR_NOT_COIN);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_EXISTS);
+
+        let (optimal_x, optimal_y, _) = calc_optimal_coin_values<X, Y>(
+            coin_x_amount,
+            coin_y_amount,
+            coin_x_min,
+            coin_y_min
+        );
+
+        let (reserves_x, reserves_y) = get_reserves_size<X, Y>();
+
+        assert!(optimal_x >= coin_x_min, ERR_INSUFFICIENT_COIN_X);
+        assert!(optimal_y >= coin_y_min, ERR_INSUFFICIENT_COIN_Y);
+
+        let coin_x_opt = coin::withdraw<X>(lp_provider, optimal_x);
+        let coin_y_opt = coin::withdraw<Y>(lp_provider, optimal_y);
+
+        let lp_coins = mint_lp<X, Y>( 
+            coin_x_opt,
+            coin_y_opt,
+            optimal_x,
+            optimal_y,
+            reserves_x,
+            reserves_y
+        );
+
+        let lp_amount = coin::value(&lp_coins);
+
+        let lp_provider_address = signer::address_of(lp_provider);
+        if (!coin::is_account_registered<LP<X, Y>>(lp_provider_address)) {
+            coin::register<LP<X, Y>>(lp_provider);
+        };
+        coin::deposit(lp_provider_address, lp_coins);
+
+        // Emit an event
+        event::emit(AddedLiquidity { pool_name: lp_name, coin_x : coin::symbol<X>(), coin_y : coin::symbol<Y>(),  coin_x_in:optimal_x, coin_y_in: optimal_y , lp_out: lp_amount });
+    }
+
+    /// Entrypoint for the `remove_liquidity` method.
+    /// Transfers Coin<X> and Coin<Y> to the sender.
+    public entry fun remove_liquidity<X, Y>( 
+        lp_provider: &signer, 
+        lp_amount: u64
+    ) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+
+        assert!(coin::is_coin_initialized<LP<X,Y>>(), ERR_NOT_COIN);
+        
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_NOT_REGISTER);
+
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+        assert!(exists<Pool<X, Y>>(pool_address), ERR_POOL_EXISTS);
+
+        let (reserves_x, reserves_y) = get_reserves_size<X, Y>();
+        let lp_coins_total = option::extract(&mut coin::supply<LP<X, Y>>());
+
+        let pool = borrow_global_mut<Pool<X, Y>>(pool_address);
+        assert!(!pool.has_paused , ERR_PAUSED );
+
+        let coin_x_out = 0;
+        let coin_y_out = 0;
+
+        if (!pool.is_stable) {
+            
+            let (weight_x, weight_y ) = pool_current_weight<X,Y>(pool);
+
+            (coin_x_out, coin_y_out) = weighted_math::compute_withdrawn_coins( 
+                lp_amount, 
+                (lp_coins_total as u64), 
+                reserves_x, 
+                reserves_y, 
+                weight_x, 
+                weight_y
+            ); 
+
+            let coin_x = coin::extract(&mut pool.coin_x, coin_x_out);
+            coin::deposit(signer::address_of(lp_provider), coin_x);
+
+            let coin_y = coin::extract(&mut pool.coin_y, coin_y_out);
+            coin::deposit(signer::address_of(lp_provider), coin_y);
+
+        } else {
+
+            let multiplier = fixed_point64::create_from_rational( (lp_amount as u128), (lp_coins_total as u128)  );
+ 
+            coin_x_out = (fixed_point64::multiply_u128( (reserves_x as u128), multiplier ) as u64); 
+            coin_y_out = (fixed_point64::multiply_u128( (reserves_y as u128), multiplier ) as u64);
+
+            let coin_x = coin::extract(&mut pool.coin_x, (coin_x_out));
+            coin::deposit(signer::address_of(lp_provider), coin_x);
+
+            let coin_y = coin::extract(&mut pool.coin_y, (coin_y_out));
+            coin::deposit(signer::address_of(lp_provider), coin_y);
+
+        };
+
+        let burn_coin = coin::withdraw<LP<X, Y>>(lp_provider, lp_amount);
+        coin::burn(burn_coin, &pool.lp_burn);
+
+        // Emit an event
+        event::emit(RemovedLiquidity { pool_name: lp_name, coin_x : coin::symbol<X>(), coin_y : coin::symbol<Y>(), lp_in: lp_amount, coin_x_out, coin_y_out });
+    }
+
+    #[view]
+    public fun is_order<X, Y>(): bool {
+        let comp = compare<X, Y>();
+        assert!(!comparator::is_equal(&comp), ERR_THE_SAME_COIN);
+
+        if (comparator::is_smaller_than(&comp)) {
+            true
+        } else {
+            false
+        }
+    }
+
+    #[view]
+    public fun get_config_object_address() : address  acquires AMMManager  {
+        let config = borrow_global<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        signer::address_of(&config_object_signer)
+    }
+
+    #[view]
+    public fun get_treasury_address(): address acquires AMMManager {
+        let config = borrow_global<AMMManager>(@legato_addr);
+        config.treasury_address
+    }
+
+    // Retrieves information about the LBP pool
+    #[view]
+    public fun lbp_info<X,Y>() : (u64, u64, u64, u64) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+        
+        if (is_order) {
+            assert!(exists<Pool<X, Y>>(pool_address), ERR_POOL_EXISTS);
+            let pool = borrow_global_mut<Pool<X, Y>>(pool_address);
+
+            assert!( pool.is_lbp == true , ERR_NOT_LBP);
+
+            let ( weight_x, weight_y ) = pool_current_weight(pool);
+            let params = option::borrow(&pool.lbp_params);
+
+            (weight_x,  weight_y, lbp::total_amount_collected(params), lbp::total_target_amount(params))
+        } else {
+            assert!(exists<Pool<Y, X>>(pool_address), ERR_POOL_EXISTS);
+            let pool = borrow_global_mut<Pool<Y, X>>(pool_address);
+
+            assert!( pool.is_lbp == true , ERR_NOT_LBP);
+
+            let ( weight_y, weight_x ) = pool_current_weight(pool);
+            let params = option::borrow(&pool.lbp_params);
+
+            ( weight_x, weight_y, lbp::total_amount_collected(params), lbp::total_target_amount(params))
+        }
+ 
+    }
+
+    /// Calculate amounts needed for adding new liquidity for both `X` and `Y`.
+    /// * `x_desired` - desired value of coins `X`.
+    /// * `y_desired` - desired value of coins `Y`.
+    /// Returns both `X` and `Y` coins amounts.
+    public fun calc_optimal_coin_values<X, Y>(
+        x_desired: u64,
+        y_desired: u64,
+        coin_x_min: u64,
+        coin_y_min: u64
+    ): (u64, u64, bool) acquires Pool, AMMManager   {
+        let (reserves_x, reserves_y) = get_reserves_size<X, Y>();
+
+        let config = borrow_global<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+        let pool = borrow_global<Pool<X, Y>>(pool_address);
+
+        if (reserves_x == 0 && reserves_y == 0) {
+            return (x_desired, y_desired, true)
+        } else {
+
+            // For non-stable pools, use weighted math to compute optimal values.
+            if (!pool.is_stable) {
+
+                let (weight_x, weight_y ) = pool_current_weight<X,Y>(pool);
+
+                let y_needed = weighted_math::compute_optimal_value(x_desired, reserves_y, weight_y, reserves_x, weight_x );
+
+                if (y_needed <= y_desired) {
+                    assert!(y_needed >= coin_y_min, ERR_INSUFFICIENT_COIN_Y);
+                    return (x_desired, y_needed, false)
+                } else {
+                    let x_needed =  weighted_math::compute_optimal_value(y_desired, reserves_x, weight_x, reserves_y, weight_y);
+                    assert!(x_needed <= x_desired, ERR_OVERLIMIT);
+                    assert!(x_needed >= coin_x_min, ERR_INSUFFICIENT_COIN_X);
+                    return (x_needed, y_desired, false)
+                }
+
+            } else {
+
+                // For stable pools, use stable math to compute the optimal values.
+                let coin_y_returned = stable_math::get_amount_out(
+                    x_desired,
+                    reserves_x, 
+                    reserves_y
+                );
+
+                if (coin_y_returned <= y_desired) {
+                    assert!(coin_y_returned >= coin_y_min, ERR_INSUFFICIENT_COIN_Y);
+                    return (x_desired, coin_y_returned, false)
+                } else {
+                    let coin_x_returned = stable_math::get_amount_out(
+                        y_desired,
+                        reserves_y, 
+                        reserves_x
+                    );
+
+                    assert!(coin_x_returned <= x_desired, ERR_OVERLIMIT);
+                    assert!(coin_x_returned >= coin_x_min, ERR_INSUFFICIENT_COIN_X);
+                    return (coin_x_returned, y_desired, false) 
+                }
+
+            }
+
+        }
+    }
+
+    public fun get_reserves_size<X, Y>(): (u64, u64) acquires Pool, AMMManager {
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+
+        assert!(exists<Pool<X, Y>>(pool_address), ERR_POOL_NOT_REGISTER);
+
+        let pool = borrow_global<Pool<X, Y>>(pool_address);
+
+        let x_reserve = coin::value(&pool.coin_x);
+        let y_reserve = coin::value(&pool.coin_y);
+
+        (x_reserve, y_reserve)
+    }
+
+    // Retrieve the current weights of the pool
+    public fun pool_current_weight<X,Y>(pool: &Pool<X, Y> ): (u64, u64)  {
+        
+        if (!pool.is_lbp) {
+            ( pool.weight_x, pool.weight_y )
+        } else {
+            let params = option::borrow(&pool.lbp_params);
+            lbp::current_weight( params ) 
+        }
+
+    }
+
+    /// Calculates the provided liquidity based on the current LP supply and reserves.
+    /// If the LP supply is zero, it computes the initial liquidity and increases the supply.
+    public fun calculate_provided_liq<X,Y>(pool: &mut Pool<X, Y>, lp_supply: u64, coin_x_reserve: u64, coin_y_reserve: u64, optimal_coin_x: u64, optimal_coin_y: u64 ) : u64 {
+        if (!pool.is_stable) {
+
+            // Obtain the current weights of the pool
+            let (weight_x, weight_y ) = pool_current_weight<X,Y>(pool);
+
+            if (0 == lp_supply) {
+
+                let initial_liq = weighted_math::compute_initial_lp( weight_x, weight_y , optimal_coin_x , optimal_coin_y  );
+                assert!(initial_liq > MINIMAL_LIQUIDITY, ERR_LIQUID_NOT_ENOUGH);
+
+                coin::merge(&mut pool.min_liquidity, coin::mint<LP<X, Y>>(MINIMAL_LIQUIDITY, &pool.lp_mint) );
+
+                initial_liq - MINIMAL_LIQUIDITY
+            } else {
+                weighted_math::compute_derive_lp( optimal_coin_x, optimal_coin_y, weight_x, weight_y, coin_x_reserve, coin_y_reserve, lp_supply )
+            }
+        } else {
+            if (0 == lp_supply) {
+
+                let initial_liq = stable_math::compute_initial_lp(  optimal_coin_x , optimal_coin_y  );
+                assert!(initial_liq > MINIMAL_LIQUIDITY, ERR_LIQUID_NOT_ENOUGH);
+
+                coin::merge(&mut pool.min_liquidity, coin::mint<LP<X, Y>>(MINIMAL_LIQUIDITY, &pool.lp_mint) );
+
+                initial_liq - MINIMAL_LIQUIDITY
+            } else {
+                let x_liq = (lp_supply as u128) * (optimal_coin_x as u128) / (coin_x_reserve as u128);
+                let y_liq = (lp_supply as u128) * (optimal_coin_y as u128) / (coin_y_reserve as u128);
+                if (x_liq < y_liq) {
+                    assert!(x_liq < (U64_MAX as u128), ERR_U64_OVERFLOW);
+                    (x_liq as u64)
+                } else {
+                    assert!(y_liq < (U64_MAX as u128), ERR_U64_OVERFLOW);
+                    (y_liq as u64)
+                }
+            }
+        }
+    }
+
+    // ======== Internal Functions =========
+
+    fun coin_symbol_prefix<CoinType>(): String {
+        let symbol = coin::symbol<CoinType>();
+        let prefix_length = SYMBOL_PREFIX_LENGTH;
+        if (string::length(&symbol) < SYMBOL_PREFIX_LENGTH) {
+            prefix_length = string::length(&symbol);
+        };
+        string::sub_string(&symbol, 0, prefix_length)
+    }
+
+    /// Compare two coins, 'X' and 'Y'.
+    fun compare<X, Y>(): Result {
+        let x_info = type_info::type_of<X>();
+        let x_compare = &mut type_info::struct_name(&x_info);
+        vector::append(x_compare, type_info::module_name(&x_info));
+
+        let y_info = type_info::type_of<Y>();
+        let y_compare = &mut type_info::struct_name(&y_info);
+        vector::append(y_compare, type_info::module_name(&y_info));
+
+        let comp = comparator::compare(x_compare, y_compare);
+        if (!comparator::is_equal(&comp)) return comp;
+
+        let x_address = type_info::account_address(&x_info);
+        let y_address = type_info::account_address(&y_info);
+        comparator::compare(&x_address, &y_address)
+    }
+
+    /// Generate LP coin name and symbol for pair `X`/`Y`.
+    /// ```
+    /// name = "LP-" + symbol<X>() + "-" + symbol<Y>();
+    /// symbol = symbol<X>()[0:4] + "-" + symbol<Y>()[0:4];
+    /// ```
+    /// For example, for `LP<BTC, USDT>`,
+    /// the result will be `(b"LP-BTC-USDT", b"BTC-USDT")`
+    public fun generate_lp_name_and_symbol<X, Y>(): (String, String) {
+        let lp_name = string::utf8(b"");
+        string::append_utf8(&mut lp_name, b"LP-");
+        string::append(&mut lp_name, coin::symbol<X>());
+        string::append_utf8(&mut lp_name, b"-");
+        string::append(&mut lp_name, coin::symbol<Y>());
+
+        let lp_symbol = string::utf8(b"");
+        string::append(&mut lp_symbol, coin_symbol_prefix<X>());
+        string::append_utf8(&mut lp_symbol, b"-");
+        string::append(&mut lp_symbol, coin_symbol_prefix<Y>());
+
+        (lp_name, lp_symbol)
+    }
+
+    fun init_pool_params<X,Y>(weight_x: u64, weight_y: u64, swap_fee: FixedPoint64, lp_mint: MintCapability<LP<X,Y>>, lp_burn: BurnCapability<LP<X,Y>>, is_stable: bool, is_lbp: bool, lbp_params: Option<LBPParams> ) : Pool<X, Y> {
+        // Ensure that the normalized weights sum up to 100%
+        if (!is_lbp) {
+            assert!( weight_x+weight_y == 10000, ERR_WEIGHTS_SUM); 
+        };
+        
+        Pool<X, Y> {
+            coin_x: coin::zero<X>(),
+            coin_y: coin::zero<Y>(), 
+            weight_x,
+            weight_y,
+            swap_fee,
+            lp_mint,
+            lp_burn,
+            min_liquidity: coin::zero<LP<X,Y>>(),
+            has_paused: false,
+            is_stable,
+            is_lbp,
+            lbp_params
+        }
+    }
+
+    fun swap_out_y<X, Y>(
+        sender: &signer,
+        coin_in_value: u64,
+        coin_out_min_value: u64,
+        reserve_in: u64,
+        reserve_out: u64,
+    ) acquires Pool, AMMManager {
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_NOT_REGISTER);
+        
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+
+        let pool = borrow_global_mut<Pool<X, Y>>(pool_address);
+
+        assert!(!pool.has_paused , ERR_PAUSED );
+
+        let (coin_x_after_fees, coin_x_fee) = weighted_math::get_fee_to_treasury( pool.swap_fee , coin_in_value);
+
+        let (weight_x, weight_y ) = pool_current_weight<X,Y>(pool);
+
+        let coin_y_out = get_amount_out(
+            pool.is_stable,
+            coin_x_after_fees,
+            reserve_in,
+            weight_x,
+            reserve_out,
+            weight_y
+        );
+
+        assert!(
+            coin_y_out >= coin_out_min_value,
+            ERR_COIN_OUT_NUM_LESS_THAN_EXPECTED_MINIMUM
+        );
+
+        if (pool.is_lbp) {
+            let params = option::borrow_mut(&mut pool.lbp_params);
+            let is_buy = lbp::is_buy(params);  
+            lbp::verify_and_adjust_amount(params, is_buy, coin_in_value, coin_y_out, false );
+        };
+
+        let coin_in = coin::withdraw<X>(sender, coin_in_value);
+        let fee_in = coin::extract(&mut coin_in, coin_x_fee);
+
+        coin::deposit( config.treasury_address, fee_in);
+
+        coin::merge(&mut pool.coin_x, coin_in);
+
+        let out_swapped = coin::extract(&mut pool.coin_y, coin_y_out);
+
+        if (!coin::is_account_registered<Y>(signer::address_of(sender))) {
+            coin::register<Y>(sender);
+        };
+
+        coin::deposit(signer::address_of(sender), out_swapped);
+
+        // Emit an event
+        event::emit(Swapped { pool_name: lp_name, coin_in : coin::symbol<X>(), coin_out : coin::symbol<Y>(), amount_in: coin_in_value, amount_out: coin_y_out });
+    }
+
+    fun swap_out_x<X,Y>(
+        sender: &signer,
+        coin_in_value: u64,
+        coin_out_min_value: u64,
+        reserve_in: u64,
+        reserve_out: u64
+    ) acquires Pool, AMMManager {
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name , _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name), ERR_POOL_NOT_REGISTER);
+        
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+
+        let pool = borrow_global_mut<Pool<X, Y>>(pool_address);
+
+        assert!(!pool.has_paused , ERR_PAUSED );
+
+        let (coin_y_after_fees, coin_y_fee) =  weighted_math::get_fee_to_treasury( pool.swap_fee , coin_in_value);
+
+        let (weight_x, weight_y ) = pool_current_weight(pool);
+
+        let coin_x_out = weighted_math::get_amount_out(
+            coin_y_after_fees,
+            reserve_in,
+            weight_y,
+            reserve_out,
+            weight_x
+        );
+
+        assert!(
+            coin_x_out >= coin_out_min_value,
+            ERR_COIN_OUT_NUM_LESS_THAN_EXPECTED_MINIMUM
+        );
+
+        if (pool.is_lbp) {
+            let params = option::borrow_mut(&mut pool.lbp_params);
+            let is_buy = lbp::is_buy(params);   
+            lbp::verify_and_adjust_amount(params, !is_buy, coin_in_value, coin_x_out, false);
+        };
+
+        let coin_in = coin::withdraw<Y>(sender, coin_in_value);
+        let fee_in = coin::extract(&mut coin_in, coin_y_fee);
+
+        coin::deposit( config.treasury_address, fee_in);
+
+        coin::merge(&mut pool.coin_y, coin_in);
+
+        let out_swapped = coin::extract(&mut pool.coin_x, coin_x_out);
+
+        if (!coin::is_account_registered<X>(signer::address_of(sender))) {
+            coin::register<X>(sender);
+        };
+
+        coin::deposit(signer::address_of(sender), out_swapped);
+
+        // Emit an event
+        event::emit(Swapped { pool_name: lp_name, coin_in : coin::symbol<Y>(), coin_out : coin::symbol<X>(), amount_in: coin_in_value, amount_out: coin_x_out });
+    }
+
+    fun get_amount_out(is_stable: bool, coin_in: u64, reserve_in: u64, weight_in: u64, reserve_out: u64, weight_out: u64) : u64 {
+        if (!is_stable) {
+            weighted_math::get_amount_out(
+                coin_in,
+                reserve_in,
+                weight_in, 
+                reserve_out,
+                weight_out, 
+            )
+        } else { 
+            stable_math::get_amount_out(
+                coin_in,
+                reserve_in, 
+                reserve_out
+            )
+        }
+    }
+
+    // mint LP tokens
+    fun mint_lp<X, Y>( 
+        coin_x: Coin<X>,
+        coin_y: Coin<Y>,
+        optimal_x: u64,
+        optimal_y: u64,
+        coin_x_reserve: u64,
+        coin_y_reserve: u64
+    ): Coin<LP<X, Y>>  acquires Pool, AMMManager {
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer);
+
+        assert!(exists<Pool<X, Y>>(pool_address), ERR_POOL_EXISTS);
+
+        let pool = borrow_global_mut<Pool<X, Y>>(pool_address);
+        assert!(!pool.has_paused , ERR_PAUSED );
+
+        // let x_provided_val = coin::value<X>(&coin_x);
+        // let y_provided_val = coin::value<Y>(&coin_y);
+
+        // Retrieves total LP coins supply
+        let lp_coins_total = option::extract(&mut coin::supply<LP<X, Y>>());
+
+        // Computes provided liquidity
+        let provided_liq = calculate_provided_liq<X,Y>(pool, (lp_coins_total as u64), coin_x_reserve, coin_y_reserve, optimal_x, optimal_y  );
+
+        // Merges provided coins into pool
+        coin::merge(&mut pool.coin_x, coin_x);
+        coin::merge(&mut pool.coin_y, coin_y);
+
+        assert!(coin::value(&pool.coin_x) < MAX_POOL_VALUE, ERR_POOL_FULL);
+        assert!(coin::value(&pool.coin_y) < MAX_POOL_VALUE, ERR_POOL_FULL);
+
+        // Mints LP tokens
+        coin::mint<LP<X, Y>>(provided_liq, &pool.lp_mint)
+    }
+
+    // ======== Only Governance =========
+
+    // Adds a user to the whitelist
+    public entry fun add_whitelist(sender: &signer, whitelist_address: address) acquires AMMManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        assert!( !smart_vector::contains(&config.whitelist, &whitelist_address) , ERR_INVALID_ADDRESS);
+        smart_vector::push_back(&mut config.whitelist, whitelist_address);
+    }
+
+    // Removes a user from the whitelist
+    public entry fun remove_whitelist(sender: &signer, whitelist_address: address) acquires AMMManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (found, idx) = smart_vector::index_of<address>(&config.whitelist, &whitelist_address);
+        assert!(  found , ERR_INVALID_ADDRESS);
+        smart_vector::swap_remove<address>(&mut config.whitelist, idx );
+    }
+
+    // Update treasury address
+    public entry fun update_treasury_address(sender: &signer, new_address: address) acquires AMMManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        config.treasury_address = new_address;
+    }
+
+    // Enable or disable whitelist requirement
+    public entry fun enable_whitelist(sender: &signer, is_enable: bool) acquires AMMManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        config.enable_whitelist = is_enable;
+    }
+
+    // Updates the swap fee for the specified pool
+    public entry fun update_pool_fee<X,Y>(sender: &signer, fee_numerator: u128, fee_denominator: u128) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name), ERR_POOL_NOT_REGISTER);
+
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer); 
+
+        let pool_config = borrow_global_mut<Pool<X, Y>>(pool_address);
+        pool_config.swap_fee = fixed_point64::create_from_rational( fee_numerator, fee_denominator );
+
+    }
+
+    // Pause/Unpause the LP pool
+    public entry fun pause<X,Y>(sender: &signer, is_pause: bool) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name), ERR_POOL_NOT_REGISTER);
+
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer); 
+
+        let pool_config = borrow_global_mut<Pool<X, Y>>(pool_address);
+        pool_config.has_paused = is_pause
+    }
+
+    // Set a new target amount for LBP
+    public entry fun lbp_set_target_amount<X,Y>(sender: &signer, new_target_amount: u64) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_NOT_REGISTER);
+        
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer); 
+        let pool_config = borrow_global_mut<Pool<X, Y>>(pool_address);
+
+        let params = option::borrow_mut(&mut pool_config.lbp_params);
+        lbp::set_new_target_amount(  params, new_target_amount );
+    }
+
+    // Enable/Disable buy with pair or with vault tokens
+    public entry fun lbp_enable_buy_with_pair_and_vault<X,Y>(sender: &signer, enable_pair: bool, enable_vault: bool) acquires AMMManager, Pool {
+        let is_order = is_order<X, Y>();
+        assert!(is_order, ERR_MUST_BE_ORDER);
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<AMMManager>(@legato_addr);
+        let (lp_name, _) = generate_lp_name_and_symbol<X, Y>();
+        assert!( smart_vector::contains(&config.pool_list, &lp_name) , ERR_POOL_NOT_REGISTER);
+
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let pool_address = signer::address_of(&config_object_signer); 
+        let pool_config = borrow_global_mut<Pool<X, Y>>(pool_address);
+
+        assert!( pool_config.is_lbp , ERR_NOT_LBP);
+
+        let params = option::borrow_mut(&mut pool_config.lbp_params);
+
+        lbp::enable_buy_with_pair(  params, enable_pair );
+        lbp::enable_buy_with_vault(  params, enable_vault );
+    }
+
+
+    // ======== Test-related Functions =========
+
+    #[test_only] 
+    public fun init_module_for_testing(deployer: &signer) {
+        init_module(deployer)
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/lbp.move b/packages/aptos/sources/lbp.move
new file mode 100644
index 0000000..e64cbee
--- /dev/null
+++ b/packages/aptos/sources/lbp.move
@@ -0,0 +1,177 @@
+// Copyright (c) Tamago Blockchain Labs, Inc.
+// SPDX-License-Identifier: MIT
+
+// An extension to AMM for LBP pools allows automatic weight shifting triggered by liquidity inflow
+// The pool can be paired with any tokens such as APT or USDC and even with staking rewards earned through Legato Vault
+
+module legato_addr::lbp {
+
+    use aptos_std::fixed_point64::{Self, FixedPoint64}; 
+    use legato_addr::weighted_math::{power};
+
+
+    friend legato_addr::amm;
+
+
+    /// The integer scaling setting for weights
+    const WEIGHT_SCALE: u64 = 10000;
+
+    // ======== Errors ========
+ 
+    const ERR_INVALID_WEIGHT: u64 = 301;  
+    const ERR_INVALID_SENDER: u64 = 302;
+    const ERR_EMPTY: u64 = 303;
+    const ERR_INVALID_POOL: u64 = 304;
+    const ERR_INSUFFICIENT_AMOUNT : u64 = 305;
+    const ERR_INVALID_AMOUNT: u64 = 306;
+    const ERR_TOO_LOW_AMOUNT: u64 = 307;
+    const ERR_BUY_DISABLED_WITH_TOKEN: u64 = 308;
+    const ERR_BUY_DISABLED_WITH_VAULT: u64 = 309;
+
+    // ======== Structs =========
+
+    // Defines the settings for the LBP pool.
+    struct LBPParams has store {
+        is_proj_on_x: bool, // Indicates if the project token is on the X side of the pool.
+        start_weight: u64,  // Initial weight of the project token.
+        final_weight: u64, // The weight when the pool is stabilized.  
+        is_vault: bool, // Accepts vault tokens
+        target_amount: u64, // The target amount required to fully shift the weight.
+        total_amount_collected: u64,  // Total amount accumulated in the pool.
+        enable_buy_with_pair: bool, // Enable/Disable buy with pair tokens (SUI or USDC).
+        enable_buy_with_vault: bool // Enable/Disable buy with vault tokens (PT).
+    }
+
+    // Constructs initialization parameters for an Lpending_amountBP
+    public(friend) fun construct_init_params(
+        proj_on_x: bool, // Indicates whether the project token is on the X or Y side
+        start_weight: u64, 
+        final_weight: u64,  
+        is_vault: bool, // Determines if accept vault tokens.
+        target_amount: u64
+    ) : LBPParams {
+
+        // Check if weights and trigger amount are within valid ranges.
+        assert!( start_weight >= 5000 && start_weight < WEIGHT_SCALE, ERR_INVALID_WEIGHT );
+        assert!( final_weight >= 5000 && final_weight < WEIGHT_SCALE, ERR_INVALID_WEIGHT );
+        assert!( start_weight > final_weight, ERR_INVALID_WEIGHT ); 
+
+        LBPParams {
+            is_proj_on_x: proj_on_x,
+            start_weight,
+            final_weight, 
+            is_vault, 
+            target_amount,
+            total_amount_collected: 0,
+            enable_buy_with_pair: true,
+            enable_buy_with_vault: true
+        }
+    }
+
+    // Calculates the current weight of the project token.
+    // -  decline_ratio = (total_collected / target_amount)^(stablized_weight / start_weight)
+    public(friend) fun current_weight(params: &LBPParams ) : (u64, u64) {
+ 
+        // Check if fully shifted 
+        let weight_base = if ( params.total_amount_collected >= params.target_amount ) {
+            // Use final weight if the target amount is reached
+            params.final_weight
+        } else if (10000 > params.total_amount_collected  ) {
+            // Return the start weight is value is less than 10000
+            params.start_weight
+        } else {
+
+            // Calculate the weight difference
+            let weight_diff = if (params.start_weight > params.final_weight) { 
+                params.start_weight-params.final_weight
+            } else {
+                0
+            };
+
+            assert!( weight_diff > 0 , ERR_INVALID_WEIGHT);
+
+            // Ensure the accumulated amount does not exceed the target amount
+            let accumulated_amount = if (params.target_amount > params.total_amount_collected) {
+                (params.total_amount_collected as u128)
+            } else {    
+                (params.target_amount as u128)
+            };
+            let total_target_amount = (params.target_amount as u128);
+
+            // Calculate the decline ratio for weight adjustment
+            let decline_ratio = power( fixed_point64::create_from_rational(accumulated_amount, total_target_amount), fixed_point64::create_from_rational( (params.final_weight as u128), (params.start_weight as u128) ));
+            
+            // Adjust the start weight by the decline ratio to get the current weight
+            params.start_weight-(fixed_point64::multiply_u128((weight_diff as u128), decline_ratio) as u64)
+        };
+
+
+        let weight_pair = WEIGHT_SCALE-weight_base;
+
+        if ( params.is_proj_on_x ) {
+            (weight_base, weight_pair)
+        } else {
+            (weight_pair, weight_base) 
+        } 
+    }
+
+    // Only admin can set a new target amount
+    public(friend) fun set_new_target_amount(params: &mut LBPParams, new_target_amount: u64)  {
+        assert!( new_target_amount > params.total_amount_collected, ERR_TOO_LOW_AMOUNT );
+        params.target_amount = new_target_amount;
+    }
+
+    public(friend) fun enable_buy_with_pair(params: &mut LBPParams, is_enable: bool)  { 
+        params.enable_buy_with_pair = is_enable;
+    }
+
+    public(friend) fun enable_buy_with_vault(params: &mut LBPParams, is_enable: bool)  { 
+        params.enable_buy_with_vault = is_enable;
+    }
+
+    public (friend) fun is_buy(params: &LBPParams) : bool {
+        // X -> Y
+        if ( params.is_proj_on_x ) {
+            false
+        } else {
+            // Y -> X
+            true
+        }
+    }
+
+    // Verifies and adjusts the amount for weight calculation
+    public(friend) fun verify_and_adjust_amount(params: &mut LBPParams, is_buy: bool, amount_in: u64, _amount_out: u64, is_vault: bool ) {
+        // Works when the weight is not stabilized
+        if ( params.target_amount >  params.total_amount_collected) {
+            // Considered only buy transactions
+            if (is_buy) {
+                assert!( params.target_amount > amount_in, ERR_INVALID_AMOUNT );
+                if (is_vault) {
+                    assert!( params.enable_buy_with_vault, ERR_BUY_DISABLED_WITH_VAULT );
+                } else {
+                    assert!( params.enable_buy_with_pair, ERR_BUY_DISABLED_WITH_TOKEN );
+                };
+                
+                // Update the total amount collected
+                params.total_amount_collected = params.total_amount_collected+amount_in;
+            };
+        };
+    }
+
+    public fun is_vault(params: &LBPParams) : bool {
+        params.is_vault
+    }
+
+    public fun total_amount_collected(params: &LBPParams) : u64 {
+        params.total_amount_collected
+    }
+
+    public fun total_target_amount(params: &LBPParams) : u64 {
+        params.target_amount
+    }
+
+    public fun proj_on_x(params: &LBPParams) : bool {
+        params.is_proj_on_x
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/sources/utils/mock_legato.move b/packages/aptos/sources/utils/mock_legato.move
index a07227c..8d26306 100644
--- a/packages/aptos/sources/utils/mock_legato.move
+++ b/packages/aptos/sources/utils/mock_legato.move
@@ -4,10 +4,9 @@
 module legato_addr::mock_legato {
 
     use std::signer;
-    use std::string::{Self, String };  
-
-    use aptos_framework::account;
-    use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability}; 
+    use std::string::{Self };  
+ 
+    use aptos_framework::coin::{Self,   MintCapability, BurnCapability}; 
 
     const TOKEN_NAME: vector<u8> = b"Legato Token";
 
diff --git a/packages/aptos/sources/utils/mock_legato_fa.move b/packages/aptos/sources/utils/mock_legato_fa.move
index 84f42fc..e3fccb8 100644
--- a/packages/aptos/sources/utils/mock_legato_fa.move
+++ b/packages/aptos/sources/utils/mock_legato_fa.move
@@ -6,6 +6,7 @@ module legato_addr::mock_legato_fa {
     use aptos_framework::object;
     use aptos_framework::fungible_asset::{  Metadata};
     use aptos_framework::object::Object;
+    
     use legato_addr::base_fungible_asset;
     use std::string::utf8;
 
diff --git a/packages/aptos/sources/utils/mock_usdc.move b/packages/aptos/sources/utils/mock_usdc.move
index 49d6711..0351912 100644
--- a/packages/aptos/sources/utils/mock_usdc.move
+++ b/packages/aptos/sources/utils/mock_usdc.move
@@ -4,10 +4,9 @@
 module legato_addr::mock_usdc {
 
     use std::signer;
-    use std::string::{Self, String };  
+    use std::string::{Self };  
 
-    use aptos_framework::account;
-    use aptos_framework::coin::{Self, Coin, MintCapability, BurnCapability}; 
+    use aptos_framework::coin::{Self, MintCapability, BurnCapability}; 
 
     const TOKEN_NAME: vector<u8> = b"USDC Token";
 
diff --git a/packages/aptos/sources/utils/mock_usdc_fa.move b/packages/aptos/sources/utils/mock_usdc_fa.move
index 0060e19..731c625 100644
--- a/packages/aptos/sources/utils/mock_usdc_fa.move
+++ b/packages/aptos/sources/utils/mock_usdc_fa.move
@@ -5,7 +5,7 @@
 module legato_addr::mock_usdc_fa {
 
     use aptos_framework::object;
-    use aptos_framework::fungible_asset::{Self, Metadata};
+    use aptos_framework::fungible_asset::{ Metadata};
     use aptos_framework::object::Object;
     use legato_addr::base_fungible_asset;
     use std::string::utf8;
diff --git a/packages/aptos/sources/vault.move b/packages/aptos/sources/vault.move
index a47d4b6..b5b67b0 100644
--- a/packages/aptos/sources/vault.move
+++ b/packages/aptos/sources/vault.move
@@ -6,6 +6,553 @@
 
 module legato_addr::vault {
 
+    use std::signer;  
+    use aptos_framework::timestamp;
+    use std::string::{Self, String, utf8};
+    use aptos_framework::event;
+    use aptos_framework::aptos_coin::AptosCoin;
+    use aptos_framework::object::{Self, ExtendRef};
+    use aptos_framework::delegation_pool as dp;
+    use aptos_framework::coin::{Self,   MintCapability, BurnCapability}; 
+    use aptos_std::smart_vector::{Self, SmartVector};
+    use aptos_std::type_info;   
+    use aptos_std::fixed_point64::{Self, FixedPoint64};
+    use aptos_std::math_fixed64::{Self};
+    use aptos_std::table::{Self, Table};
+
+    // ======== Constants ========
+
+    const MIN_APT_TO_STAKE: u64 = 100000000; // 1 APT
     
+    const DEFAULT_EXIT_FEE: u128 = 553402322211286548; // 3% in fixed-point
+    const DEFAULT_BATCH_AMOUNT: u64 = 1500000000; // 15 APT
+
+    // ======== Errors ========
+
+    const ERR_UNAUTHORIZED: u64 = 101;
+    const ERR_INVALID_MATURITY: u64 = 102;
+    const ERR_VAULT_EXISTS: u64 = 103;
+    const ERR_VAULT_MATURED: u64 = 104;
+    const ERR_MIN_THRESHOLD: u64 = 105;
+    const ERR_INVALID_VAULT: u64 = 106;  
+    const ERR_VAULT_NOT_MATURED: u64 = 107;
+    const ERR_INVALID_ADDRESS: u64 = 108;
+    const ERR_INVALID_TYPE: u64 = 109;
+
+    // ======== Structs =========
+
+    // represent the future value at maturity date
+    struct PT_TOKEN<phantom P> has drop {}
+
+    struct VaultReserve<phantom P> has key { 
+        pt_mint: MintCapability<PT_TOKEN<P>>,
+        pt_burn: BurnCapability<PT_TOKEN<P>>,  
+        pt_total_supply: u64,  // Total supply of PT tokens
+    }
+
+    struct VaultConfig has store { 
+        maturity_time: u64,
+        vault_apy: FixedPoint64, // Vault's APY based on the average rate
+        enable_mint: bool, 
+        enable_exit: bool,
+        enable_redeem: bool
+    }
+
+    struct VaultManager has key {  
+        delegator_pools: SmartVector<address>, // Supported delegator pools
+        vault_list: SmartVector<String>,  // List of all vaults in the system
+        vault_config: Table<String, VaultConfig>,
+        extend_ref: ExtendRef, // self-sovereight identity
+        pending_stake: u64, // Pending stake
+        pending_unstake: Table<address, u64>, 
+        unlocked_amount: u64,
+        pending_withdrawal: Table<address, u64>,
+        requesters: SmartVector<address>,
+        batch_amount: u64,
+        exit_fee: FixedPoint64 
+    }
+
+
+    #[event]
+    struct NewVault has drop, store {
+        vault_name: String,
+        maturity_time: u64,
+        vault_apy: u128 // fixed-point raw value
+    }
+
+    #[event]
+    struct MintEvent has drop, store {
+        vault_name: String,
+        vault_apy: u128,
+        maturity_time: u64,
+        current_time: u64,
+        apt_in: u64,
+        pt_out: u64,
+        sender: address
+    }
+
+    #[event]
+    struct RequestRedeem has drop, store {
+        vault_name: String,
+        current_time: u64,
+        pt_amount: u64,
+        sender: address
+    }
+
+    #[event]
+    struct RequestExit has drop, store {
+        vault_name: String,
+        current_time: u64,
+        pt_amount: u64,
+        exit_amount: u64,
+        sender: address
+    }
+
+    #[event]
+    struct Withdrawn has drop, store {
+        withdraw_amount: u64,
+        requester: address
+    }
+
+    // constructor
+    fun init_module(sender: &signer) {
+
+        let constructor_ref = object::create_object(signer::address_of(sender));
+        let extend_ref = object::generate_extend_ref(&constructor_ref);
+
+        move_to(sender, VaultManager { 
+            delegator_pools: smart_vector::new<address>(), 
+            vault_list: smart_vector::new<String>(),
+            vault_config: table::new<String, VaultConfig>(),
+            extend_ref, 
+            pending_stake: 0,
+            unlocked_amount: 0,
+            pending_unstake: table::new<address, u64>(),
+            pending_withdrawal: table::new<address, u64>(),
+            requesters: smart_vector::new<address>(), 
+            batch_amount: DEFAULT_BATCH_AMOUNT,
+            exit_fee: fixed_point64::create_from_raw_value(DEFAULT_EXIT_FEE)
+        });
+    }
+
+    // ======== Public Functions =========
+
+    // Convert APT into future PT tokens equivalent to the value at the maturity date.
+    public entry fun mint<P>(sender: &signer, input_amount: u64) acquires VaultReserve, VaultManager {
+        assert!(exists<VaultReserve<P>>(@legato_addr), ERR_INVALID_VAULT);
+        assert!(coin::balance<AptosCoin>(signer::address_of(sender)) >= MIN_APT_TO_STAKE, ERR_MIN_THRESHOLD);
+
+        let type_name = type_info::type_name<P>();
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+
+        let vault_config = table::borrow_mut( &mut config.vault_config, type_name );        
+        let vault_reserve = borrow_global_mut<VaultReserve<P>>(@legato_addr); 
+        
+        // Ensure that the vault has not yet matured
+        assert!(vault_config.maturity_time > timestamp::now_seconds() , ERR_VAULT_MATURED); 
+
+        // attaches to object 
+        let input_coin = coin::withdraw<AptosCoin>(sender, input_amount);
+        if (!coin::is_account_registered<AptosCoin>(signer::address_of(&config_object_signer))) {
+            coin::register<AptosCoin>(&config_object_signer);
+        };
+
+        coin::deposit(signer::address_of(&config_object_signer), input_coin);
+
+        // Update the pending stake amount 
+        config.pending_stake = config.pending_stake+input_amount;
+
+        let vault_apy: FixedPoint64 = vault_config.vault_apy;
+        let maturity_time: u64 = vault_config.maturity_time;
+
+        // Calculate the amount of PT tokens to be sent out 
+        let pt_amount = calculate_pt_debt_amount( vault_apy, timestamp::now_seconds(), maturity_time, input_amount );
+
+        // Mint PT tokens and deposit them into the sender's account
+        let pt_coin = coin::mint<PT_TOKEN<P>>(pt_amount, &vault_reserve.pt_mint); 
+        if (!coin::is_account_registered<PT_TOKEN<P>>(signer::address_of(sender))) {
+            coin::register<PT_TOKEN<P>>(sender);
+        };
+        coin::deposit(signer::address_of(sender), pt_coin);
+
+        // Update 
+        vault_reserve.pt_total_supply = vault_reserve.pt_total_supply+pt_amount;
+
+        transfer_stake();
+
+        // Emit an event 
+        event::emit(
+            MintEvent {
+                vault_name: type_name,
+                vault_apy:  fixed_point64::get_raw_value(vault_apy),
+                maturity_time,
+                current_time: timestamp::now_seconds(),
+                apt_in: input_amount,
+                pt_out: pt_amount,
+                sender: signer::address_of(sender)
+            }
+        )
+    }
+
+    // Check if the APT in pending_stake meets the BATCH_AMOUNT, then use it to stake on a randomly supported validator.
+    public entry fun transfer_stake() acquires VaultManager {
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        if (config.pending_stake >= config.batch_amount) { 
+            
+            let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+
+            let validator_index = timestamp::now_seconds() % smart_vector::length( &config.delegator_pools );
+            let validator_address = *smart_vector::borrow( &config.delegator_pools, validator_index );
+            let pool_address = dp::get_owned_pool_address(validator_address);
+            dp::add_stake(&config_object_signer, pool_address, config.pending_stake);
+
+            config.pending_stake = 0;
+ 
+        };
+    }
+
+    // request redeem when the vault reaches its maturity date
+    public entry fun request_redeem<P>( sender: &signer, amount: u64 ) acquires VaultReserve, VaultManager {
+        assert!(exists<VaultReserve<P>>(@legato_addr), ERR_INVALID_VAULT);
+
+        let type_name = type_info::type_name<P>();
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+
+        let vault_config = table::borrow_mut( &mut config.vault_config, type_name );        
+        let vault_reserve = borrow_global_mut<VaultReserve<P>>(@legato_addr); 
+
+        // Check if the vault has matured
+        assert!(timestamp::now_seconds() > vault_config.maturity_time, ERR_VAULT_NOT_MATURED);
+
+        // Burn PT tokens on the sender's account
+        let pt_coin = coin::withdraw<PT_TOKEN<P>>(sender, amount);
+        coin::burn(pt_coin, &vault_reserve.pt_burn);
+
+        // Add the request to the withdrawal list
+        table::add(
+            &mut config.pending_unstake,
+            signer::address_of(sender),
+            amount
+        );
+
+        if ( !smart_vector::contains(&config.requesters, &(signer::address_of(sender))) ) {
+            smart_vector::push_back(&mut config.requesters, signer::address_of(sender));
+        };
+
+        // Update
+        vault_reserve.pt_total_supply = vault_reserve.pt_total_supply-amount;
+ 
+        // Emit an event 
+        event::emit(
+            RequestRedeem {
+                vault_name: type_name, 
+                current_time: timestamp::now_seconds(), 
+                pt_amount: amount,
+                sender: signer::address_of(sender)
+            }
+        )
+    }
+
+    // request exit when the vault is not matured, the amount returned 
+    // - APT = PT / e^(rt) - exit fee%
+    public entry fun request_exit<P>(sender: &signer, amount: u64 ) acquires VaultReserve, VaultManager {
+        assert!(exists<VaultReserve<P>>(@legato_addr), ERR_INVALID_VAULT);
+
+        let type_name = type_info::type_name<P>();
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+
+        let vault_config = table::borrow_mut( &mut config.vault_config, type_name );        
+        let vault_reserve = borrow_global_mut<VaultReserve<P>>(@legato_addr); 
+
+        assert!(vault_config.maturity_time > timestamp::now_seconds() , ERR_VAULT_MATURED);
+
+        // Burn PT tokens on the sender's account
+        let pt_coin = coin::withdraw<PT_TOKEN<P>>(sender, amount);
+        coin::burn(pt_coin, &vault_reserve.pt_burn);
+
+        let adjusted_amount = calculate_exit_amount(vault_config.vault_apy, timestamp::now_seconds(), vault_config.maturity_time, amount);
+
+        // Add the request to the withdrawal list
+        table::add(
+            &mut config.pending_unstake,
+            signer::address_of(sender),
+            adjusted_amount
+        );
+
+        if ( !smart_vector::contains(&config.requesters, &(signer::address_of(sender))) ) {
+            smart_vector::push_back(&mut config.requesters, signer::address_of(sender));
+        };
+
+        // Update
+        vault_reserve.pt_total_supply = vault_reserve.pt_total_supply-amount;
+
+        // Emit an event 
+        event::emit(
+            RequestExit {
+                vault_name: type_name, 
+                current_time: timestamp::now_seconds(), 
+                pt_amount: amount,
+                exit_amount: adjusted_amount,
+                sender: signer::address_of(sender)
+            }
+        )
+    }
+
+    // Calculate the amount of PT debt to be sent out using the formula P = S * e^(rt)
+    public fun calculate_pt_debt_amount(apy: FixedPoint64, from_timestamp: u64, to_timestamp: u64, input_amount: u64): u64 {
+        
+        // Calculate time duration in years
+        let time = fixed_point64::create_from_rational( ((to_timestamp-from_timestamp) as u128), 31556926 );
+
+        // Calculate rt (rate * time)
+        let rt = math_fixed64::mul_div( apy, time, fixed_point64::create_from_u128(1));
+        let multiplier = math_fixed64::exp(rt);
+
+        // the final PT debt amount
+        ( fixed_point64::multiply_u128( (input_amount as u128), multiplier  ) as u64 )
+    }
+
+    // Calculate the amount when exiting using the formula S = P / e^(rt
+    public fun calculate_exit_amount(apy: FixedPoint64, from_timestamp: u64, to_timestamp: u64, output_amount: u64) : u64 {
+
+        let time = fixed_point64::create_from_rational( ((to_timestamp-from_timestamp) as u128), 31556926 );
+
+        let rt = math_fixed64::mul_div( apy, time, fixed_point64::create_from_u128(1));
+        let denominator = math_fixed64::exp(rt);
+
+        ( fixed_point64::divide_u128( (output_amount as u128), denominator  ) as u64 )
+    }
+
+    #[view]
+    // get PT balance from the given account
+    public fun get_pt_balance<P>(account: address): u64 {
+        coin::balance<PT_TOKEN<P>>(account)
+    }
+
+    #[view]
+    public fun get_config_object_address() : address  acquires VaultManager  {
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        signer::address_of(&config_object_signer)
+    }
+
+    // ======== Only Governance =========
+
+    // Create a new vault
+    public entry fun new_vault<P>(
+        sender: &signer, 
+        maturity_time: u64,
+        apy_numerator: u128,
+        apy_denominator: u128
+    ) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        // Check if the vault already exists 
+        assert!(!vault_exist<P>(@legato_addr), ERR_VAULT_EXISTS);
+        // The maturity date should not have passed.
+        assert!(maturity_time > timestamp::now_seconds()+86400, ERR_INVALID_MATURITY);
+        // Ensure the current maturity date is greater than that of the previous vault.
+        assert_maturity_time<P>(@legato_addr, maturity_time);
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+
+        // Construct the name and symbol of the PT token
+        let type_name = type_info::type_name<P>();
+        let index = string::index_of(&type_name, &utf8(b"vault_token_name::"));
+        assert!(index != string::length(&type_name) , ERR_INVALID_TYPE );
+        let token_name = string::utf8(b"PT-");
+        let token_symbol =  string::sub_string( &type_name, index+18, string::length(&type_name));
+        string::append( &mut token_name, token_symbol);
+
+        // Initialize vault's PT token 
+        let (pt_burn, freeze_cap, pt_mint) = coin::initialize<PT_TOKEN<P>>(
+            sender,
+            token_name,
+            token_symbol,
+            8, // Number of decimal places
+            true, // token is fungible
+        );
+
+        coin::destroy_freeze_cap(freeze_cap);
+
+        let vault_apy = fixed_point64::create_from_rational( apy_numerator, apy_denominator );
+
+        let vault_config = VaultConfig { 
+            maturity_time,
+            vault_apy,
+            enable_mint: true,
+            enable_redeem: true,
+            enable_exit: true
+        };
+
+        move_to(
+            sender,
+            VaultReserve<P> {
+                pt_mint,
+                pt_burn, 
+                pt_total_supply: 0
+            },
+        );
+
+        // Add the vault name to the list.
+        smart_vector::push_back(&mut config.vault_list, type_name);
+
+        // Add the configuration to the config table.
+        table::add(
+            &mut config.vault_config,
+            type_name,
+            vault_config
+        );
+
+        // Emit an event 
+        event::emit(
+            NewVault {
+                vault_name: type_name,
+                maturity_time,
+                vault_apy: fixed_point64::get_raw_value(vault_apy)
+            }
+        )
+
+    }
+
+    // Update the batch amount for staking.
+    public entry fun update_batch_amount(sender: &signer, new_amount: u64) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        config.batch_amount = new_amount;
+    }
+
+    // Add a validator to the whitelist.
+    public entry fun add_whitelist(sender: &signer, whitelist_address: address) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        smart_vector::push_back(&mut config.delegator_pools, whitelist_address);
+    }
+
+    // Remove a validator from the whitelist.
+    public entry fun remove_whitelist(sender: &signer, whitelist_address: address) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        let (found, idx) = smart_vector::index_of<address>(&config.delegator_pools, &whitelist_address);
+        assert!(  found , ERR_INVALID_ADDRESS);
+        smart_vector::swap_remove<address>(&mut config.delegator_pools, idx );
+    }
+
+    // Admin proceeds to unlock APT for further withdrawal according to the request table.
+    public entry fun admin_proceed_unstake(sender: &signer, validator_address: address) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        let total_requester = smart_vector::length( &config.requesters );
+        
+        if (total_requester > 0) {
+
+            let requester_count = 0;
+            let total_unlock = 0;
+
+            while ( requester_count < total_requester) {
+                let requester_address = *smart_vector::borrow( &config.requesters, requester_count );
+                let unlock_amount = table::remove( &mut config.pending_unstake, requester_address);
+
+                if (unlock_amount > 0) {
+                    table::add(
+                        &mut config.pending_withdrawal,
+                        requester_address,
+                        unlock_amount
+                    );
+
+                    total_unlock = total_unlock+unlock_amount;
+                };
+
+                requester_count = requester_count+1;
+            };
+
+            let pool_address = dp::get_owned_pool_address(validator_address);
+            dp::unlock(&config_object_signer, pool_address, total_unlock);
+
+            config.unlocked_amount = config.unlocked_amount + total_unlock;
+        };
+        
+    }
+
+    // Admin proceeds with withdrawal of unlocked APT tokens.
+    public entry fun admin_proceed_withdrawal(sender: &signer,  validator_address: address) acquires VaultManager {
+        assert!( signer::address_of(sender) == @legato_addr , ERR_UNAUTHORIZED);
+
+        let config = borrow_global_mut<VaultManager>(@legato_addr);
+        let config_object_signer = object::generate_signer_for_extending(&config.extend_ref);
+        
+        // Proceed with withdrawal if there are unlocked tokens.
+        if ( config.unlocked_amount > 0) {
+
+            // Withdraw the unlocked APT tokens from the delegation pool.
+            let pool_address = dp::get_owned_pool_address(validator_address);
+            dp::withdraw(&config_object_signer, pool_address, config.unlocked_amount);
+
+            // Retrieve the total withdrawn APT tokens.
+            let total_apt_withdrawn = coin::balance<AptosCoin>(  signer::address_of(&config_object_signer)  );
+
+            let total_requester = smart_vector::length( &config.requesters );
+            let requester_count = 0;
+
+            // Loop through each requester and process their withdrawal.
+            while ( requester_count < total_requester) {
+                let requester_address = *smart_vector::borrow( &config.requesters, requester_count );
+                let withdraw_amount = table::remove( &mut config.pending_withdrawal, requester_address);
+
+                if (withdraw_amount > 0) {
+                    // Ensure withdrawal amount does not exceed the available balance.
+                    if (withdraw_amount > total_apt_withdrawn) {
+                        withdraw_amount = total_apt_withdrawn;
+                    };
+                   let apt_coin = coin::withdraw<AptosCoin>(&config_object_signer, withdraw_amount);
+                   coin::deposit(requester_address, apt_coin);
+
+                    // Emit an event 
+                    event::emit(
+                        Withdrawn {
+                            withdraw_amount,
+                            requester: requester_address
+                        }
+                    );
+
+                };
+
+                requester_count = requester_count+1;
+            };
+
+             // Reset the unlocked amount after processing withdrawals.
+            config.unlocked_amount = 0;
+        };
+
+    }
+
+
+    // ======== Internal Functions =========
+
+    fun vault_exist<P>(addr: address): bool {
+        exists<VaultReserve<P>>(addr)
+    }
+
+    fun assert_maturity_time<P>(addr: address, maturity_time: u64) acquires VaultManager {
+
+        let global_config = borrow_global_mut<VaultManager>(addr);
+        if ( smart_vector::length( &global_config.vault_list ) > 0) { 
+            let recent_vault_name = *smart_vector::borrow( &global_config.vault_list, smart_vector::length( &global_config.vault_list )-1 );
+            let recent_vault_config = table::borrow_mut( &mut global_config.vault_config, recent_vault_name );
+            assert!(  maturity_time > recent_vault_config.maturity_time, ERR_INVALID_MATURITY  );
+        };
+
+    }
+ 
+    #[test_only]
+    public fun init_module_for_testing(deployer: &signer) {
+        init_module(deployer)
+    }
 
 }
\ No newline at end of file
diff --git a/packages/aptos/tests/amm_tests.move b/packages/aptos/tests/amm_tests.move
new file mode 100644
index 0000000..62bc24a
--- /dev/null
+++ b/packages/aptos/tests/amm_tests.move
@@ -0,0 +1,187 @@
+
+
+#[test_only]
+module legato_addr::amm_tests {
+
+    use std::string::utf8;
+    use std::signer;
+
+    use aptos_framework::account;
+    use aptos_framework::coin::{Self, MintCapability};
+
+    use legato_addr::mock_usdc::{Self, USDC_TOKEN};
+    use legato_addr::mock_legato::{Self, LEGATO_TOKEN};
+    use legato_addr::amm::{Self, LP};
+
+    // When setting up a 90/10 pool of ~$100k
+    // Initial allocation at 1 XBTC = 50,000 USDC
+    const XBTC_AMOUNT: u64 = 180_000_000; // 90% at 1.8 BTC
+    const USDC_AMOUNT_90_10: u64 = 10000_000000; // 10% at 10,000 USDC
+
+    // When setting up a 50/50 pool of ~$100k
+    // Initial allocation at 1 LEGATO = 0.001 USDC
+    const LEGATO_AMOUNT: u64  = 50000000_00000000; // 50,000,000 LEGATO
+    const USDC_AMOUNT_50_50: u64 = 50000_000000; // 50,000 USDC
+
+    // test coins
+
+    struct XBTC_TOKEN {}
+
+    // Registering pools
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_register_pools(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+    }
+
+    // Swapping tokens
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_swap_usdc_for_xbtc(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        mock_usdc::mint( user , 100_000000 ); // 100 USDC
+        amm::swap<USDC_TOKEN, XBTC_TOKEN>(user, 100_000000, 0); // 100 USDC
+ 
+        assert!(coin::balance<XBTC_TOKEN>(signer::address_of(user)) == 197_906, 1); // 0.00197906 XBTC at a rate of 1 BTC = 52405 USDT
+    }
+
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_swap_xbtc_for_usdc(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        amm::swap<XBTC_TOKEN, USDC_TOKEN>(lp_provider, 100000, 0);  // 0.001 XBTC
+
+        assert!(coin::balance<USDC_TOKEN>(signer::address_of(lp_provider)) == 49_613272, 1); // 49.613272 USDC at a rate of 1 BTC = 51465 USDT
+    }
+
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_swap_usdc_for_legato(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        mock_usdc::mint( user , 250_000000 ); // 250 USDC
+        amm::swap<USDC_TOKEN, LEGATO_TOKEN>(user, 250_000000, 0); // 250 USDC
+
+        assert!(coin::balance<LEGATO_TOKEN>(signer::address_of(user)) == 247518_59598004, 1); // 247,518 LEGATO at a rate of 0.001010028 LEGATO/USDC
+    }
+
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_swap_legato_for_usdc(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        mock_legato::mint( user, 100000_00000000 ); // 100,000 LEGATO
+        amm::swap<LEGATO_TOKEN, USDC_TOKEN>(user, 100000_00000000, 0); // 100,000 LEGATO
+
+        assert!(coin::balance<USDC_TOKEN>(signer::address_of(user)) == 99_302388, 1); // 99.302 USDC at a rate of 0.00099302 LEGATO/USDC
+    }
+
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_remove_liquidity(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        mock_usdc::mint(lp_provider, 5000_000000);
+
+        amm::add_liquidity<USDC_TOKEN, XBTC_TOKEN>(
+            lp_provider,
+            5000_000000, // 5000 USDC
+            1,
+            15000000, // 0.15 XBTC
+            1
+        );
+
+        let lp_balance = coin::balance<LP<USDC_TOKEN, XBTC_TOKEN>>( signer::address_of(lp_provider) );
+
+        amm::remove_liquidity<USDC_TOKEN, XBTC_TOKEN>(
+            lp_provider,
+            lp_balance
+        );
+        
+
+    }
+
+    #[test_only]
+    public fun register_pools(deployer: &signer, lp_provider: &signer, user: &signer) {
+        
+        amm::init_module_for_testing(deployer);
+        mock_usdc::init_module_for_testing(deployer);
+        mock_legato::init_module_for_testing(deployer);
+
+        let deployer_address = signer::address_of(deployer);
+        let lp_provider_address = signer::address_of(lp_provider);
+        let user_address = signer::address_of(user);
+
+        account::create_account_for_test(lp_provider_address);  
+        account::create_account_for_test(deployer_address); 
+        account::create_account_for_test(user_address); 
+        account::create_account_for_test( amm::get_config_object_address() ); 
+
+        // USDC
+        mock_usdc::mint( deployer , USDC_AMOUNT_50_50+USDC_AMOUNT_90_10 ); 
+        assert!( (coin::balance<USDC_TOKEN>(  deployer_address )) == USDC_AMOUNT_50_50+USDC_AMOUNT_90_10, 0 );
+
+        // LEGATO 
+        mock_legato::mint( deployer , LEGATO_AMOUNT ); 
+        assert!( (coin::balance<LEGATO_TOKEN>(  deployer_address )) == LEGATO_AMOUNT, 0 );
+
+        // XBTC
+        coin::register<XBTC_TOKEN>(deployer);
+        coin::register<XBTC_TOKEN>(lp_provider);
+        coin::register<XBTC_TOKEN>(user);
+        let xbtc_mint_cap = register_coin<XBTC_TOKEN>(deployer, b"BTC", b"BTC", 8);
+        coin::deposit(deployer_address, coin::mint<XBTC_TOKEN>(XBTC_AMOUNT, &xbtc_mint_cap));
+        coin::deposit(lp_provider_address, coin::mint<XBTC_TOKEN>(XBTC_AMOUNT, &xbtc_mint_cap));
+        coin::destroy_mint_cap(xbtc_mint_cap);
+        assert!(coin::balance<XBTC_TOKEN>(deployer_address) == XBTC_AMOUNT, 1);
+
+        // Setup a 10/90 pool
+        amm::register_pool<USDC_TOKEN, XBTC_TOKEN>(deployer, 1000, 9000);
+
+        amm::add_liquidity<USDC_TOKEN, XBTC_TOKEN>(
+            deployer,
+            USDC_AMOUNT_90_10,
+            1,
+            XBTC_AMOUNT,
+            1
+        );
+
+        assert!(coin::balance<USDC_TOKEN>(deployer_address) == USDC_AMOUNT_50_50, 2);
+        assert!(coin::balance<XBTC_TOKEN>(deployer_address) == 0, 3);
+
+        assert!(coin::balance<LP<USDC_TOKEN, XBTC_TOKEN>>(deployer_address) == 2_68994649, 4);
+
+        // Setup a 50/50 pool
+        amm::register_pool<USDC_TOKEN, LEGATO_TOKEN>(deployer, 5000, 5000);
+
+        amm::add_liquidity<USDC_TOKEN, LEGATO_TOKEN>(
+            deployer,
+            USDC_AMOUNT_50_50,
+            1,
+            LEGATO_AMOUNT,
+            1
+        );
+
+        assert!(coin::balance<USDC_TOKEN>(deployer_address) == 0, 5);
+        assert!(coin::balance<LEGATO_TOKEN>(deployer_address) == 0, 6);
+
+    }
+
+
+    #[test_only]
+    fun register_coin<CoinType>(
+        coin_admin: &signer,
+        name: vector<u8>,
+        symbol: vector<u8>,
+        decimals: u8
+    ): MintCapability<CoinType> {
+        let (burn_cap, freeze_cap, mint_cap) =
+            coin::initialize<CoinType>(
+                coin_admin,
+                utf8(name),
+                utf8(symbol),
+                decimals,
+                true);
+        coin::destroy_freeze_cap(freeze_cap);
+        coin::destroy_burn_cap(burn_cap);
+
+        mint_cap
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/tests/aptos_system/delegation_pool_tests.move b/packages/aptos/tests/aptos_system/delegation_pool_tests.move
new file mode 100644
index 0000000..ec08419
--- /dev/null
+++ b/packages/aptos/tests/aptos_system/delegation_pool_tests.move
@@ -0,0 +1,205 @@
+
+#[test_only]
+module legato_addr::delegation_pool_tests {
+
+    // use std::features;
+    use std::signer;
+
+    use aptos_std::bls12381;
+    use aptos_std::stake;
+    use aptos_std::vector;
+
+    use aptos_framework::account;
+    use aptos_framework::aptos_coin::AptosCoin;
+    use aptos_framework::coin;
+    use aptos_framework::reconfiguration;
+    use aptos_framework::delegation_pool as dp;
+    use aptos_framework::timestamp;
+
+    #[test_only]
+    const EPOCH_DURATION: u64 = 60;
+
+    #[test_only]
+    const LOCKUP_CYCLE_SECONDS: u64 = 3600;
+
+    #[test_only]
+    const MODULE_EVENT: u64 = 26;
+
+    #[test_only]
+    const DELEGATION_POOLS: u64 = 11;
+
+    #[test_only]
+    const OPERATOR_BENEFICIARY_CHANGE: u64 = 39;
+
+    #[test_only]
+    const COMMISSION_CHANGE_DELEGATION_POOL: u64 = 42;
+
+    #[test_only]
+    const ONE_APT: u64 = 100000000; // 1x10**8
+
+    #[test_only]
+    const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1;
+    const VALIDATOR_STATUS_ACTIVE: u64 = 2;
+    const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3;
+    const VALIDATOR_STATUS_INACTIVE: u64 = 4;
+
+
+    #[test(aptos_framework = @aptos_framework, validator = @0x123)]
+    public entry fun test_validator_staking(
+        aptos_framework: &signer,
+        validator: &signer,
+    ) {
+        initialize_for_test(aptos_framework);
+        let (_sk, pk, pop) = generate_identity();
+        initialize_test_validator(&pk, &pop, validator, 100 * ONE_APT, true, true);
+
+        // Validator has a lockup now that they've joined the validator set.
+        let validator_address = signer::address_of(validator);
+        let pool_address = dp::get_owned_pool_address(validator_address);
+        assert!(stake::get_remaining_lockup_secs(pool_address) == LOCKUP_CYCLE_SECONDS, 1);
+
+        // Validator adds more stake while already being active.
+        // The added stake should go to pending_active to wait for activation when next epoch starts.
+        stake::mint(validator, 900 * ONE_APT);
+        dp::add_stake(validator, pool_address, 100 * ONE_APT);
+        assert!(coin::balance<AptosCoin>(validator_address) == 800 * ONE_APT, 2);
+        stake::assert_validator_state(pool_address, 100 * ONE_APT, 0, 100 * ONE_APT, 0, 0);
+
+        // Pending_active stake is activated in the new epoch.
+        // Rewards of 1 coin are also distributed for the existing active stake of 100 coins.
+        end_epoch();
+        assert!(stake::get_validator_state(pool_address) == VALIDATOR_STATUS_ACTIVE, 3);
+        stake::assert_validator_state(pool_address, 201 * ONE_APT, 0, 0, 0, 0);
+
+        // Request unlock of 100 coins. These 100 coins are moved to pending_inactive and will be unlocked when the
+        // current lockup expires.
+        dp::unlock(validator, pool_address, 100 * ONE_APT);
+        stake::assert_validator_state(pool_address, 10100000001, 0, 0, 9999999999, 0);
+
+        // Enough time has passed so the current lockup cycle should have ended.
+        // The first epoch after the lockup cycle ended should automatically move unlocked (pending_inactive) stake
+        // to inactive.
+        timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS);
+        end_epoch();
+        // Rewards were also minted to pending_inactive, which got all moved to inactive.
+        stake::assert_validator_state(pool_address, 10201000001, 10099999998, 0, 0, 0);
+        // Lockup is renewed and validator is still active.
+        assert!(stake::get_validator_state(pool_address) == VALIDATOR_STATUS_ACTIVE, 4);
+        assert!(stake::get_remaining_lockup_secs(pool_address) == LOCKUP_CYCLE_SECONDS, 5);
+
+        // Validator withdraws from inactive stake multiple times.
+        dp::withdraw(validator, pool_address, 50 * ONE_APT);
+        assert!(coin::balance<AptosCoin>(validator_address) == 84999999999, 6);
+        stake::assert_validator_state(pool_address, 10201000001, 5099999999, 0, 0, 0);
+        dp::withdraw(validator, pool_address, 51 * ONE_APT);
+        assert!(coin::balance<AptosCoin>(validator_address) == 90099999998, 7);
+        stake::assert_validator_state(pool_address, 10201000001, 0, 0, 0, 0);
+
+        // Enough time has passed again and the validator's lockup is renewed once more. Validator is still active.
+        timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS);
+        end_epoch();
+
+        assert!(stake::get_validator_state(pool_address) == VALIDATOR_STATUS_ACTIVE, 8);
+        assert!(stake::get_remaining_lockup_secs(pool_address) == LOCKUP_CYCLE_SECONDS, 9);
+    }
+
+    #[test_only]
+    public fun initialize_for_test(aptos_framework: &signer) {
+        initialize_for_test_custom(
+            aptos_framework,
+            100 * ONE_APT,
+            10000 * ONE_APT,
+            LOCKUP_CYCLE_SECONDS,
+            true,
+            1,
+            100,
+            1000000
+        );
+    }
+
+    #[test_only]
+    public fun end_epoch() {
+        stake::end_epoch();
+        reconfiguration::reconfigure_for_test_custom();
+    }
+
+    // Convenient function for setting up all required stake initializations.
+    #[test_only]
+    public fun initialize_for_test_custom(
+        aptos_framework: &signer,
+        minimum_stake: u64,
+        maximum_stake: u64,
+        recurring_lockup_secs: u64,
+        allow_validator_set_change: bool,
+        rewards_rate_numerator: u64,
+        rewards_rate_denominator: u64,
+        voting_power_increase_limit: u64,
+    ) {
+        account::create_account_for_test(signer::address_of(aptos_framework));
+        stake::initialize_for_test_custom(
+            aptos_framework,
+            minimum_stake,
+            maximum_stake,
+            recurring_lockup_secs,
+            allow_validator_set_change,
+            rewards_rate_numerator,
+            rewards_rate_denominator,
+            voting_power_increase_limit
+        );
+        reconfiguration::initialize_for_test(aptos_framework);
+        // features::change_feature_flags(aptos_framework, vector[
+        //     DELEGATION_POOLS, 
+        //     MODULE_EVENT,
+        //     OPERATOR_BENEFICIARY_CHANGE,
+        //     COMMISSION_CHANGE_DELEGATION_POOL
+        // ], vector[]);
+    }
+
+    #[test_only]
+    public fun generate_identity(): (bls12381::SecretKey, bls12381::PublicKey, bls12381::ProofOfPossession) {
+        let (sk, pkpop) = bls12381::generate_keys();
+        let pop = bls12381::generate_proof_of_possession(&sk);
+        let unvalidated_pk = bls12381::public_key_with_pop_to_normal(&pkpop);
+        (sk, unvalidated_pk, pop)
+    }
+
+    #[test_only]
+    public fun initialize_test_validator(
+        public_key: &bls12381::PublicKey,
+        proof_of_possession: &bls12381::ProofOfPossession,
+        validator: &signer,
+        amount: u64,
+        should_join_validator_set: bool,
+        should_end_epoch: bool
+    ) {
+        let validator_address = signer::address_of(validator);
+        if (!account::exists_at(signer::address_of(validator))) {
+            account::create_account_for_test(validator_address);
+        };
+
+        dp::initialize_delegation_pool(validator, 0, vector::empty<u8>());
+        validator_address = dp::get_owned_pool_address(validator_address);
+
+        let pk_bytes = bls12381::public_key_to_bytes(public_key);
+        let pop_bytes = bls12381::proof_of_possession_to_bytes(proof_of_possession);
+        stake::rotate_consensus_key(validator, validator_address, pk_bytes, pop_bytes);
+
+        if (amount > 0) {
+            mint_and_add_stake(validator, amount);
+        };
+
+        if (should_join_validator_set) {
+            stake::join_validator_set(validator, validator_address);
+        };
+        if (should_end_epoch) {
+            end_epoch();
+        };
+    }
+
+    #[test_only]
+    public fun mint_and_add_stake(account: &signer, amount: u64) {
+        stake::mint(account, amount);
+        dp::add_stake(account, dp::get_owned_pool_address(signer::address_of(account)), amount);
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/tests/lbp_usdc_tests.move b/packages/aptos/tests/lbp_usdc_tests.move
new file mode 100644
index 0000000..0713cd6
--- /dev/null
+++ b/packages/aptos/tests/lbp_usdc_tests.move
@@ -0,0 +1,108 @@
+// Test launching new tokens (LEGATO) on LBP when using USDC as the settlement assets
+
+#[test_only]
+module legato_addr::lbp_usdc_tests {
+
+    use std::signer;
+
+    use aptos_framework::account;
+    use aptos_framework::coin::{Self};
+
+    use legato_addr::mock_usdc::{Self, USDC_TOKEN};
+    use legato_addr::mock_legato::{Self, LEGATO_TOKEN};
+    use legato_addr::amm::{Self, LP};
+
+    const LEGATO_AMOUNT: u64 = 60000000_00000000; // 60 mil. LEGATO
+    const USDC_AMOUNT: u64 = 500_000_000; // 500 USDC for bootstrap LP
+
+    // Registering pools
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_register_pools(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+    }
+
+    #[test(deployer = @legato_addr, lp_provider = @0xdead, user = @0xbeef )]
+    fun test_trade_until_stabilized(deployer: &signer, lp_provider: &signer, user: &signer) {
+        register_pools(deployer, lp_provider, user);
+
+        trade_until_stabilized(user);
+    }
+
+    #[test_only]
+    public fun register_pools(deployer: &signer, lp_provider: &signer, user: &signer) {
+        
+        amm::init_module_for_testing(deployer);
+        mock_usdc::init_module_for_testing(deployer);
+        mock_legato::init_module_for_testing(deployer);
+
+        let deployer_address = signer::address_of(deployer);
+        let lp_provider_address = signer::address_of(lp_provider);
+        let user_address = signer::address_of(user);
+
+        account::create_account_for_test(lp_provider_address);  
+        account::create_account_for_test(deployer_address); 
+        account::create_account_for_test(user_address); 
+        account::create_account_for_test( amm::get_config_object_address() ); 
+
+        // USDC
+        mock_usdc::mint( deployer , USDC_AMOUNT  ); 
+        assert!( (coin::balance<USDC_TOKEN>(  deployer_address )) == USDC_AMOUNT, 0 );
+
+        // LEGATO 
+        mock_legato::mint( deployer , LEGATO_AMOUNT ); 
+        assert!( (coin::balance<LEGATO_TOKEN>(  deployer_address )) == LEGATO_AMOUNT, 0 );
+    
+        amm::register_lbp_pool<USDC_TOKEN, LEGATO_TOKEN >( 
+            deployer, 
+            false, // LEGATO is on Y
+            9000,
+            6000, 
+            false,
+            50000_000000 // 50,000 USDC
+        );
+
+        amm::add_liquidity<USDC_TOKEN, LEGATO_TOKEN>(
+            deployer,
+            USDC_AMOUNT,
+            1,
+            LEGATO_AMOUNT,
+            1
+        );
+
+    }
+
+    #[test_only]
+    public fun trade_until_stabilized( user: &signer) {
+        
+        // Buy LEGATO with 1000 USDC for 50 times
+        let counter=  0;
+        let current_weight_legato = 9000;
+        let current_weight_usdc = 1000;
+
+        mock_usdc::mint( user, 50000_000000 ); // 50,000 USDC
+
+        while ( counter < 50) {
+            amm::swap<USDC_TOKEN, LEGATO_TOKEN>(user, 1000_000000, 0); // 1,000 USDC
+            let current_balance = coin::balance<LEGATO_TOKEN>(signer::address_of(user));
+            mock_legato::burn( user, current_balance );
+
+            // Check weights
+            let (weight_usdc, weight_legato, _, _ ) = amm::lbp_info<USDC_TOKEN, LEGATO_TOKEN>();
+            // Keep lowering
+            assert!( current_weight_legato >= weight_legato, counter );
+            assert!( current_weight_usdc <= weight_usdc, counter );
+
+            current_weight_legato = weight_legato;
+            current_weight_usdc = weight_usdc;
+
+            counter = counter+1;
+        };
+
+        // Check final weights
+        let (weight_usdc, weight_legato, _, _ ) = amm::lbp_info<USDC_TOKEN, LEGATO_TOKEN>();
+        assert!( weight_usdc == 4000, 0 );
+        assert!( weight_legato == 6000, 1 );
+
+    }
+
+}
\ No newline at end of file
diff --git a/packages/aptos/tests/vault_tests.move b/packages/aptos/tests/vault_tests.move
new file mode 100644
index 0000000..360e6a6
--- /dev/null
+++ b/packages/aptos/tests/vault_tests.move
@@ -0,0 +1,296 @@
+ 
+#[test_only]
+module legato_addr::vault_tests {
+
+    use std::features;
+    use std::signer;
+
+    use aptos_std::bls12381;
+    use aptos_std::stake;
+    use aptos_std::vector;
+
+    use aptos_framework::account;
+    use aptos_framework::aptos_coin::AptosCoin;
+    use aptos_framework::coin;
+    use aptos_framework::reconfiguration;
+    use aptos_framework::delegation_pool as dp;
+    use aptos_framework::timestamp;
+ 
+    use legato_addr::vault;
+    use legato_addr::vault_token_name::{MAR_2024, JUN_2024};
+    
+    #[test_only]
+    const EPOCH_DURATION: u64 = 86400;
+
+    #[test_only]
+    const ONE_APT: u64 = 100000000; // 1x10**8
+
+    #[test_only]
+    const LOCKUP_CYCLE_SECONDS: u64 = 3600;
+
+    #[test_only]
+    const DELEGATION_POOLS: u64 = 11;
+
+    #[test_only]
+    const MODULE_EVENT: u64 = 26;
+
+    #[test(deployer = @legato_addr,aptos_framework = @aptos_framework, validator_1 = @0xdead, validator_2 = @0x1111, user_1 = @0xbeef, user_2 = @0xfeed)]
+    fun test_mint_redeem(
+        deployer: &signer,
+        aptos_framework: &signer,
+        validator_1: &signer, 
+        validator_2: &signer,
+        user_1: &signer,
+        user_2: &signer
+    ) {
+        initialize_for_test(aptos_framework, validator_1, validator_2);
+        
+        // Setup Legato vaults
+        setup_vaults(deployer, signer::address_of(validator_1), signer::address_of(validator_2));
+
+        // Prepare test accounts
+        create_test_accounts( deployer, user_1, user_2);
+
+        // Mint APT tokens
+        stake::mint(user_1, 100 * ONE_APT);
+        stake::mint(user_2, 200 * ONE_APT); 
+
+        assert!(coin::balance<AptosCoin>(signer::address_of(user_1)) == 100 * ONE_APT, 0);
+        assert!(coin::balance<AptosCoin>(signer::address_of(user_2)) == 200 * ONE_APT, 1);
+    
+        // Stake PT tokens.
+        vault::mint<MAR_2024>( user_1, 100 * ONE_APT);
+        vault::mint<MAR_2024>( user_2, 200 * ONE_APT);
+
+        // Check PT token balances.
+        let pt_amount_1 = vault::get_pt_balance<MAR_2024>(signer::address_of(user_1));
+        let pt_amount_2 = vault::get_pt_balance<MAR_2024>(signer::address_of(user_2));
+        assert!( pt_amount_1 == 101_37836771, 2); // 101.378 PT
+        assert!( pt_amount_2 == 202_75673543, 3); // 202.756 PT
+
+        // Fast forward 100 epochs.
+        let i:u64=1;  
+        while(i <= 100) 
+        {
+            timestamp::fast_forward_seconds(EPOCH_DURATION);
+            end_epoch();
+            i=i+1; // Incrementing the counter
+        };
+
+        // Check the staked amount.
+        let pool_address = dp::get_owned_pool_address(signer::address_of(validator_1) );
+        let (pool_staked_amount,_,_) = dp::get_stake(pool_address , vault::get_config_object_address() );
+
+        assert!( pool_staked_amount == 331_20350570, 4); // 331.203 APT 
+
+        // Request redemption of PT tokens.
+        vault::request_redeem<MAR_2024>( user_1, 101_37836771 ); 
+
+        // Perform admin tasks.
+        vault::admin_proceed_unstake(deployer, signer::address_of(validator_1)  );
+        
+        // Fast forward one epoch.
+        timestamp::fast_forward_seconds(EPOCH_DURATION);
+        end_epoch();
+
+        vault::admin_proceed_withdrawal( deployer , signer::address_of(validator_1));
+
+        // Verify has the correct amount of APT tokens after redemption.
+        let apt_amount = coin::balance<AptosCoin>(signer::address_of(user_1)); 
+        assert!( apt_amount == 101_37836770, 5);
+    }
+
+    #[test(deployer = @legato_addr,aptos_framework = @aptos_framework, validator_1 = @0xdead, validator_2 = @0x1111, user_1 = @0xbeef, user_2 = @0xfeed)]
+    fun test_mint_exit(
+        deployer: &signer,
+        aptos_framework: &signer,
+        validator_1: &signer, 
+        validator_2: &signer,
+        user_1: &signer,
+        user_2: &signer
+    ) {
+        initialize_for_test(aptos_framework, validator_1, validator_2); 
+
+        // Setup Legato vaults
+        setup_vaults(deployer, signer::address_of(validator_1), signer::address_of(validator_2));
+
+        // Prepare test accounts
+        create_test_accounts( deployer, user_1, user_2);
+
+        // Stake PT tokens.
+        stake::mint(user_1, 100 * ONE_APT);
+        stake::mint(user_2, 200 * ONE_APT); 
+
+        vault::mint<MAR_2024>( user_1, 100 * ONE_APT);
+        vault::mint<MAR_2024>( user_2, 200 * ONE_APT);
+
+        // Fast forward 10 epochs.
+        let i:u64=1;  
+        while(i <= 10) 
+        {
+            timestamp::fast_forward_seconds(EPOCH_DURATION);
+            end_epoch();
+            i=i+1; // Incrementing the counter
+        };
+
+        let amount_before = vault::get_pt_balance<MAR_2024>(signer::address_of(user_1));
+        assert!( amount_before == 101_37836771, 0); // 101.378 PT
+        
+        // Request exit
+        vault::request_exit<MAR_2024>( user_1, amount_before ); 
+
+        // Perform admin tasks.
+        vault::admin_proceed_unstake(deployer, signer::address_of(validator_1)  );
+        
+        // Fast forward one epoch.
+        timestamp::fast_forward_seconds(EPOCH_DURATION);
+        end_epoch();
+
+        vault::admin_proceed_withdrawal( deployer , signer::address_of(validator_1));
+
+        let apt_amount = coin::balance<AptosCoin>(signer::address_of(user_1)); 
+        assert!( apt_amount == 100_13708438, 0); // 100.137 PT
+        
+    }
+
+    #[test_only]
+    public fun setup_vaults(sender: &signer, validator_1: address, validator_2: address) {
+
+        vault::init_module_for_testing(sender);
+
+        // Update the batch amount to 200 APT.
+        vault::update_batch_amount(sender, 200 * ONE_APT );
+
+        // Add the validators to the whitelist.
+        vault::add_whitelist(sender, validator_1);
+        vault::add_whitelist(sender, validator_2);
+
+        // Vault #1 matures in 100 epochs.
+        let maturity_1 = timestamp::now_seconds()+(100*EPOCH_DURATION);
+
+        // Create Vault #1 with an APY of 5%.
+        vault::new_vault<MAR_2024>(sender, maturity_1, 5, 100);
+
+        // Vault #2 matures in 200 epochs.
+        let maturity_2 = timestamp::now_seconds()+(200*EPOCH_DURATION);
+
+        // Create Vault #2 with an APY of 4%.
+        vault::new_vault<JUN_2024>(sender, maturity_2, 4, 100);
+    }
+
+    #[test_only]
+    public fun create_test_accounts(
+        deployer: &signer,
+        user_1: &signer,
+        user_2: &signer
+    ) {
+        account::create_account_for_test(signer::address_of(user_1));
+        account::create_account_for_test(signer::address_of(user_2)); 
+        account::create_account_for_test(signer::address_of(deployer)); 
+        account::create_account_for_test( vault::get_config_object_address() ); 
+    }
+
+    #[test_only]
+    public fun initialize_for_test(
+        aptos_framework: &signer,
+        validator_1: &signer, 
+        validator_2: &signer
+    ) {
+        initialize_for_test_custom(
+            aptos_framework,
+            100 * ONE_APT,
+            10000 * ONE_APT,
+            LOCKUP_CYCLE_SECONDS,
+            true,
+            1,
+            1000,
+            1000000
+        );
+        let (_sk_1, pk_1, pop_1) = generate_identity();
+
+        initialize_test_validator(&pk_1, &pop_1, validator_1, 1000 * ONE_APT, true, false);
+        let (_sk_2, pk_2, pop_2) = generate_identity();
+        initialize_test_validator(&pk_2, &pop_2, validator_2, 2000 * ONE_APT, true, true);
+    }
+
+    #[test_only]
+    public fun end_epoch() {
+        stake::end_epoch();
+        reconfiguration::reconfigure_for_test_custom();
+    }
+
+    // Convenient function for setting up all required stake initializations.
+    #[test_only]
+    public fun initialize_for_test_custom(
+        aptos_framework: &signer,
+        minimum_stake: u64,
+        maximum_stake: u64,
+        recurring_lockup_secs: u64,
+        allow_validator_set_change: bool,
+        rewards_rate_numerator: u64,
+        rewards_rate_denominator: u64,
+        voting_power_increase_limit: u64,
+    ) {
+        account::create_account_for_test(signer::address_of(aptos_framework));
+        stake::initialize_for_test_custom(
+            aptos_framework,
+            minimum_stake,
+            maximum_stake,
+            recurring_lockup_secs,
+            allow_validator_set_change,
+            rewards_rate_numerator,
+            rewards_rate_denominator,
+            voting_power_increase_limit
+        );
+        reconfiguration::initialize_for_test(aptos_framework);
+        // features::change_feature_flags(aptos_framework, vector[DELEGATION_POOLS, MODULE_EVENT], vector[]);
+    }
+
+    #[test_only]
+    public fun generate_identity(): (bls12381::SecretKey, bls12381::PublicKey, bls12381::ProofOfPossession) {
+        let (sk, pkpop) = bls12381::generate_keys();
+        let pop = bls12381::generate_proof_of_possession(&sk);
+        let unvalidated_pk = bls12381::public_key_with_pop_to_normal(&pkpop);
+        (sk, unvalidated_pk, pop)
+    }
+
+    #[test_only]
+    public fun initialize_test_validator(
+        public_key: &bls12381::PublicKey,
+        proof_of_possession: &bls12381::ProofOfPossession,
+        validator: &signer,
+        amount: u64,
+        should_join_validator_set: bool,
+        should_end_epoch: bool
+    ) {
+        let validator_address = signer::address_of(validator);
+        if (!account::exists_at(signer::address_of(validator))) {
+            account::create_account_for_test(validator_address);
+        };
+
+        dp::initialize_delegation_pool(validator, 0, vector::empty<u8>());
+        validator_address = dp::get_owned_pool_address(validator_address);
+
+        let pk_bytes = bls12381::public_key_to_bytes(public_key);
+        let pop_bytes = bls12381::proof_of_possession_to_bytes(proof_of_possession);
+        stake::rotate_consensus_key(validator, validator_address, pk_bytes, pop_bytes);
+
+        if (amount > 0) {
+            mint_and_add_stake(validator, amount);
+        };
+
+        if (should_join_validator_set) {
+            stake::join_validator_set(validator, validator_address);
+        };
+        if (should_end_epoch) {
+            end_epoch();
+        };
+    }
+
+    #[test_only]
+    public fun mint_and_add_stake(account: &signer, amount: u64) {
+        stake::mint(account, amount);
+        dp::add_stake(account, dp::get_owned_pool_address(signer::address_of(account)), amount);
+    }
+
+}
\ No newline at end of file