From f065a98b6ea0f4517030ef10b43ad9a86c67e164 Mon Sep 17 00:00:00 2001
From: Aegean <egeaybars1102@gmail.com>
Date: Mon, 22 Jul 2024 11:27:17 +0300
Subject: [PATCH 1/4] aa spending limit code

---
 .../applications/aa_spending_limit/Scarb.lock |  20 ++
 .../applications/aa_spending_limit/Scarb.toml |  18 ++
 .../aa_spending_limit/src/erc20.cairo         |  40 +++
 .../aa_spending_limit/src/hello_account.cairo |  45 ++++
 .../aa_spending_limit/src/lib.cairo           |   3 +
 .../src/standard_account.cairo                | 246 ++++++++++++++++++
 .../aa_spending_limit/tests/aa.cairo          | 217 +++++++++++++++
 .../aa_spending_limit/tests/lib.cairo         |   2 +
 .../aa_spending_limit/tests/utils.cairo       |  65 +++++
 9 files changed, 656 insertions(+)
 create mode 100644 listings/applications/aa_spending_limit/Scarb.lock
 create mode 100644 listings/applications/aa_spending_limit/Scarb.toml
 create mode 100644 listings/applications/aa_spending_limit/src/erc20.cairo
 create mode 100644 listings/applications/aa_spending_limit/src/hello_account.cairo
 create mode 100644 listings/applications/aa_spending_limit/src/lib.cairo
 create mode 100644 listings/applications/aa_spending_limit/src/standard_account.cairo
 create mode 100644 listings/applications/aa_spending_limit/tests/aa.cairo
 create mode 100644 listings/applications/aa_spending_limit/tests/lib.cairo
 create mode 100644 listings/applications/aa_spending_limit/tests/utils.cairo

diff --git a/listings/applications/aa_spending_limit/Scarb.lock b/listings/applications/aa_spending_limit/Scarb.lock
new file mode 100644
index 00000000..318293a5
--- /dev/null
+++ b/listings/applications/aa_spending_limit/Scarb.lock
@@ -0,0 +1,20 @@
+# Code generated by scarb DO NOT EDIT.
+version = 1
+
+[[package]]
+name = "aa_tutorial"
+version = "0.1.0"
+dependencies = [
+ "openzeppelin",
+ "snforge_std",
+]
+
+[[package]]
+name = "openzeppelin"
+version = "0.9.0"
+source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.9.0#364db5b1aecc1335d2e65db887291d19aa28937d"
+
+[[package]]
+name = "snforge_std"
+version = "0.25.0"
+source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.25.0#5b366e24821e530fea97f11b211d220e8493fbea"
diff --git a/listings/applications/aa_spending_limit/Scarb.toml b/listings/applications/aa_spending_limit/Scarb.toml
new file mode 100644
index 00000000..17df048a
--- /dev/null
+++ b/listings/applications/aa_spending_limit/Scarb.toml
@@ -0,0 +1,18 @@
+[package]
+name = "aa_tutorial"
+version.workspace = true
+edition = "2023_11"
+
+[dependencies]
+starknet.workspace = true
+openzeppelin.workspace = true
+snforge_std.workspace = true
+
+[dev-dependencies]
+snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" }
+
+[scripts]
+test.workspace = true
+
+[[target.starknet-contract]]
+casm = true
diff --git a/listings/applications/aa_spending_limit/src/erc20.cairo b/listings/applications/aa_spending_limit/src/erc20.cairo
new file mode 100644
index 00000000..91a7fbc8
--- /dev/null
+++ b/listings/applications/aa_spending_limit/src/erc20.cairo
@@ -0,0 +1,40 @@
+#[starknet::contract]
+mod MyERC20Token {
+    use openzeppelin::token::erc20::ERC20Component;
+    use starknet::ContractAddress;
+
+    component!(path: ERC20Component, storage: erc20, event: ERC20Event);
+
+    #[abi(embed_v0)]
+    impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
+    #[abi(embed_v0)]
+    impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
+    #[abi(embed_v0)]
+    impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl<ContractState>;
+    impl InternalImpl = ERC20Component::InternalImpl<ContractState>;
+
+    #[storage]
+    struct Storage {
+        #[substorage(v0)]
+        erc20: ERC20Component::Storage
+    }
+
+    #[event]
+    #[derive(Drop, starknet::Event)]
+    enum Event {
+        #[flat]
+        ERC20Event: ERC20Component::Event
+    }
+
+    #[constructor]
+    fn constructor(
+        ref self: ContractState,
+        name: felt252,
+        symbol: felt252,
+        fixed_supply: u256,
+        recipient: ContractAddress
+    ) {
+        self.erc20.initializer(name, symbol);
+        self.erc20._mint(recipient, fixed_supply);
+    }
+}
diff --git a/listings/applications/aa_spending_limit/src/hello_account.cairo b/listings/applications/aa_spending_limit/src/hello_account.cairo
new file mode 100644
index 00000000..343a006d
--- /dev/null
+++ b/listings/applications/aa_spending_limit/src/hello_account.cairo
@@ -0,0 +1,45 @@
+use starknet::account::Call;
+
+// IERC6 obtained from Open Zeppelin's cairo-contracts/src/account/interface.cairo
+#[starknet::interface]
+trait ISRC6<TState> {
+    fn __execute__(self: @TState, calls: Array<Call>) -> Array<Span<felt252>>;
+    fn __validate__(self: @TState, calls: Array<Call>) -> felt252;
+    fn is_valid_signature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;
+}
+
+#[starknet::contract(account)]
+mod HelloAccount {
+    use core::traits::Into;
+    use starknet::VALIDATED;
+    use starknet::account::Call;
+    use starknet::get_caller_address;
+
+    #[storage]
+    struct Storage {} // Empty storage. No public key is stored.
+
+    #[abi(embed_v0)]
+    impl SRC6Impl of super::ISRC6<ContractState> {
+        fn is_valid_signature(
+            self: @ContractState, hash: felt252, signature: Array<felt252>
+        ) -> felt252 {
+            // No signature is required so any signature is valid.
+            VALIDATED
+        }
+        fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
+            let hash = 0;
+            let mut signature: Array<felt252> = ArrayTrait::new();
+            signature.append(0);
+            self.is_valid_signature(hash, signature)
+        }
+        fn __execute__(self: @ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
+            let sender = get_caller_address();
+            assert(sender.is_zero(), 'Account: invalid caller');
+            let Call { to, selector, calldata } = calls.at(0);
+            let _res = starknet::call_contract_syscall(*to, *selector, *calldata).unwrap();
+            let mut res = ArrayTrait::new();
+            res.append(_res);
+            res
+        }
+    }
+}
diff --git a/listings/applications/aa_spending_limit/src/lib.cairo b/listings/applications/aa_spending_limit/src/lib.cairo
new file mode 100644
index 00000000..c5ac4012
--- /dev/null
+++ b/listings/applications/aa_spending_limit/src/lib.cairo
@@ -0,0 +1,3 @@
+//mod hello_account;
+mod standard_account;
+mod erc20;
diff --git a/listings/applications/aa_spending_limit/src/standard_account.cairo b/listings/applications/aa_spending_limit/src/standard_account.cairo
new file mode 100644
index 00000000..e1d80767
--- /dev/null
+++ b/listings/applications/aa_spending_limit/src/standard_account.cairo
@@ -0,0 +1,246 @@
+use starknet::account::Call;
+use starknet::ContractAddress;
+
+#[starknet::interface]
+trait IAccount<T> {
+    fn public_key(self: @T) -> felt252;
+    fn get_time_limit(self: @T) -> u64;
+    fn supports_interface(self: @T, interface_id: felt252) -> bool;
+    fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
+    fn set_spending_limit(ref self: T, token_address: ContractAddress, _limit: u256);
+    fn get_spending_limit_timestamp(self: @T, token_address: ContractAddress) -> u64;
+    fn get_current_spending_limit(self: @T, token_address: ContractAddress) -> u256;
+    fn get_spending_limit(self: @T, token_address: ContractAddress) -> u256;
+    fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
+    fn __validate__(self: @T, calls: Array<Call>) -> felt252;
+    fn __validate_declare__(self: @T, class_hash: felt252) -> felt252;
+    fn __validate_deploy__(
+        self: @T, class_hash: felt252, salt: felt252, public_key: felt252
+    ) -> felt252;
+}
+
+#[derive(Copy, Drop, Serde, starknet::Store)]
+struct SpendingLimit {
+    exists: bool,
+    timestamp: u64,
+    limit: u256,
+}
+
+#[starknet::contract(account)]
+mod Account {
+    use core::traits::Into;
+    use core::traits::TryInto;
+    use super::{Call, IAccount, SpendingLimit};
+    use core::Felt252TryIntoU128;
+    use core::integer::u256_checked_sub;
+    use starknet::{
+        ContractAddress, get_caller_address, call_contract_syscall, get_tx_info, VALIDATED,
+        get_block_timestamp, get_contract_address
+    };
+    use zeroable::Zeroable;
+    use ecdsa::check_ecdsa_signature;
+
+    const SRC6_TRAIT_ID: felt252 =
+        1270010605630597976495846281167968799381097569185364931397797212080166453709; // hash of SNIP-6 trait
+
+    const transfer_selector: felt252 =
+        0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e;
+
+    const approve_selector: felt252 =
+        0x0219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c;
+
+
+    #[storage]
+    struct Storage {
+        public_key: felt252,
+        spending_limit: LegacyMap<ContractAddress, SpendingLimit>,
+        current_spending_limit: LegacyMap<ContractAddress, u256>,
+        time_limit: u64,
+    }
+
+    //time_limit is in seconds
+    #[constructor]
+    fn constructor(ref self: ContractState, public_key: felt252, time_limit: u64) {
+        self.public_key.write(public_key);
+        self.time_limit.write(time_limit);
+    }
+
+    #[abi(embed_v0)]
+    impl AccountImpl of IAccount<ContractState> {
+        fn public_key(self: @ContractState) -> felt252 {
+            self.public_key.read()
+        }
+
+        fn get_time_limit(self: @ContractState) -> u64 {
+            self.time_limit.read()
+        }
+
+        fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
+            interface_id == SRC6_TRAIT_ID
+        }
+
+        fn is_valid_signature(
+            self: @ContractState, hash: felt252, signature: Array<felt252>
+        ) -> felt252 {
+            let is_valid = self.is_valid_signature_bool(hash, signature.span());
+            if is_valid {
+                VALIDATED
+            } else {
+                0
+            }
+        }
+
+        fn set_spending_limit(
+            ref self: ContractState, token_address: ContractAddress, _limit: u256
+        ) {
+            assert(get_caller_address() == get_contract_address(), 'Invalid caller');
+            let timemstamp = get_block_timestamp();
+            let new_limit: SpendingLimit = SpendingLimit {
+                exists: true, limit: _limit, timestamp: timemstamp
+            };
+            self.spending_limit.write(token_address, new_limit);
+            self.current_spending_limit.write(token_address, _limit);
+        }
+
+        fn get_current_spending_limit(
+            self: @ContractState, token_address: ContractAddress
+        ) -> u256 {
+            self.current_spending_limit.read(token_address)
+        }
+
+        fn get_spending_limit_timestamp(
+            self: @ContractState, token_address: ContractAddress
+        ) -> u64 {
+            self.spending_limit.read(token_address).timestamp
+        }
+
+        fn get_spending_limit(self: @ContractState, token_address: ContractAddress) -> u256 {
+            let spending_limit = self.spending_limit.read(token_address);
+            let current_timestamp: u64 = get_block_timestamp();
+            let time_limit: u64 = self.time_limit.read();
+            let timestamp: u64 = spending_limit.timestamp;
+
+            if ((timestamp + time_limit) >= current_timestamp) {
+                return self.current_spending_limit.read(token_address);
+            }
+            return spending_limit.limit;
+        }
+
+        fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
+            assert(!calls.is_empty(), 'Account: No call data given');
+            self.only_protocol();
+            let mut res = ArrayTrait::new();
+            loop {
+                match calls.pop_front() {
+                    Option::Some(call) => {
+                        let Call { to, selector, calldata } = call;
+                        let limit_exists: bool = self.spending_limit.read(to).exists;
+
+                        if (self.is_spending_tx(selector) && limit_exists) {
+                            let low: u128 = Felt252TryIntoU128::try_into(*calldata[1]).unwrap();
+                            let high: u128 = Felt252TryIntoU128::try_into(*calldata[2]).unwrap();
+                            let value: u256 = u256 { low, high };
+
+                            //TODO: check if the limit updated when timestamp is greater than the limit
+                            let mut current_limit: u256 = self.get_spending_limit(to);
+                            current_limit -= value;
+                            self.current_spending_limit.write(to, current_limit);
+                            self.update_timestamp(to);
+                        }
+                        let _res = call_contract_syscall(to, selector, calldata).unwrap();
+                        res.append(_res);
+                    },
+                    Option::None(_) => { break (); },
+                };
+            };
+            res
+        }
+
+        fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+
+        fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+
+        fn __validate_deploy__(
+            self: @ContractState, class_hash: felt252, salt: felt252, public_key: felt252
+        ) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+    }
+
+    #[generate_trait]
+    impl PrivateImpl of PrivateTrait {
+        fn only_protocol(self: @ContractState) {
+            let sender: ContractAddress = get_caller_address();
+            assert(sender.is_zero(), 'Account: invalid caller');
+        }
+
+        fn update_timestamp(ref self: ContractState, token_address: ContractAddress) {
+            let mut spending_limit = self.spending_limit.read(token_address);
+            let current_timestamp: u64 = get_block_timestamp();
+            let time_limit: u64 = self.time_limit.read();
+            let timestamp: u64 = spending_limit.timestamp;
+
+            if ((timestamp + time_limit) < current_timestamp) {
+                spending_limit.timestamp = current_timestamp;
+                self.spending_limit.write(token_address, spending_limit);
+            }
+        }
+
+        fn is_spending_tx(ref self: ContractState, selector: felt252) -> bool {
+            if (selector == transfer_selector || selector == approve_selector) {
+                return true;
+            }
+            return false;
+        }
+
+        fn is_valid_signature_bool(
+            self: @ContractState, hash: felt252, signature: Span<felt252>
+        ) -> bool {
+            let is_valid_length = signature.len() == 2_u32;
+            if !is_valid_length {
+                return false;
+            }
+            check_ecdsa_signature(
+                hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32)
+            )
+        }
+
+        fn validate_transaction(self: @ContractState) -> felt252 {
+            let tx_info = get_tx_info().unbox();
+            let tx_hash = tx_info.transaction_hash;
+            let signature = tx_info.signature;
+
+            let is_valid = self.is_valid_signature_bool(tx_hash, signature);
+            assert(is_valid, 'Account: Incorrect tx signature');
+            VALIDATED
+        }
+
+        fn execute_single_call(self: @ContractState, call: Call) -> Span<felt252> {
+            let Call { to, selector, calldata } = call;
+            call_contract_syscall(to, selector, calldata).unwrap()
+        }
+
+        fn execute_multiple_calls(
+            self: @ContractState, mut calls: Array<Call>
+        ) -> Array<Span<felt252>> {
+            let mut res = ArrayTrait::new();
+            loop {
+                match calls.pop_front() {
+                    Option::Some(call) => {
+                        let _res = self.execute_single_call(call);
+                        res.append(_res);
+                    },
+                    Option::None(_) => { break (); },
+                };
+            };
+            res
+        }
+    }
+}
diff --git a/listings/applications/aa_spending_limit/tests/aa.cairo b/listings/applications/aa_spending_limit/tests/aa.cairo
new file mode 100644
index 00000000..f0e41c4a
--- /dev/null
+++ b/listings/applications/aa_spending_limit/tests/aa.cairo
@@ -0,0 +1,217 @@
+use core::option::OptionTrait;
+use core::traits::TryInto;
+use core::traits::Into;
+use starknet::{ContractAddress, contract_address_const, account::Call};
+use core::integer::{U64IntoFelt252};
+
+use snforge_std::{
+    declare, ContractClassTrait, start_mock_call, stop_mock_call, start_cheat_caller_address,
+    stop_cheat_caller_address, start_cheat_block_timestamp, stop_cheat_block_timestamp
+};
+use snforge_std::signature::KeyPairTrait;
+use snforge_std::signature::stark_curve::{
+    StarkCurveKeyPairImpl, StarkCurveSignerImpl, StarkCurveVerifierImpl
+};
+use super::utils::{
+    deploy_account_contract, deploy_erc20_contract, create_transfer_call, create_approve_call,
+    declare_erc20_contract, deploy_declared_erc20
+};
+
+use aa_tutorial::standard_account::IAccountDispatcher;
+use aa_tutorial::standard_account::IAccountDispatcherTrait;
+use aa_tutorial::standard_account::IAccountSafeDispatcher;
+use aa_tutorial::standard_account::IAccountSafeDispatcherTrait;
+
+use openzeppelin::token::erc20::interface::IERC20Dispatcher;
+use openzeppelin::token::erc20::interface::IERC20DispatcherTrait;
+
+#[test]
+fn check_stored_pub_key() {
+    let public_key = 999;
+    let limit = 1000000000000000; //0.001 * 10 ** 18
+    let contract_address = deploy_account_contract(public_key, limit);
+    let dispatcher = IAccountDispatcher { contract_address };
+    let stored_pub_key = dispatcher.public_key();
+    assert(public_key == stored_pub_key, 'Wrong pub key');
+}
+
+#[test]
+fn check_stored_time_limit() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600; //600 seconds - 10 minutes
+    let contract_address = deploy_account_contract(signer.public_key, time_limit);
+    let dispatcher = IAccountDispatcher { contract_address };
+
+    let stored_time_limit = dispatcher.get_time_limit();
+
+    assert(time_limit == stored_time_limit, 'Time limit wrong');
+}
+
+#[test]
+fn check_stored_spending_limit() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600;
+    let spending_limit = 1000000000000000; //0.001 * 10 ** 18
+    let contract_address = deploy_account_contract(signer.public_key, time_limit);
+    let dispatcher = IAccountDispatcher { contract_address };
+
+    let mock_token_address: ContractAddress = contract_address_const::<1121321>();
+    dispatcher.set_spending_limit(mock_token_address, spending_limit);
+    let stored_spending_limit = dispatcher.get_spending_limit(mock_token_address);
+
+    //Will revert after time_limit seconds have passed.
+    assert(stored_spending_limit == spending_limit, 'Wrong spending limit');
+}
+
+#[test]
+fn check_spending_limit_after_transfer() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600;
+    let spending_limit = 10000000000000000; //0.01 * 10 ** 18
+    let account_address = deploy_account_contract(signer.public_key, time_limit);
+    let token_address = deploy_erc20_contract(
+        'MyToken', 'MTK', 10000000000000000000, account_address
+    ); //Pre-mints 10 MTK.
+
+    let account_dispatcher = IAccountDispatcher { contract_address: account_address };
+
+    account_dispatcher.set_spending_limit(token_address, spending_limit);
+    let mock_account_address = contract_address_const::<
+        0x067981c7f9f55bcbdd4e0d0a9c5bbcea77dacb42cccbf13554a847d6353f728e
+    >();
+    let transfer_value: u256 = 1000000000000000; //0.001 MTK
+
+    let transferCall = create_transfer_call(token_address, mock_account_address, transfer_value);
+
+    start_cheat_caller_address(account_address, 0.try_into().unwrap());
+    account_dispatcher.__execute__(array![transferCall]);
+    stop_cheat_caller_address(account_address);
+
+    let remaining_limit = account_dispatcher.get_spending_limit(token_address);
+    println!("Remaining limit: {}", remaining_limit);
+    let current_limit = account_dispatcher.get_current_spending_limit(token_address);
+    println!("Remaining current limit: {}", current_limit);
+
+    assert(remaining_limit == (spending_limit - transfer_value), 'Spending limit wrong');
+}
+
+#[test]
+fn spending_limit_after_time_limit() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600;
+    let spending_limit = 10000000000000000; //0.01 * 10 ** 18
+    let account_address = deploy_account_contract(signer.public_key, time_limit);
+    let token_address = deploy_erc20_contract(
+        'MyToken', 'MTK', 10000000000000000000, account_address
+    ); //Pre-mints 10 MTK.
+
+    let account_dispatcher = IAccountDispatcher { contract_address: account_address };
+
+    let start_timestamp = 1000;
+    start_cheat_block_timestamp(account_address, start_timestamp);
+    account_dispatcher.set_spending_limit(token_address, spending_limit);
+    stop_cheat_block_timestamp(account_address);
+    let mock_account_address = contract_address_const::<123456789>();
+    let transfer_value: u256 = 1000000000000000; //0.001 MTK
+
+    let transferCall1 = create_transfer_call(token_address, mock_account_address, transfer_value);
+
+    start_cheat_caller_address(account_address, 0.try_into().unwrap());
+    account_dispatcher.__execute__(array![transferCall1]);
+
+    let new_timestamp = start_timestamp + time_limit + 10;
+    start_cheat_block_timestamp(account_address, new_timestamp);
+    let new_limit = account_dispatcher.get_spending_limit(token_address);
+    assert(new_limit == spending_limit, 'Spending limit wrong');
+
+    let transferCall2 = create_transfer_call(token_address, mock_account_address, transfer_value);
+    account_dispatcher.__execute__(array![transferCall2]);
+
+    let spending_limit_timestamp = account_dispatcher.get_spending_limit_timestamp(token_address);
+    stop_cheat_block_timestamp(account_address);
+
+    assert(spending_limit_timestamp == new_timestamp, 'Timestamp not updated');
+}
+
+#[test]
+fn multicall_spending_limit() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600;
+    let spending_limit = 10000000000000000; //0.01 * 10 ** 18
+    let account_address = deploy_account_contract(signer.public_key, time_limit);
+    let erc20_class = declare_erc20_contract();
+
+    let token_address1 = deploy_declared_erc20(
+        erc20_class, 'MyToken', 'MTK', 10000000000000000000, account_address
+    ); //Pre-mints 10 MTK.
+    let token_address2 = deploy_declared_erc20(
+        erc20_class, 'MyTokenTest', 'MTKT', 10000000000000000000, account_address
+    ); //Pre-mints 10 MTK1.
+    let account_dispatcher = IAccountDispatcher { contract_address: account_address };
+
+    let transfer_value: u256 = 1000000000000000; //0.001 MTK
+    let approve_value: u256 = 5000000000000000; //0.005 MTK
+
+    let mock_account_address = contract_address_const::<123456789>();
+    let transferCall_1 = create_transfer_call(token_address1, mock_account_address, transfer_value);
+    let approveCall_1 = create_approve_call(token_address1, mock_account_address, approve_value);
+    let approveCall_2 = create_approve_call(token_address2, mock_account_address, approve_value);
+    let start_timestamp_1 = 1000;
+    let start_timestamp_2 = 1200;
+    start_cheat_block_timestamp(account_address, start_timestamp_1);
+    account_dispatcher.set_spending_limit(token_address1, spending_limit);
+    stop_cheat_block_timestamp(account_address);
+
+    start_cheat_block_timestamp(account_address, start_timestamp_2);
+    account_dispatcher.set_spending_limit(token_address2, spending_limit);
+    stop_cheat_block_timestamp(account_address);
+
+    start_cheat_caller_address(account_address, 0.try_into().unwrap());
+    account_dispatcher.__execute__(array![transferCall_1, approveCall_1, approveCall_2]);
+
+    let spending_limit_1 = account_dispatcher.get_spending_limit(token_address1);
+    let spending_limit_2 = account_dispatcher.get_spending_limit(token_address2);
+    assert(
+        spending_limit_1 == (spending_limit - (approve_value + transfer_value)),
+        'Multicall limit wrong'
+    );
+    assert(spending_limit_2 == (spending_limit - approve_value), 'Token2 limit wrong');
+
+    start_cheat_block_timestamp(account_address, start_timestamp_2 + time_limit + 150);
+    let transferCall_2 = create_transfer_call(token_address2, mock_account_address, transfer_value);
+    account_dispatcher.__execute__(array![transferCall_2]);
+    stop_cheat_block_timestamp(account_address);
+    let spending_limit_21 = account_dispatcher.get_spending_limit(token_address2);
+    println!("spending_limit_21: {}", spending_limit_21);
+    assert(spending_limit_21 == (spending_limit - transfer_value), 'Spending limit wrong');
+    stop_cheat_caller_address(account_address);
+}
+
+#[test]
+#[should_panic(expected: ('u256_sub Overflow',))]
+fn check_over_spending_limit() {
+    let mut signer = KeyPairTrait::<felt252, felt252>::from_secret_key(123);
+    let time_limit = 600;
+    let spending_limit = 10000000000000000; //0.01 * 10 ** 18
+    let account_address = deploy_account_contract(signer.public_key, time_limit);
+    let token_address = deploy_erc20_contract(
+        'MyToken', 'MTK', 10000000000000000000, account_address
+    ); //Pre-mints 10 MTK.
+    let mock_account_address = contract_address_const::<123456789>();
+
+    let account_dispatcher = IAccountDispatcher { contract_address: account_address };
+
+    let start_timestamp = 1000;
+    let approve_value = 11000000000000000; // 0.0011 * 10 ** 18
+
+    start_cheat_block_timestamp(account_address, start_timestamp);
+    account_dispatcher.set_spending_limit(token_address, spending_limit);
+    stop_cheat_block_timestamp(account_address);
+
+    start_cheat_caller_address(account_address, 0.try_into().unwrap());
+    let approveCall = create_approve_call(token_address, mock_account_address, approve_value);
+    start_cheat_block_timestamp(account_address, start_timestamp + 500);
+    account_dispatcher.__execute__(array![approveCall]);
+    stop_cheat_block_timestamp(account_address);
+    stop_cheat_caller_address(account_address);
+}
diff --git a/listings/applications/aa_spending_limit/tests/lib.cairo b/listings/applications/aa_spending_limit/tests/lib.cairo
new file mode 100644
index 00000000..b0d4a61d
--- /dev/null
+++ b/listings/applications/aa_spending_limit/tests/lib.cairo
@@ -0,0 +1,2 @@
+mod utils;
+mod aa;
diff --git a/listings/applications/aa_spending_limit/tests/utils.cairo b/listings/applications/aa_spending_limit/tests/utils.cairo
new file mode 100644
index 00000000..da6d200b
--- /dev/null
+++ b/listings/applications/aa_spending_limit/tests/utils.cairo
@@ -0,0 +1,65 @@
+use core::result::ResultTrait;
+use starknet::{ContractAddress, account::Call, contract_address_const};
+use core::integer::{U64IntoFelt252, U256TryIntoFelt252};
+
+use snforge_std::{declare, ContractClassTrait, ContractClass};
+
+fn deploy_account_contract(public_key: felt252, time_limit: u64) -> ContractAddress {
+    let contract = declare("Account").unwrap();
+    let time_limit_felt252: felt252 = U64IntoFelt252::into(time_limit);
+    let constructor_args = array![public_key, time_limit_felt252];
+    let (contract_address, _) = contract.deploy(@constructor_args).unwrap();
+    contract_address
+}
+
+fn deploy_erc20_contract(
+    name: felt252, symbol: felt252, fixed_supply: u256, recipient: ContractAddress
+) -> ContractAddress {
+    let contract = declare("MyERC20Token").unwrap();
+    let constructor_args = array![
+        name, symbol, fixed_supply.low.into(), fixed_supply.high.into(), recipient.into()
+    ];
+
+    let (contract_address, _) = contract.deploy(@constructor_args).unwrap();
+    contract_address
+}
+
+fn declare_erc20_contract() -> ContractClass {
+    declare("MyERC20Token").unwrap()
+}
+
+fn deploy_declared_erc20(
+    class: ContractClass,
+    name: felt252,
+    symbol: felt252,
+    fixed_supply: u256,
+    recipient: ContractAddress
+) -> ContractAddress {
+    let constructor_args = array![
+        name, symbol, fixed_supply.low.into(), fixed_supply.high.into(), recipient.into()
+    ];
+
+    let (contract_address, _) = class.deploy(@constructor_args).unwrap();
+    contract_address
+}
+
+
+fn create_transfer_call(
+    token_address: ContractAddress, recipient: ContractAddress, value: u256
+) -> Call {
+    return Call {
+        to: token_address,
+        selector: selector!("transfer"),
+        calldata: array![recipient.into(), value.low.into(), value.high.into()].span(),
+    };
+}
+
+fn create_approve_call(
+    token_address: ContractAddress, allowed_address: ContractAddress, value: u256
+) -> Call {
+    return Call {
+        to: token_address,
+        selector: selector!("approve"),
+        calldata: array![allowed_address.into(), value.low.into(), value.high.into()].span(),
+    };
+}

From ef810243055e510d6545b8be1ecfb6567f8e9639 Mon Sep 17 00:00:00 2001
From: Aegean <egeaybars1102@gmail.com>
Date: Mon, 22 Jul 2024 11:32:21 +0300
Subject: [PATCH 2/4] remove unnecessary code

---
 .../{standard_account.cairo => account.cairo} | 21 ---------
 .../aa_spending_limit/src/hello_account.cairo | 45 -------------------
 .../aa_spending_limit/src/lib.cairo           |  3 +-
 3 files changed, 1 insertion(+), 68 deletions(-)
 rename listings/applications/aa_spending_limit/src/{standard_account.cairo => account.cairo} (92%)
 delete mode 100644 listings/applications/aa_spending_limit/src/hello_account.cairo

diff --git a/listings/applications/aa_spending_limit/src/standard_account.cairo b/listings/applications/aa_spending_limit/src/account.cairo
similarity index 92%
rename from listings/applications/aa_spending_limit/src/standard_account.cairo
rename to listings/applications/aa_spending_limit/src/account.cairo
index e1d80767..9de648ca 100644
--- a/listings/applications/aa_spending_limit/src/standard_account.cairo
+++ b/listings/applications/aa_spending_limit/src/account.cairo
@@ -221,26 +221,5 @@ mod Account {
             assert(is_valid, 'Account: Incorrect tx signature');
             VALIDATED
         }
-
-        fn execute_single_call(self: @ContractState, call: Call) -> Span<felt252> {
-            let Call { to, selector, calldata } = call;
-            call_contract_syscall(to, selector, calldata).unwrap()
-        }
-
-        fn execute_multiple_calls(
-            self: @ContractState, mut calls: Array<Call>
-        ) -> Array<Span<felt252>> {
-            let mut res = ArrayTrait::new();
-            loop {
-                match calls.pop_front() {
-                    Option::Some(call) => {
-                        let _res = self.execute_single_call(call);
-                        res.append(_res);
-                    },
-                    Option::None(_) => { break (); },
-                };
-            };
-            res
-        }
     }
 }
diff --git a/listings/applications/aa_spending_limit/src/hello_account.cairo b/listings/applications/aa_spending_limit/src/hello_account.cairo
deleted file mode 100644
index 343a006d..00000000
--- a/listings/applications/aa_spending_limit/src/hello_account.cairo
+++ /dev/null
@@ -1,45 +0,0 @@
-use starknet::account::Call;
-
-// IERC6 obtained from Open Zeppelin's cairo-contracts/src/account/interface.cairo
-#[starknet::interface]
-trait ISRC6<TState> {
-    fn __execute__(self: @TState, calls: Array<Call>) -> Array<Span<felt252>>;
-    fn __validate__(self: @TState, calls: Array<Call>) -> felt252;
-    fn is_valid_signature(self: @TState, hash: felt252, signature: Array<felt252>) -> felt252;
-}
-
-#[starknet::contract(account)]
-mod HelloAccount {
-    use core::traits::Into;
-    use starknet::VALIDATED;
-    use starknet::account::Call;
-    use starknet::get_caller_address;
-
-    #[storage]
-    struct Storage {} // Empty storage. No public key is stored.
-
-    #[abi(embed_v0)]
-    impl SRC6Impl of super::ISRC6<ContractState> {
-        fn is_valid_signature(
-            self: @ContractState, hash: felt252, signature: Array<felt252>
-        ) -> felt252 {
-            // No signature is required so any signature is valid.
-            VALIDATED
-        }
-        fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
-            let hash = 0;
-            let mut signature: Array<felt252> = ArrayTrait::new();
-            signature.append(0);
-            self.is_valid_signature(hash, signature)
-        }
-        fn __execute__(self: @ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
-            let sender = get_caller_address();
-            assert(sender.is_zero(), 'Account: invalid caller');
-            let Call { to, selector, calldata } = calls.at(0);
-            let _res = starknet::call_contract_syscall(*to, *selector, *calldata).unwrap();
-            let mut res = ArrayTrait::new();
-            res.append(_res);
-            res
-        }
-    }
-}
diff --git a/listings/applications/aa_spending_limit/src/lib.cairo b/listings/applications/aa_spending_limit/src/lib.cairo
index c5ac4012..370ce69b 100644
--- a/listings/applications/aa_spending_limit/src/lib.cairo
+++ b/listings/applications/aa_spending_limit/src/lib.cairo
@@ -1,3 +1,2 @@
-//mod hello_account;
-mod standard_account;
+mod account;
 mod erc20;

From 1205ff80247c4f5051fa4643702e0bbb67d40a94 Mon Sep 17 00:00:00 2001
From: Aegean <egeaybars1102@gmail.com>
Date: Mon, 22 Jul 2024 13:11:39 +0300
Subject: [PATCH 3/4] update contract & readme

---
 .../aa_spending_limit/src/account.cairo       |  4 +-
 pages/applications/aa_spending_limit.md       | 60 +++++++++++++++++++
 2 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 pages/applications/aa_spending_limit.md

diff --git a/listings/applications/aa_spending_limit/src/account.cairo b/listings/applications/aa_spending_limit/src/account.cairo
index 9de648ca..d02c1e4a 100644
--- a/listings/applications/aa_spending_limit/src/account.cairo
+++ b/listings/applications/aa_spending_limit/src/account.cairo
@@ -141,7 +141,6 @@ mod Account {
                             let high: u128 = Felt252TryIntoU128::try_into(*calldata[2]).unwrap();
                             let value: u256 = u256 { low, high };
 
-                            //TODO: check if the limit updated when timestamp is greater than the limit
                             let mut current_limit: u256 = self.get_spending_limit(to);
                             current_limit -= value;
                             self.current_spending_limit.write(to, current_limit);
@@ -181,6 +180,9 @@ mod Account {
             assert(sender.is_zero(), 'Account: invalid caller');
         }
 
+        //If the current block timestamp is past the time_limit, 
+        //The max new limit set by the account owner is written to the current spending limit.
+        //And the current timestamp is updated.
         fn update_timestamp(ref self: ContractState, token_address: ContractAddress) {
             let mut spending_limit = self.spending_limit.read(token_address);
             let current_timestamp: u64 = get_block_timestamp();
diff --git a/pages/applications/aa_spending_limit.md b/pages/applications/aa_spending_limit.md
new file mode 100644
index 00000000..46d5dd0d
--- /dev/null
+++ b/pages/applications/aa_spending_limit.md
@@ -0,0 +1,60 @@
+# Account Contract with Spending Limits
+
+In this example, we will write an account contract with spending limits.
+
+Before proceeding with this example, make sure that you read the [intro](../advanced-concepts/account_abstraction/account_contract.md) to account contracts on Starknet which will help you understand how Starknet account contracts work.
+
+## Key Specifications
+The spending limit for the account contract will have the following features: 
+
+- The limit can be set for any ERC-20 token with any amount by the account owner.
+- The limit will reset after a specified time. The account owner could set any time limit they want (daily, weekly or 12 hours).
+- If one of the calls has the "approve" or "transfer" function selectors, the spending limit will decrease accordingly by the amount given in the function call. If there is no limit left, the transaction will revert.
+
+The function `__execute__` will check if the function called is "approve" or "transfer", and if yes, the limit will decrease by the amount in the call. Here is the code for the `__execute__` function: 
+```rs
+fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
+            assert(!calls.is_empty(), 'Account: No call data given');
+            self.only_protocol();
+            let mut res = ArrayTrait::new();
+            loop {
+                //Loop through the calls, supporting multicalls.
+                match calls.pop_front() {
+                    Option::Some(call) => {
+                        let Call { to, selector, calldata } = call;
+
+                        //The limit should have been set before by the account owner.
+                        let limit_exists: bool = self.spending_limit.read(to).exists;
+
+                        //is_spending_tx function checks if the function is "approve" or "transfer"
+                        //See the full code below how it is done!
+                        if (self.is_spending_tx(selector) && limit_exists) {
+                            let low: u128 = Felt252TryIntoU128::try_into(*calldata[1]).unwrap();
+                            let high: u128 = Felt252TryIntoU128::try_into(*calldata[2]).unwrap();
+
+                            //Extract the value the account approves/transfers from the calldata
+                            let value: u256 = u256 { low, high }; 
+                            
+                            //Update the spending limit for other calls in this tx or for future txs.
+                            let mut current_limit: u256 = self.get_spending_limit(to);
+                            current_limit -= value;
+                            self.current_spending_limit.write(to, current_limit);
+                            self.update_timestamp(to);
+                        }
+
+                        //calls the function after spending limit & timestamp were updated.
+                        let _res = call_contract_syscall(to, selector, calldata).unwrap();
+                        res.append(_res);
+                    },
+                    Option::None(_) => { break (); },
+                };
+            };
+            res
+        }
+```
+
+Here is the full code of the account contract with spending limits:
+
+```rs
+{{#rustdoc_include ../../listings/applications/aa_spending_limit/src/account.cairo}}
+```
\ No newline at end of file

From 86f516d4b5b5acecec4c84765bfbdd9edc61b17a Mon Sep 17 00:00:00 2001
From: julio4 <30329843+julio4@users.noreply.github.com>
Date: Thu, 21 Nov 2024 12:22:37 +0700
Subject: [PATCH 4/4] fix: update pr 236 (wip)

---
 Scarb.lock                                    |   8 +
 .../account_spending_limits}/Scarb.lock       |   0
 .../account_spending_limits}/Scarb.toml       |  10 +-
 .../account_spending_limits/src/account.cairo | 260 ++++++++++++++++++
 .../account_spending_limits}/src/erc20.cairo  |   0
 .../account_spending_limits}/src/lib.cairo    |   0
 .../account_spending_limits}/tests/aa.cairo   |   0
 .../account_spending_limits}/tests/lib.cairo  |   0
 .../tests/utils.cairo                         |   0
 .../aa_spending_limit/src/account.cairo       | 227 ---------------
 .../account_spending_limits.md                |  34 +++
 pages/applications/aa_spending_limit.md       |  60 ----
 routes.ts                                     |   4 +
 13 files changed, 311 insertions(+), 292 deletions(-)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/Scarb.lock (100%)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/Scarb.toml (56%)
 create mode 100644 listings/advanced-concepts/account_spending_limits/src/account.cairo
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/src/erc20.cairo (100%)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/src/lib.cairo (100%)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/tests/aa.cairo (100%)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/tests/lib.cairo (100%)
 rename listings/{applications/aa_spending_limit => advanced-concepts/account_spending_limits}/tests/utils.cairo (100%)
 delete mode 100644 listings/applications/aa_spending_limit/src/account.cairo
 create mode 100644 pages/advanced-concepts/account_abstraction/account_spending_limits.md
 delete mode 100644 pages/applications/aa_spending_limit.md

diff --git a/Scarb.lock b/Scarb.lock
index 6cd8c91c..4589d79a 100644
--- a/Scarb.lock
+++ b/Scarb.lock
@@ -1,6 +1,14 @@
 # Code generated by scarb DO NOT EDIT.
 version = 1
 
+[[package]]
+name = "account_spending_limits"
+version = "0.1.0"
+dependencies = [
+ "openzeppelin",
+ "snforge_std",
+]
+
 [[package]]
 name = "advanced_factory"
 version = "0.1.0"
diff --git a/listings/applications/aa_spending_limit/Scarb.lock b/listings/advanced-concepts/account_spending_limits/Scarb.lock
similarity index 100%
rename from listings/applications/aa_spending_limit/Scarb.lock
rename to listings/advanced-concepts/account_spending_limits/Scarb.lock
diff --git a/listings/applications/aa_spending_limit/Scarb.toml b/listings/advanced-concepts/account_spending_limits/Scarb.toml
similarity index 56%
rename from listings/applications/aa_spending_limit/Scarb.toml
rename to listings/advanced-concepts/account_spending_limits/Scarb.toml
index 17df048a..f5671a4a 100644
--- a/listings/applications/aa_spending_limit/Scarb.toml
+++ b/listings/advanced-concepts/account_spending_limits/Scarb.toml
@@ -1,18 +1,18 @@
 [package]
-name = "aa_tutorial"
+name = "account_spending_limits"
 version.workspace = true
-edition = "2023_11"
+edition.workspace = true
 
 [dependencies]
 starknet.workspace = true
 openzeppelin.workspace = true
-snforge_std.workspace = true
 
 [dev-dependencies]
-snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" }
+assert_macros.workspace = true
+snforge_std.workspace = true
 
 [scripts]
 test.workspace = true
 
 [[target.starknet-contract]]
-casm = true
+build-external-contracts = ["openzeppelin_presets::erc20::ERC20Upgradeable"]
diff --git a/listings/advanced-concepts/account_spending_limits/src/account.cairo b/listings/advanced-concepts/account_spending_limits/src/account.cairo
new file mode 100644
index 00000000..922c672d
--- /dev/null
+++ b/listings/advanced-concepts/account_spending_limits/src/account.cairo
@@ -0,0 +1,260 @@
+use starknet::account::Call;
+use starknet::ContractAddress;
+
+#[starknet::interface]
+trait ISRC6<TContractState> {
+    fn __execute__(ref self: TContractState, calls: Array<Call>) -> Array<Span<felt252>>;
+    fn __validate__(self: @TContractState, calls: Array<Call>) -> felt252;
+    fn is_valid_signature(
+        self: @TContractState, hash: felt252, signature: Array<felt252>
+    ) -> felt252;
+}
+
+#[starknet::interface]
+trait ISRC5<TContractState> {
+    fn supports_interface(self: @TContractState, interface_id: felt252) -> bool;
+}
+
+#[starknet::interface]
+trait IDeployableAccount<TContractState> {
+    fn __validate_deploy__(
+        self: @TContractState, class_hash: felt252, salt: felt252, public_key: felt252
+    ) -> felt252;
+}
+
+#[starknet::interface]
+trait IDeclarerAccount<TContractState> {
+    fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252;
+}
+
+#[starknet::interface]
+trait ISpendingLimitsAccount<TContractState> {
+    fn public_key(self: @TContractState) -> felt252;
+    fn get_time_limit(self: @TContractState) -> u64;
+    fn set_spending_limit(ref self: TContractState, token_address: ContractAddress, _limit: u256);
+    fn get_spending_limit_timestamp(self: @TContractState, token_address: ContractAddress) -> u64;
+    fn get_current_spending_limit(self: @TContractState, token_address: ContractAddress) -> u256;
+    fn get_spending_limit(self: @TContractState, token_address: ContractAddress) -> u256;
+}
+
+#[derive(Copy, Drop, Serde, starknet::Store)]
+struct SpendingLimit {
+    exists: bool,
+    timestamp: u64,
+    limit: u256,
+}
+
+#[starknet::contract(account)]
+mod Account {
+    use super::{
+        ISRC6, ISRC5, IDeployableAccount, IDeclarerAccount, ISpendingLimitsAccount, SpendingLimit
+    };
+    use starknet::{
+        ContractAddress, get_caller_address, get_tx_info, VALIDATED, get_block_timestamp,
+        get_contract_address, account::Call, syscalls::call_contract_syscall
+    };
+    use starknet::storage::{
+        Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
+        StoragePointerWriteAccess
+    };
+    use core::num::traits::Zero;
+
+    #[storage]
+    struct Storage {
+        public_key: felt252,
+        spending_limit: Map<ContractAddress, SpendingLimit>,
+        current_spending_limit: Map<ContractAddress, u256>,
+        time_limit: u64,
+    }
+    const SRC6_TRAIT_ID: felt252 =
+        1270010605630597976495846281167968799381097569185364931397797212080166453709;
+
+    pub mod Selectors {
+        pub const TRANSFER: felt252 =
+            0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e;
+        pub const APPROVE: felt252 =
+            0x0219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c;
+    }
+
+    pub mod Errors {
+        pub const INVALID_CALLER: felt252 = 'Account: Invalid caller';
+        pub const INVALID_SIGNATURE: felt252 = 'Account: Invalid tx signature';
+        pub const INVALID_TX_VERSION: felt252 = 'Account: Invalid tx version';
+        pub const UNAUTHORIZED: felt252 = 'Account: Unauthorized';
+    }
+
+    // time_limit is in seconds
+    #[constructor]
+    fn constructor(ref self: ContractState, public_key: felt252, time_limit: u64) {
+        self.public_key.write(public_key);
+        self.time_limit.write(time_limit);
+    }
+
+    //
+    // External
+    //
+
+    #[abi(embed_v0)]
+    impl SRC6 of ISRC6<ContractState> {
+        fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
+            self.only_protocol();
+
+            let mut res = array![];
+            for call in calls {
+                let Call { to, selector, calldata } = call;
+
+                let limit_exists: bool = self.spending_limit.read(to).exists;
+                if (self.is_spending_tx(selector) && limit_exists) {
+                    let low: u128 = (*calldata[1]).try_into().unwrap();
+                    let high: u128 = (*calldata[2]).try_into().unwrap();
+                    let value: u256 = u256 { low, high };
+
+                    let mut current_limit: u256 = self.get_spending_limit(to);
+                    current_limit -= value;
+                    self.current_spending_limit.write(to, current_limit);
+                    self.update_timestamp(to);
+                }
+
+                let syscall_res = call_contract_syscall(to, selector, calldata).unwrap_syscall();
+                res.append(syscall_res);
+            };
+            res
+        }
+
+        fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+
+        fn is_valid_signature(
+            self: @ContractState, hash: felt252, signature: Array<felt252>
+        ) -> felt252 {
+            let is_valid = self._is_valid_signature(hash, signature.span());
+            if is_valid {
+                VALIDATED
+            } else {
+                0
+            }
+        }
+    }
+
+    #[abi(embed_v0)]
+    impl SRC5 of ISRC5<ContractState> {
+        fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
+            interface_id == SRC6_TRAIT_ID
+        }
+    }
+
+    #[abi(embed_v0)]
+    impl DeployableAccount of IDeployableAccount<ContractState> {
+        fn __validate_deploy__(
+            self: @ContractState, class_hash: felt252, salt: felt252, public_key: felt252
+        ) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+    }
+
+    #[abi(embed_v0)]
+    impl DeclarerAccount of IDeclarerAccount<ContractState> {
+        fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
+            self.only_protocol();
+            self.validate_transaction()
+        }
+    }
+
+    #[abi(embed_v0)]
+    impl SpendingLimitsAccount of ISpendingLimitsAccount<ContractState> {
+        fn public_key(self: @ContractState) -> felt252 {
+            self.public_key.read()
+        }
+
+        fn get_time_limit(self: @ContractState) -> u64 {
+            self.time_limit.read()
+        }
+
+        fn set_spending_limit(
+            ref self: ContractState, token_address: ContractAddress, _limit: u256
+        ) {
+            assert(get_caller_address() == get_contract_address(), 'Invalid caller');
+            let timemstamp = get_block_timestamp();
+            let new_limit: SpendingLimit = SpendingLimit {
+                exists: true, limit: _limit, timestamp: timemstamp
+            };
+            self.spending_limit.write(token_address, new_limit);
+            self.current_spending_limit.write(token_address, _limit);
+        }
+
+        fn get_current_spending_limit(
+            self: @ContractState, token_address: ContractAddress
+        ) -> u256 {
+            self.current_spending_limit.read(token_address)
+        }
+
+        fn get_spending_limit_timestamp(
+            self: @ContractState, token_address: ContractAddress
+        ) -> u64 {
+            self.spending_limit.read(token_address).timestamp
+        }
+
+        fn get_spending_limit(self: @ContractState, token_address: ContractAddress) -> u256 {
+            let spending_limit = self.spending_limit.read(token_address);
+            let time_limit: u64 = self.time_limit.read();
+            let current_timestamp: u64 = get_block_timestamp();
+
+            if ((spending_limit.timestamp + time_limit) >= current_timestamp) {
+                self.current_spending_limit.read(token_address)
+            } else {
+                spending_limit.limit
+            }
+        }
+    }
+
+    #[generate_trait]
+    impl PrivateImpl of PrivateTrait {
+        fn only_protocol(self: @ContractState) {
+            let sender = get_caller_address();
+            assert(sender.is_zero(), Errors::INVALID_CALLER);
+        }
+
+        // If the current block timestamp is past the time_limit,
+        // The max new limit set by the account owner is written to the current spending limit.
+        // And the current timestamp is updated.
+        fn update_timestamp(ref self: ContractState, token_address: ContractAddress) {
+            let mut spending_limit = self.spending_limit.read(token_address);
+            let current_timestamp: u64 = get_block_timestamp();
+            let time_limit: u64 = self.time_limit.read();
+            let timestamp: u64 = spending_limit.timestamp;
+
+            if ((timestamp + time_limit) < current_timestamp) {
+                spending_limit.timestamp = current_timestamp;
+                self.spending_limit.write(token_address, spending_limit);
+            }
+        }
+
+        fn is_spending_tx(ref self: ContractState, selector: felt252) -> bool {
+            selector == Selectors::TRANSFER || selector == Selectors::APPROVE
+        }
+
+        fn _is_valid_signature(
+            self: @ContractState, hash: felt252, signature: Span<felt252>
+        ) -> bool {
+            if signature.len() == 2_u32 {
+                core::ecdsa::check_ecdsa_signature(
+                    hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32)
+                )
+            } else {
+                false
+            }
+        }
+
+        fn validate_transaction(self: @ContractState) -> felt252 {
+            let tx_info = get_tx_info().unbox();
+            let tx_hash = tx_info.transaction_hash;
+            let signature = tx_info.signature;
+
+            assert(self._is_valid_signature(tx_hash, signature), Errors::INVALID_SIGNATURE);
+            VALIDATED
+        }
+    }
+}
diff --git a/listings/applications/aa_spending_limit/src/erc20.cairo b/listings/advanced-concepts/account_spending_limits/src/erc20.cairo
similarity index 100%
rename from listings/applications/aa_spending_limit/src/erc20.cairo
rename to listings/advanced-concepts/account_spending_limits/src/erc20.cairo
diff --git a/listings/applications/aa_spending_limit/src/lib.cairo b/listings/advanced-concepts/account_spending_limits/src/lib.cairo
similarity index 100%
rename from listings/applications/aa_spending_limit/src/lib.cairo
rename to listings/advanced-concepts/account_spending_limits/src/lib.cairo
diff --git a/listings/applications/aa_spending_limit/tests/aa.cairo b/listings/advanced-concepts/account_spending_limits/tests/aa.cairo
similarity index 100%
rename from listings/applications/aa_spending_limit/tests/aa.cairo
rename to listings/advanced-concepts/account_spending_limits/tests/aa.cairo
diff --git a/listings/applications/aa_spending_limit/tests/lib.cairo b/listings/advanced-concepts/account_spending_limits/tests/lib.cairo
similarity index 100%
rename from listings/applications/aa_spending_limit/tests/lib.cairo
rename to listings/advanced-concepts/account_spending_limits/tests/lib.cairo
diff --git a/listings/applications/aa_spending_limit/tests/utils.cairo b/listings/advanced-concepts/account_spending_limits/tests/utils.cairo
similarity index 100%
rename from listings/applications/aa_spending_limit/tests/utils.cairo
rename to listings/advanced-concepts/account_spending_limits/tests/utils.cairo
diff --git a/listings/applications/aa_spending_limit/src/account.cairo b/listings/applications/aa_spending_limit/src/account.cairo
deleted file mode 100644
index d02c1e4a..00000000
--- a/listings/applications/aa_spending_limit/src/account.cairo
+++ /dev/null
@@ -1,227 +0,0 @@
-use starknet::account::Call;
-use starknet::ContractAddress;
-
-#[starknet::interface]
-trait IAccount<T> {
-    fn public_key(self: @T) -> felt252;
-    fn get_time_limit(self: @T) -> u64;
-    fn supports_interface(self: @T, interface_id: felt252) -> bool;
-    fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
-    fn set_spending_limit(ref self: T, token_address: ContractAddress, _limit: u256);
-    fn get_spending_limit_timestamp(self: @T, token_address: ContractAddress) -> u64;
-    fn get_current_spending_limit(self: @T, token_address: ContractAddress) -> u256;
-    fn get_spending_limit(self: @T, token_address: ContractAddress) -> u256;
-    fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
-    fn __validate__(self: @T, calls: Array<Call>) -> felt252;
-    fn __validate_declare__(self: @T, class_hash: felt252) -> felt252;
-    fn __validate_deploy__(
-        self: @T, class_hash: felt252, salt: felt252, public_key: felt252
-    ) -> felt252;
-}
-
-#[derive(Copy, Drop, Serde, starknet::Store)]
-struct SpendingLimit {
-    exists: bool,
-    timestamp: u64,
-    limit: u256,
-}
-
-#[starknet::contract(account)]
-mod Account {
-    use core::traits::Into;
-    use core::traits::TryInto;
-    use super::{Call, IAccount, SpendingLimit};
-    use core::Felt252TryIntoU128;
-    use core::integer::u256_checked_sub;
-    use starknet::{
-        ContractAddress, get_caller_address, call_contract_syscall, get_tx_info, VALIDATED,
-        get_block_timestamp, get_contract_address
-    };
-    use zeroable::Zeroable;
-    use ecdsa::check_ecdsa_signature;
-
-    const SRC6_TRAIT_ID: felt252 =
-        1270010605630597976495846281167968799381097569185364931397797212080166453709; // hash of SNIP-6 trait
-
-    const transfer_selector: felt252 =
-        0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e;
-
-    const approve_selector: felt252 =
-        0x0219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c;
-
-
-    #[storage]
-    struct Storage {
-        public_key: felt252,
-        spending_limit: LegacyMap<ContractAddress, SpendingLimit>,
-        current_spending_limit: LegacyMap<ContractAddress, u256>,
-        time_limit: u64,
-    }
-
-    //time_limit is in seconds
-    #[constructor]
-    fn constructor(ref self: ContractState, public_key: felt252, time_limit: u64) {
-        self.public_key.write(public_key);
-        self.time_limit.write(time_limit);
-    }
-
-    #[abi(embed_v0)]
-    impl AccountImpl of IAccount<ContractState> {
-        fn public_key(self: @ContractState) -> felt252 {
-            self.public_key.read()
-        }
-
-        fn get_time_limit(self: @ContractState) -> u64 {
-            self.time_limit.read()
-        }
-
-        fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
-            interface_id == SRC6_TRAIT_ID
-        }
-
-        fn is_valid_signature(
-            self: @ContractState, hash: felt252, signature: Array<felt252>
-        ) -> felt252 {
-            let is_valid = self.is_valid_signature_bool(hash, signature.span());
-            if is_valid {
-                VALIDATED
-            } else {
-                0
-            }
-        }
-
-        fn set_spending_limit(
-            ref self: ContractState, token_address: ContractAddress, _limit: u256
-        ) {
-            assert(get_caller_address() == get_contract_address(), 'Invalid caller');
-            let timemstamp = get_block_timestamp();
-            let new_limit: SpendingLimit = SpendingLimit {
-                exists: true, limit: _limit, timestamp: timemstamp
-            };
-            self.spending_limit.write(token_address, new_limit);
-            self.current_spending_limit.write(token_address, _limit);
-        }
-
-        fn get_current_spending_limit(
-            self: @ContractState, token_address: ContractAddress
-        ) -> u256 {
-            self.current_spending_limit.read(token_address)
-        }
-
-        fn get_spending_limit_timestamp(
-            self: @ContractState, token_address: ContractAddress
-        ) -> u64 {
-            self.spending_limit.read(token_address).timestamp
-        }
-
-        fn get_spending_limit(self: @ContractState, token_address: ContractAddress) -> u256 {
-            let spending_limit = self.spending_limit.read(token_address);
-            let current_timestamp: u64 = get_block_timestamp();
-            let time_limit: u64 = self.time_limit.read();
-            let timestamp: u64 = spending_limit.timestamp;
-
-            if ((timestamp + time_limit) >= current_timestamp) {
-                return self.current_spending_limit.read(token_address);
-            }
-            return spending_limit.limit;
-        }
-
-        fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
-            assert(!calls.is_empty(), 'Account: No call data given');
-            self.only_protocol();
-            let mut res = ArrayTrait::new();
-            loop {
-                match calls.pop_front() {
-                    Option::Some(call) => {
-                        let Call { to, selector, calldata } = call;
-                        let limit_exists: bool = self.spending_limit.read(to).exists;
-
-                        if (self.is_spending_tx(selector) && limit_exists) {
-                            let low: u128 = Felt252TryIntoU128::try_into(*calldata[1]).unwrap();
-                            let high: u128 = Felt252TryIntoU128::try_into(*calldata[2]).unwrap();
-                            let value: u256 = u256 { low, high };
-
-                            let mut current_limit: u256 = self.get_spending_limit(to);
-                            current_limit -= value;
-                            self.current_spending_limit.write(to, current_limit);
-                            self.update_timestamp(to);
-                        }
-                        let _res = call_contract_syscall(to, selector, calldata).unwrap();
-                        res.append(_res);
-                    },
-                    Option::None(_) => { break (); },
-                };
-            };
-            res
-        }
-
-        fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
-            self.only_protocol();
-            self.validate_transaction()
-        }
-
-        fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
-            self.only_protocol();
-            self.validate_transaction()
-        }
-
-        fn __validate_deploy__(
-            self: @ContractState, class_hash: felt252, salt: felt252, public_key: felt252
-        ) -> felt252 {
-            self.only_protocol();
-            self.validate_transaction()
-        }
-    }
-
-    #[generate_trait]
-    impl PrivateImpl of PrivateTrait {
-        fn only_protocol(self: @ContractState) {
-            let sender: ContractAddress = get_caller_address();
-            assert(sender.is_zero(), 'Account: invalid caller');
-        }
-
-        //If the current block timestamp is past the time_limit, 
-        //The max new limit set by the account owner is written to the current spending limit.
-        //And the current timestamp is updated.
-        fn update_timestamp(ref self: ContractState, token_address: ContractAddress) {
-            let mut spending_limit = self.spending_limit.read(token_address);
-            let current_timestamp: u64 = get_block_timestamp();
-            let time_limit: u64 = self.time_limit.read();
-            let timestamp: u64 = spending_limit.timestamp;
-
-            if ((timestamp + time_limit) < current_timestamp) {
-                spending_limit.timestamp = current_timestamp;
-                self.spending_limit.write(token_address, spending_limit);
-            }
-        }
-
-        fn is_spending_tx(ref self: ContractState, selector: felt252) -> bool {
-            if (selector == transfer_selector || selector == approve_selector) {
-                return true;
-            }
-            return false;
-        }
-
-        fn is_valid_signature_bool(
-            self: @ContractState, hash: felt252, signature: Span<felt252>
-        ) -> bool {
-            let is_valid_length = signature.len() == 2_u32;
-            if !is_valid_length {
-                return false;
-            }
-            check_ecdsa_signature(
-                hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32)
-            )
-        }
-
-        fn validate_transaction(self: @ContractState) -> felt252 {
-            let tx_info = get_tx_info().unbox();
-            let tx_hash = tx_info.transaction_hash;
-            let signature = tx_info.signature;
-
-            let is_valid = self.is_valid_signature_bool(tx_hash, signature);
-            assert(is_valid, 'Account: Incorrect tx signature');
-            VALIDATED
-        }
-    }
-}
diff --git a/pages/advanced-concepts/account_abstraction/account_spending_limits.md b/pages/advanced-concepts/account_abstraction/account_spending_limits.md
new file mode 100644
index 00000000..b3f04e17
--- /dev/null
+++ b/pages/advanced-concepts/account_abstraction/account_spending_limits.md
@@ -0,0 +1,34 @@
+# Account Contract with Spending Limits
+
+In this example, we will write an account contract with spending limits.
+
+Before proceeding with this example, make sure that you read the [intro](/advanced-concepts/account_abstraction/account_contract) to account contracts on Starknet which will help you understand how Starknet account contracts work.
+
+## Key Specifications
+
+The account contract will have the following features: 
+
+- Spending limits can be added for any ERC-20 token with any amount by the account owner.
+- A limit will reset after a specified time. The account owner can set any time limit they want (daily, weekly or 12 hours), but once set, it cannot be changed by anyone.
+
+### How to detect that a function call is a spending transaction?
+
+We need to identify the function calls that are spending transactions.
+The token standard (i.e. ERC-20) is defined in the [SNIP-2](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-2.md).
+ In this example, we will consider the "approve" and "transfer" functions as spending transactions.
+
+- If one of the calls has the "approve" or "transfer" function selectors, the spending limit will decrease accordingly by the amount given in the function call. If there is no limit left, the transaction will revert.
+
+## Implementation
+
+The function `__execute__` will check if the function called is "approve" or "transfer", and if yes, the limit will decrease by the amount in the call. Here is the code for the `__execute__` function: 
+
+```cairo
+// Execute
+```
+
+Here is the full code of the account contract with spending limits:
+
+```cairo
+// [!include ~/listings/advanced-concepts/account_spending_limits/src/account.cairo]
+```
\ No newline at end of file
diff --git a/pages/applications/aa_spending_limit.md b/pages/applications/aa_spending_limit.md
deleted file mode 100644
index 46d5dd0d..00000000
--- a/pages/applications/aa_spending_limit.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Account Contract with Spending Limits
-
-In this example, we will write an account contract with spending limits.
-
-Before proceeding with this example, make sure that you read the [intro](../advanced-concepts/account_abstraction/account_contract.md) to account contracts on Starknet which will help you understand how Starknet account contracts work.
-
-## Key Specifications
-The spending limit for the account contract will have the following features: 
-
-- The limit can be set for any ERC-20 token with any amount by the account owner.
-- The limit will reset after a specified time. The account owner could set any time limit they want (daily, weekly or 12 hours).
-- If one of the calls has the "approve" or "transfer" function selectors, the spending limit will decrease accordingly by the amount given in the function call. If there is no limit left, the transaction will revert.
-
-The function `__execute__` will check if the function called is "approve" or "transfer", and if yes, the limit will decrease by the amount in the call. Here is the code for the `__execute__` function: 
-```rs
-fn __execute__(ref self: ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
-            assert(!calls.is_empty(), 'Account: No call data given');
-            self.only_protocol();
-            let mut res = ArrayTrait::new();
-            loop {
-                //Loop through the calls, supporting multicalls.
-                match calls.pop_front() {
-                    Option::Some(call) => {
-                        let Call { to, selector, calldata } = call;
-
-                        //The limit should have been set before by the account owner.
-                        let limit_exists: bool = self.spending_limit.read(to).exists;
-
-                        //is_spending_tx function checks if the function is "approve" or "transfer"
-                        //See the full code below how it is done!
-                        if (self.is_spending_tx(selector) && limit_exists) {
-                            let low: u128 = Felt252TryIntoU128::try_into(*calldata[1]).unwrap();
-                            let high: u128 = Felt252TryIntoU128::try_into(*calldata[2]).unwrap();
-
-                            //Extract the value the account approves/transfers from the calldata
-                            let value: u256 = u256 { low, high }; 
-                            
-                            //Update the spending limit for other calls in this tx or for future txs.
-                            let mut current_limit: u256 = self.get_spending_limit(to);
-                            current_limit -= value;
-                            self.current_spending_limit.write(to, current_limit);
-                            self.update_timestamp(to);
-                        }
-
-                        //calls the function after spending limit & timestamp were updated.
-                        let _res = call_contract_syscall(to, selector, calldata).unwrap();
-                        res.append(_res);
-                    },
-                    Option::None(_) => { break (); },
-                };
-            };
-            res
-        }
-```
-
-Here is the full code of the account contract with spending limits:
-
-```rs
-{{#rustdoc_include ../../listings/applications/aa_spending_limit/src/account.cairo}}
-```
\ No newline at end of file
diff --git a/routes.ts b/routes.ts
index 1969da23..21a70023 100644
--- a/routes.ts
+++ b/routes.ts
@@ -213,6 +213,10 @@ const config: Sidebar = [
             text: "Account Contract",
             link: "/advanced-concepts/account_abstraction/account_contract",
           },
+          {
+            text: "Account with Spending Limits",
+            link: "/advanced-concepts/account_abstraction/account_spending_limits",
+          }
         ],
       },
       {