From 940093d3c53bfa3bbc5497d28a1919cfaa744c1a Mon Sep 17 00:00:00 2001
From: tuxmain <tuxmain@zettascript.org>
Date: Thu, 4 Aug 2022 17:13:39 +0200
Subject: [PATCH] feat(oneshot-account): Pallet oneshot-account

---
 Cargo.lock                                    |  21 +
 Cargo.toml                                    |   1 +
 .../cucumber-features/oneshot_account.feature |  21 +
 end2end-tests/tests/common/mod.rs             |   1 +
 end2end-tests/tests/common/oneshot.rs         | 116 ++++++
 end2end-tests/tests/cucumber_tests.rs         | 105 ++++-
 pallets/oneshot-account/Cargo.toml            |  84 ++++
 pallets/oneshot-account/src/benchmarking.rs   | 138 +++++++
 pallets/oneshot-account/src/check_nonce.rs    |  86 ++++
 pallets/oneshot-account/src/lib.rs            | 377 ++++++++++++++++++
 pallets/oneshot-account/src/mock.rs           | 123 ++++++
 pallets/oneshot-account/src/types.rs          |  24 ++
 pallets/oneshot-account/src/weights.rs        |  52 +++
 resources/metadata.scale                      | Bin 122124 -> 125548 bytes
 runtime/common/Cargo.toml                     |   2 +
 runtime/common/src/pallets_config.rs          |   8 +-
 .../src/weights/pallet_oneshot_account.rs     |  70 ++++
 runtime/g1/Cargo.toml                         |   2 +
 runtime/g1/src/lib.rs                         |   3 +-
 runtime/gdev/Cargo.toml                       |   2 +
 runtime/gdev/src/lib.rs                       |   5 +-
 runtime/gdev/tests/integration_tests.rs       |  78 ++++
 runtime/gtest/Cargo.toml                      |   2 +
 runtime/gtest/src/lib.rs                      |   1 +
 24 files changed, 1317 insertions(+), 5 deletions(-)
 create mode 100644 end2end-tests/cucumber-features/oneshot_account.feature
 create mode 100644 end2end-tests/tests/common/oneshot.rs
 create mode 100644 pallets/oneshot-account/Cargo.toml
 create mode 100644 pallets/oneshot-account/src/benchmarking.rs
 create mode 100644 pallets/oneshot-account/src/check_nonce.rs
 create mode 100644 pallets/oneshot-account/src/lib.rs
 create mode 100644 pallets/oneshot-account/src/mock.rs
 create mode 100644 pallets/oneshot-account/src/types.rs
 create mode 100644 pallets/oneshot-account/src/weights.rs
 create mode 100644 runtime/common/src/weights/pallet_oneshot_account.rs

diff --git a/Cargo.lock b/Cargo.lock
index 0029d6e7f..427dd1171 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -887,6 +887,7 @@ dependencies = [
  "pallet-identity",
  "pallet-membership",
  "pallet-multisig",
+ "pallet-oneshot-account",
  "pallet-provide-randomness",
  "pallet-proxy",
  "pallet-scheduler",
@@ -2426,6 +2427,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -2494,6 +2496,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -2776,6 +2779,7 @@ dependencies = [
  "pallet-membership",
  "pallet-multisig",
  "pallet-offences",
+ "pallet-oneshot-account",
  "pallet-preimage",
  "pallet-provide-randomness",
  "pallet-proxy",
@@ -5309,6 +5313,23 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-oneshot-account"
+version = "3.0.0"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "pallet-transaction-payment",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-preimage"
 version = "4.0.0-dev"
diff --git a/Cargo.toml b/Cargo.toml
index d6ddf72f3..63920b577 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -133,6 +133,7 @@ members = [
     'pallets/duniter-wot',
     'pallets/identity',
     'pallets/membership',
+    'pallets/oneshot-account',
     'pallets/authority-members',
     'pallets/universal-dividend',
     'pallets/upgrade-origin',
diff --git a/end2end-tests/cucumber-features/oneshot_account.feature b/end2end-tests/cucumber-features/oneshot_account.feature
new file mode 100644
index 000000000..f3d74b790
--- /dev/null
+++ b/end2end-tests/cucumber-features/oneshot_account.feature
@@ -0,0 +1,21 @@
+Feature: Oneshot account
+
+  Scenario: Simple oneshot consumption
+    When alice sends 7 ÄžD to oneshot dave
+    Then alice should have 3 ÄžD
+    Then dave should have oneshot 7 ÄžD
+    When oneshot dave consumes into account bob
+    Then dave should have oneshot 0 ÄžD
+    Then bob should have 1699 cÄžD
+    Then bob should have oneshot 0 ÄžD
+
+  Scenario: Double oneshot consumption
+    When alice sends 7 ÄžD to oneshot dave
+    Then alice should have 3 ÄžD
+    Then dave should have oneshot 7 ÄžD
+    When oneshot dave consumes 4 ÄžD into account bob and the rest into oneshot charlie
+    Then dave should have oneshot 0 ÄžD
+    Then bob should have 14 ÄžD
+    Then bob should have oneshot 0 ÄžD
+    Then charlie should have 10 ÄžD
+    Then charlie should have oneshot 299 cÄžD
diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs
index 56317abf6..fb464d5a7 100644
--- a/end2end-tests/tests/common/mod.rs
+++ b/end2end-tests/tests/common/mod.rs
@@ -18,6 +18,7 @@
 
 pub mod balances;
 pub mod cert;
+pub mod oneshot;
 
 #[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
 pub mod node_runtime {}
diff --git a/end2end-tests/tests/common/oneshot.rs b/end2end-tests/tests/common/oneshot.rs
new file mode 100644
index 000000000..b8827fe88
--- /dev/null
+++ b/end2end-tests/tests/common/oneshot.rs
@@ -0,0 +1,116 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use super::node_runtime::runtime_types::gdev_runtime;
+use super::node_runtime::runtime_types::pallet_balances;
+use super::node_runtime::runtime_types::pallet_oneshot_account;
+use super::*;
+use sp_keyring::AccountKeyring;
+use subxt::{
+    sp_runtime::{AccountId32, MultiAddress},
+    PairSigner,
+};
+
+pub enum Account {
+    Normal(AccountKeyring),
+    Oneshot(AccountKeyring),
+}
+
+impl Account {
+    fn to_account_id(
+        &self,
+    ) -> pallet_oneshot_account::types::Account<MultiAddress<AccountId32, ()>> {
+        match self {
+            Account::Normal(account) => {
+                pallet_oneshot_account::types::Account::Normal(account.to_account_id().into())
+            }
+            Account::Oneshot(account) => {
+                pallet_oneshot_account::types::Account::Oneshot(account.to_account_id().into())
+            }
+        }
+    }
+}
+
+pub async fn create_oneshot_account(
+    api: &Api,
+    client: &Client,
+    from: AccountKeyring,
+    amount: u64,
+    to: AccountKeyring,
+) -> Result<()> {
+    let from = PairSigner::new(from.pair());
+    let to = to.to_account_id();
+
+    let _events = create_block_with_extrinsic(
+        client,
+        api.tx()
+            .oneshot_account()
+            .create_oneshot_account(to.into(), amount)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
+
+pub async fn consume_oneshot_account(
+    api: &Api,
+    client: &Client,
+    from: AccountKeyring,
+    to: Account,
+) -> Result<()> {
+    let from = PairSigner::new(from.pair());
+    let to = to.to_account_id();
+
+    let _events = create_block_with_extrinsic(
+        client,
+        api.tx()
+            .oneshot_account()
+            .consume_oneshot_account(0, to)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
+
+#[allow(clippy::too_many_arguments)]
+pub async fn consume_oneshot_account_with_remaining(
+    api: &Api,
+    client: &Client,
+    from: AccountKeyring,
+    amount: u64,
+    to: Account,
+    remaining_to: Account,
+) -> Result<()> {
+    let from = PairSigner::new(from.pair());
+    let to = to.to_account_id();
+    let remaining_to = remaining_to.to_account_id();
+
+    let _events = create_block_with_extrinsic(
+        client,
+        api.tx()
+            .oneshot_account()
+            .consume_oneshot_account_with_remaining(0, to, remaining_to, amount)?
+            .create_signed(&from, BaseExtrinsicParamsBuilder::new())
+            .await?,
+    )
+    .await?;
+
+    Ok(())
+}
diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs
index 265b78295..303787361 100644
--- a/end2end-tests/tests/cucumber_tests.rs
+++ b/end2end-tests/tests/cucumber_tests.rs
@@ -155,7 +155,7 @@ async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> {
     Ok(())
 }
 
-#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ÄžD|cÄžD|UD|mUD) to ([a-zA-Z]+)")]
+#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ÄžD|cÄžD|UD|mUD) to ([a-zA-Z]+)$")]
 async fn transfer(
     world: &mut DuniterWorld,
     from: String,
@@ -181,6 +181,86 @@ async fn transfer(
     }
 }
 
+#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ÄžD|cÄžD) to oneshot ([a-zA-Z]+)")]
+async fn create_oneshot_account(
+    world: &mut DuniterWorld,
+    from: String,
+    amount: u64,
+    unit: String,
+    to: String,
+) -> Result<()> {
+    // Parse inputs
+    let from = AccountKeyring::from_str(&from).expect("unknown from");
+    let to = AccountKeyring::from_str(&to).expect("unknown to");
+    let (amount, is_ud) = parse_amount(amount, &unit);
+
+    assert!(!is_ud);
+
+    common::oneshot::create_oneshot_account(world.api(), world.client(), from, amount, to).await
+}
+
+#[when(regex = r"oneshot ([a-zA-Z]+) consumes? into (oneshot|account) ([a-zA-Z]+)")]
+async fn consume_oneshot_account(
+    world: &mut DuniterWorld,
+    from: String,
+    is_dest_oneshot: String,
+    to: String,
+) -> Result<()> {
+    // Parse inputs
+    let from = AccountKeyring::from_str(&from).expect("unknown from");
+    let to = AccountKeyring::from_str(&to).expect("unknown to");
+    let to = match is_dest_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(to),
+        "account" => common::oneshot::Account::Normal(to),
+        _ => unreachable!(),
+    };
+
+    common::oneshot::consume_oneshot_account(world.api(), world.client(), from, to).await
+}
+
+#[when(
+    regex = r"oneshot ([a-zA-Z]+) consumes? (\d+) (ÄžD|cÄžD) into (oneshot|account) ([a-zA-Z]+) and the rest into (oneshot|account) ([a-zA-Z]+)"
+)]
+#[allow(clippy::too_many_arguments)]
+async fn consume_oneshot_account_with_remaining(
+    world: &mut DuniterWorld,
+    from: String,
+    amount: u64,
+    unit: String,
+    is_dest_oneshot: String,
+    to: String,
+    is_remaining_to_oneshot: String,
+    remaining_to: String,
+) -> Result<()> {
+    // Parse inputs
+    let from = AccountKeyring::from_str(&from).expect("unknown from");
+    let to = AccountKeyring::from_str(&to).expect("unknown to");
+    let remaining_to = AccountKeyring::from_str(&remaining_to).expect("unknown remaining_to");
+    let to = match is_dest_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(to),
+        "account" => common::oneshot::Account::Normal(to),
+        _ => unreachable!(),
+    };
+    let remaining_to = match is_remaining_to_oneshot.as_str() {
+        "oneshot" => common::oneshot::Account::Oneshot(remaining_to),
+        "account" => common::oneshot::Account::Normal(remaining_to),
+        _ => unreachable!(),
+    };
+    let (amount, is_ud) = parse_amount(amount, &unit);
+
+    assert!(!is_ud);
+
+    common::oneshot::consume_oneshot_account_with_remaining(
+        world.api(),
+        world.client(),
+        from,
+        amount,
+        to,
+        remaining_to,
+    )
+    .await
+}
+
 #[when(regex = r"([a-zA-Z]+) sends? all (?:his|her) (?:ÄžDs?|DUs?|UDs?) to ([a-zA-Z]+)")]
 async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
     // Parse inputs
@@ -219,6 +299,29 @@ async fn should_have(
     Ok(())
 }
 
+#[then(regex = r"([a-zA-Z]+) should have oneshot (\d+) (ÄžD|cÄžD)")]
+async fn should_have_oneshot(
+    world: &mut DuniterWorld,
+    who: String,
+    amount: u64,
+    unit: String,
+) -> Result<()> {
+    // Parse inputs
+    let who = AccountKeyring::from_str(&who)
+        .expect("unknown to")
+        .to_account_id();
+    let (amount, _is_ud) = parse_amount(amount, &unit);
+
+    let oneshot_amount = world
+        .api()
+        .storage()
+        .oneshot_account()
+        .oneshot_accounts(&who, None)
+        .await?;
+    assert_eq!(oneshot_amount.unwrap_or(0), amount);
+    Ok(())
+}
+
 #[then(regex = r"Current UD amount should be (\d+).(\d+)")]
 async fn current_ud_amount_should_be(
     world: &mut DuniterWorld,
diff --git a/pallets/oneshot-account/Cargo.toml b/pallets/oneshot-account/Cargo.toml
new file mode 100644
index 000000000..bba1f932d
--- /dev/null
+++ b/pallets/oneshot-account/Cargo.toml
@@ -0,0 +1,84 @@
+[package]
+authors = ['librelois <c@elo.tf>']
+description = 'FRAME pallet oneshot account.'
+edition = '2018'
+homepage = 'https://substrate.dev'
+license = 'AGPL-3.0'
+name = 'pallet-oneshot-account'
+readme = 'README.md'
+repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
+version = '3.0.0'
+
+[features]
+default = ['std']
+runtime-benchmarks = ['frame-benchmarking']
+std = [
+    'codec/std',
+    'frame-support/std',
+    'frame-system/std',
+    'frame-benchmarking/std',
+    'sp-core/std',
+    'sp-io/std',
+    'sp-runtime/std',
+    'sp-std/std',
+]
+try-runtime = ['frame-support/try-runtime']
+
+[dependencies]
+# crates.io
+codec = { package = 'parity-scale-codec', version = "3.1.5", default-features = false, features = ["derive"] }
+log = { version = "0.4.14", default-features = false }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
+
+# substrate
+[dependencies.frame-benchmarking]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+optional = true
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.frame-support]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.frame-system]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.pallet-transaction-payment]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-core]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-io]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-runtime]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+[dependencies.sp-std]
+default-features = false
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
+
+### DOC ###
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+### DEV ###
+
+[dev-dependencies.sp-io]
+git = 'https://github.com/duniter/substrate'
+branch = 'duniter-substrate-v0.9.23'
diff --git a/pallets/oneshot-account/src/benchmarking.rs b/pallets/oneshot-account/src/benchmarking.rs
new file mode 100644
index 000000000..fab093943
--- /dev/null
+++ b/pallets/oneshot-account/src/benchmarking.rs
@@ -0,0 +1,138 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Duniter-v2S is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+
+use frame_benchmarking::{account, benchmarks, whitelisted_caller};
+use frame_support::pallet_prelude::IsType;
+use frame_support::traits::Get;
+use frame_system::RawOrigin;
+use pallet_balances::Pallet as Balances;
+
+use crate::Pallet;
+
+const SEED: u32 = 0;
+
+benchmarks! {
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>
+    }
+    create_oneshot_account {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        let _ = T::Currency::make_free_balance_be(&caller, balance.into());
+
+        let recipient: T::AccountId = account("recipient", 0, SEED);
+        let recipient_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient.clone());
+        let transfer_amount = existential_deposit;
+    }: _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount.into())
+    verify {
+        assert_eq!(Balances::<T>::free_balance(&caller), transfer_amount);
+        assert_eq!(OneshotAccounts::<T>::get(&recipient), Some(transfer_amount.into()));
+    }
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>+From<T::Balance>
+    }
+    consume_oneshot_account {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller: T::AccountId = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        OneshotAccounts::<T>::insert(
+            caller.clone(),
+            Into::<<T::Currency as Currency<T::AccountId>>::Balance>::into(balance)
+        );
+
+        // Deposit into a normal account is more expensive than into a oneshot account
+        // so we create the recipient account with an existential deposit.
+        let recipient: T::AccountId = account("recipient", 0, SEED);
+        let recipient_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient, existential_deposit.into());
+    }: _(
+        RawOrigin::Signed(caller.clone()),
+        T::BlockNumber::zero(),
+        Account::<T>::Normal(recipient_lookup)
+    )
+    verify {
+        assert_eq!(OneshotAccounts::<T>::get(&caller), None);
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient),
+            existential_deposit.saturating_mul((3).into())
+        );
+    }
+    where_clause { where
+        T: pallet_balances::Config,
+        T::Balance: From<u64>,
+        <T::Currency as Currency<T::AccountId>>::Balance: IsType<T::Balance>+From<T::Balance>
+    }
+    consume_oneshot_account_with_remaining {
+        let existential_deposit = T::ExistentialDeposit::get();
+        let caller: T::AccountId = whitelisted_caller();
+
+        // Give some multiple of the existential deposit
+        let balance = existential_deposit.saturating_mul((2).into());
+        OneshotAccounts::<T>::insert(
+            caller.clone(),
+            Into::<<T::Currency as Currency<T::AccountId>>::Balance>::into(balance)
+        );
+
+        // Deposit into a normal account is more expensive than into a oneshot account
+        // so we create the recipient accounts with an existential deposits.
+        let recipient1: T::AccountId = account("recipient1", 0, SEED);
+        let recipient1_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient1.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient1, existential_deposit.into());
+        let recipient2: T::AccountId = account("recipient2", 1, SEED);
+        let recipient2_lookup: <T::Lookup as StaticLookup>::Source =
+            T::Lookup::unlookup(recipient2.clone());
+        let _ = T::Currency::make_free_balance_be(&recipient2, existential_deposit.into());
+    }: _(
+        RawOrigin::Signed(caller.clone()),
+        T::BlockNumber::zero(),
+        Account::<T>::Normal(recipient1_lookup),
+        Account::<T>::Normal(recipient2_lookup),
+        existential_deposit.into()
+    )
+    verify {
+        assert_eq!(OneshotAccounts::<T>::get(&caller), None);
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient1),
+            existential_deposit.saturating_mul((2).into())
+        );
+        assert_eq!(
+            Balances::<T>::free_balance(&recipient2),
+            existential_deposit.saturating_mul((2).into())
+        );
+    }
+
+    impl_benchmark_test_suite!(
+        Pallet,
+        crate::mock::new_test_ext(),
+        crate::mock::Test
+    );
+}
diff --git a/pallets/oneshot-account/src/check_nonce.rs b/pallets/oneshot-account/src/check_nonce.rs
new file mode 100644
index 000000000..056f616f2
--- /dev/null
+++ b/pallets/oneshot-account/src/check_nonce.rs
@@ -0,0 +1,86 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use crate::Config;
+
+use codec::{Decode, Encode};
+use frame_support::traits::IsSubType;
+use frame_support::weights::DispatchInfo;
+//use frame_system::Config;
+use scale_info::TypeInfo;
+use sp_runtime::{
+    traits::{DispatchInfoOf, Dispatchable, SignedExtension},
+    transaction_validity::{TransactionValidity, TransactionValidityError},
+};
+
+#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
+#[scale_info(skip_type_params(Runtime))]
+pub struct CheckNonce<T: Config>(pub frame_system::CheckNonce<T>);
+
+impl<T: Config> sp_std::fmt::Debug for CheckNonce<T> {
+    #[cfg(feature = "std")]
+    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+        write!(f, "CheckNonce({})", self.0 .0)
+    }
+
+    #[cfg(not(feature = "std"))]
+    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+        Ok(())
+    }
+}
+
+impl<T: Config + TypeInfo> SignedExtension for CheckNonce<T>
+where
+    T::Call: Dispatchable<Info = DispatchInfo> + IsSubType<crate::Call<T>>,
+{
+    type AccountId = <T as frame_system::Config>::AccountId;
+    type Call = <T as frame_system::Config>::Call;
+    type AdditionalSigned = ();
+    type Pre = ();
+    const IDENTIFIER: &'static str = "OneshotAccountCheckNonce";
+
+    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
+        self.0.additional_signed()
+    }
+
+    fn pre_dispatch(
+        self,
+        who: &Self::AccountId,
+        call: &Self::Call,
+        info: &DispatchInfoOf<Self::Call>,
+        len: usize,
+    ) -> Result<(), TransactionValidityError> {
+        if let Some(
+            crate::Call::consume_oneshot_account { .. }
+            | crate::Call::consume_oneshot_account_with_remaining { .. },
+        ) = call.is_sub_type()
+        {
+            Ok(())
+        } else {
+            self.0.pre_dispatch(who, call, info, len)
+        }
+    }
+
+    fn validate(
+        &self,
+        who: &Self::AccountId,
+        call: &Self::Call,
+        info: &DispatchInfoOf<Self::Call>,
+        len: usize,
+    ) -> TransactionValidity {
+        self.0.validate(who, call, info, len)
+    }
+}
diff --git a/pallets/oneshot-account/src/lib.rs b/pallets/oneshot-account/src/lib.rs
new file mode 100644
index 000000000..8622faef3
--- /dev/null
+++ b/pallets/oneshot-account/src/lib.rs
@@ -0,0 +1,377 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod check_nonce;
+mod types;
+
+pub use check_nonce::CheckNonce;
+pub use pallet::*;
+pub use types::*;
+
+use frame_support::pallet_prelude::*;
+use frame_support::traits::{
+    Currency, ExistenceRequirement, Imbalance, IsSubType, WithdrawReasons,
+};
+use frame_system::pallet_prelude::*;
+use pallet_transaction_payment::OnChargeTransaction;
+use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, StaticLookup, Zero};
+use sp_std::convert::TryInto;
+
+#[frame_support::pallet]
+pub mod pallet {
+    use super::*;
+    use frame_support::traits::StorageVersion;
+
+    /// The current storage version.
+    const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+
+    #[pallet::pallet]
+    #[pallet::generate_store(pub(super) trait Store)]
+    #[pallet::storage_version(STORAGE_VERSION)]
+    #[pallet::without_storage_info]
+    pub struct Pallet<T>(_);
+
+    // CONFIG //
+
+    #[pallet::config]
+    pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
+        type Currency: Currency<Self::AccountId>;
+        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+        type InnerOnChargeTransaction: OnChargeTransaction<Self>;
+    }
+
+    // STORAGE //
+
+    #[pallet::storage]
+    #[pallet::getter(fn oneshot_account)]
+    pub type OneshotAccounts<T: Config> = StorageMap<
+        _,
+        Blake2_128Concat,
+        T::AccountId,
+        <T::Currency as Currency<T::AccountId>>::Balance,
+        OptionQuery,
+    >;
+
+    // EVENTS //
+
+    #[allow(clippy::type_complexity)]
+    #[pallet::event]
+    #[pallet::generate_deposit(pub(super) fn deposit_event)]
+    pub enum Event<T: Config> {
+        OneshotAccountCreated {
+            account: T::AccountId,
+            balance: <T::Currency as Currency<T::AccountId>>::Balance,
+            creator: T::AccountId,
+        },
+        OneshotAccountConsumed {
+            account: T::AccountId,
+            dest1: (
+                T::AccountId,
+                <T::Currency as Currency<T::AccountId>>::Balance,
+            ),
+            dest2: Option<(
+                T::AccountId,
+                <T::Currency as Currency<T::AccountId>>::Balance,
+            )>,
+        },
+        Withdraw {
+            account: T::AccountId,
+            balance: <T::Currency as Currency<T::AccountId>>::Balance,
+        },
+    }
+
+    // ERRORS //
+
+    #[pallet::error]
+    pub enum Error<T> {
+        /// Block height is in the future
+        BlockHeightInFuture,
+        /// Block height is too old
+        BlockHeightTooOld,
+        /// Destination account does not exist
+        DestAccountNotExist,
+        /// Destination account has balance less than existential deposit
+        ExistentialDeposit,
+        /// Source account has insufficient balance
+        InsufficientBalance,
+        /// Destination oneshot account already exists
+        OneshotAccountAlreadyCreated,
+        /// Source oneshot account does not exist
+        OneshotAccountNotExist,
+    }
+
+    // CALLS //
+    #[pallet::call]
+    impl<T: Config> Pallet<T> {
+        /// Create an account that can only be consumed once
+        ///
+        /// - `dest`: The oneshot account to be created.
+        /// - `balance`: The balance to be transfered to this oneshot account.
+        ///
+        /// Origin account is kept alive.
+        #[pallet::weight(500_000_000)]
+        pub fn create_oneshot_account(
+            origin: OriginFor<T>,
+            dest: <T::Lookup as StaticLookup>::Source,
+            #[pallet::compact] value: <T::Currency as Currency<T::AccountId>>::Balance,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+            let dest = T::Lookup::lookup(dest)?;
+
+            ensure!(
+                value >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                Error::<T>::ExistentialDeposit
+            );
+            ensure!(
+                OneshotAccounts::<T>::get(&dest).is_none(),
+                Error::<T>::OneshotAccountAlreadyCreated
+            );
+
+            <T::Currency as Currency<T::AccountId>>::withdraw(
+                &transactor,
+                value,
+                WithdrawReasons::TRANSFER,
+                ExistenceRequirement::KeepAlive,
+            )?;
+            OneshotAccounts::<T>::insert(&dest, value);
+            Self::deposit_event(Event::OneshotAccountCreated {
+                account: dest,
+                balance: value,
+                creator: transactor,
+            });
+
+            Ok(())
+        }
+        /// Consume a oneshot account and transfer its balance to an account
+        ///
+        /// - `block_height`: Must be a recent block number. The limit is `BlockHashCount` in the past. (this is to prevent replay attacks)
+        /// - `dest`: The destination account.
+        /// - `dest_is_oneshot`: If set to `true`, then a oneshot account is created at `dest`. Else, `dest` has to be an existing account.
+        #[pallet::weight(500_000_000)]
+        pub fn consume_oneshot_account(
+            origin: OriginFor<T>,
+            block_height: T::BlockNumber,
+            dest: Account<<T::Lookup as StaticLookup>::Source>,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+
+            let (dest, dest_is_oneshot) = match dest {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            let dest = T::Lookup::lookup(dest)?;
+
+            let value = OneshotAccounts::<T>::take(&transactor)
+                .ok_or(Error::<T>::OneshotAccountNotExist)?;
+
+            ensure!(
+                block_height <= frame_system::Pallet::<T>::block_number(),
+                Error::<T>::BlockHeightInFuture
+            );
+            ensure!(
+                frame_system::pallet::BlockHash::<T>::contains_key(block_height),
+                Error::<T>::BlockHeightTooOld
+            );
+            if dest_is_oneshot {
+                ensure!(
+                    OneshotAccounts::<T>::get(&dest).is_none(),
+                    Error::<T>::OneshotAccountAlreadyCreated
+                );
+                OneshotAccounts::<T>::insert(&dest, value);
+                Self::deposit_event(Event::OneshotAccountCreated {
+                    account: dest.clone(),
+                    balance: value,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest, value)?;
+            }
+            OneshotAccounts::<T>::remove(&transactor);
+            Self::deposit_event(Event::OneshotAccountConsumed {
+                account: transactor,
+                dest1: (dest, value),
+                dest2: None,
+            });
+
+            Ok(())
+        }
+        /// Consume a oneshot account then transfer some amount to an account,
+        /// and the remaining amount to another account.
+        ///
+        /// - `block_height`: Must be a recent block number.
+        ///   The limit is `BlockHashCount` in the past. (this is to prevent replay attacks)
+        /// - `dest`: The destination account.
+        /// - `dest_is_oneshot`: If set to `true`, then a oneshot account is created at `dest`. Else, `dest` has to be an existing account.
+        /// - `dest2`: The second destination account.
+        /// - `dest2_is_oneshot`: If set to `true`, then a oneshot account is created at `dest2`. Else, `dest2` has to be an existing account.
+        /// - `balance1`: The amount transfered to `dest`, the leftover being transfered to `dest2`.
+        #[pallet::weight(500_000_000)]
+        pub fn consume_oneshot_account_with_remaining(
+            origin: OriginFor<T>,
+            block_height: T::BlockNumber,
+            dest: Account<<T::Lookup as StaticLookup>::Source>,
+            remaining_to: Account<<T::Lookup as StaticLookup>::Source>,
+            #[pallet::compact] balance: <T::Currency as Currency<T::AccountId>>::Balance,
+        ) -> DispatchResult {
+            let transactor = ensure_signed(origin)?;
+
+            let (dest1, dest1_is_oneshot) = match dest {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            let dest1 = T::Lookup::lookup(dest1)?;
+            let (dest2, dest2_is_oneshot) = match remaining_to {
+                Account::Normal(account) => (account, false),
+                Account::Oneshot(account) => (account, true),
+            };
+            let dest2 = T::Lookup::lookup(dest2)?;
+
+            let value = OneshotAccounts::<T>::take(&transactor)
+                .ok_or(Error::<T>::OneshotAccountNotExist)?;
+
+            let balance1 = balance;
+            ensure!(value > balance1, Error::<T>::InsufficientBalance);
+            let balance2 = value.saturating_sub(balance1);
+            ensure!(
+                block_height <= frame_system::Pallet::<T>::block_number(),
+                Error::<T>::BlockHeightInFuture
+            );
+            ensure!(
+                frame_system::pallet::BlockHash::<T>::contains_key(block_height),
+                Error::<T>::BlockHeightTooOld
+            );
+            if dest1_is_oneshot {
+                ensure!(
+                    OneshotAccounts::<T>::get(&dest1).is_none(),
+                    Error::<T>::OneshotAccountAlreadyCreated
+                );
+                ensure!(
+                    balance1 >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                    Error::<T>::ExistentialDeposit
+                );
+            } else {
+                ensure!(
+                    !<T::Currency as Currency<T::AccountId>>::free_balance(&dest1).is_zero(),
+                    Error::<T>::DestAccountNotExist
+                );
+            }
+            if dest2_is_oneshot {
+                ensure!(
+                    OneshotAccounts::<T>::get(&dest2).is_none(),
+                    Error::<T>::OneshotAccountAlreadyCreated
+                );
+                ensure!(
+                    balance2 >= <T::Currency as Currency<T::AccountId>>::minimum_balance(),
+                    Error::<T>::ExistentialDeposit
+                );
+                OneshotAccounts::<T>::insert(&dest2, balance2);
+                Self::deposit_event(Event::OneshotAccountCreated {
+                    account: dest2.clone(),
+                    balance: balance2,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest2, balance2)?;
+            }
+            if dest1_is_oneshot {
+                OneshotAccounts::<T>::insert(&dest1, balance1);
+                Self::deposit_event(Event::OneshotAccountCreated {
+                    account: dest1.clone(),
+                    balance: balance1,
+                    creator: transactor.clone(),
+                });
+            } else {
+                <T::Currency as Currency<T::AccountId>>::deposit_into_existing(&dest1, balance1)?;
+            }
+            OneshotAccounts::<T>::remove(&transactor);
+            Self::deposit_event(Event::OneshotAccountConsumed {
+                account: transactor,
+                dest1: (dest1, balance1),
+                dest2: Some((dest2, balance2)),
+            });
+
+            Ok(())
+        }
+    }
+}
+
+impl<T: Config> OnChargeTransaction<T> for Pallet<T>
+where
+    T::Call: IsSubType<Call<T>>,
+    T::InnerOnChargeTransaction: OnChargeTransaction<
+        T,
+        Balance = <T::Currency as Currency<T::AccountId>>::Balance,
+        LiquidityInfo = Option<<T::Currency as Currency<T::AccountId>>::NegativeImbalance>,
+    >,
+{
+    type Balance = <T::Currency as Currency<T::AccountId>>::Balance;
+    type LiquidityInfo = Option<<T::Currency as Currency<T::AccountId>>::NegativeImbalance>;
+    fn withdraw_fee(
+        who: &T::AccountId,
+        call: &T::Call,
+        dispatch_info: &DispatchInfoOf<T::Call>,
+        fee: Self::Balance,
+        tip: Self::Balance,
+    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
+        if let Some(
+            Call::consume_oneshot_account { .. }
+            | Call::consume_oneshot_account_with_remaining { .. },
+        ) = call.is_sub_type()
+        {
+            if fee.is_zero() {
+                return Ok(None);
+            }
+
+            if let Some(balance) = OneshotAccounts::<T>::get(&who) {
+                if balance >= fee {
+                    OneshotAccounts::<T>::insert(who, balance.saturating_sub(fee));
+                    Self::deposit_event(Event::Withdraw {
+                        account: who.clone(),
+                        balance: fee,
+                    });
+                    // TODO
+                    return Ok(Some(
+                        <T::Currency as Currency<T::AccountId>>::NegativeImbalance::zero(),
+                    ));
+                }
+            }
+            Err(TransactionValidityError::Invalid(
+                InvalidTransaction::Payment,
+            ))
+        } else {
+            T::InnerOnChargeTransaction::withdraw_fee(who, call, dispatch_info, fee, tip)
+        }
+    }
+    fn correct_and_deposit_fee(
+        who: &T::AccountId,
+        dispatch_info: &DispatchInfoOf<T::Call>,
+        post_info: &PostDispatchInfoOf<T::Call>,
+        corrected_fee: Self::Balance,
+        tip: Self::Balance,
+        already_withdrawn: Self::LiquidityInfo,
+    ) -> Result<(), TransactionValidityError> {
+        T::InnerOnChargeTransaction::correct_and_deposit_fee(
+            who,
+            dispatch_info,
+            post_info,
+            corrected_fee,
+            tip,
+            already_withdrawn,
+        )
+    }
+}
diff --git a/pallets/oneshot-account/src/mock.rs b/pallets/oneshot-account/src/mock.rs
new file mode 100644
index 000000000..07ea86c70
--- /dev/null
+++ b/pallets/oneshot-account/src/mock.rs
@@ -0,0 +1,123 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use crate::{self as pallet_oneshot_account};
+use frame_support::{parameter_types, traits::Everything, weights::IdentityFee};
+use frame_system as system;
+use pallet_transaction_payment::CurrencyAdapter;
+use sp_core::H256;
+use sp_runtime::{
+    testing::Header,
+    traits::{BlakeTwo256, IdentityLookup},
+    BuildStorage,
+};
+
+type Balance = u64;
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+type Block = frame_system::mocking::MockBlock<Test>;
+
+// Configure a mock runtime to test the pallet.
+frame_support::construct_runtime!(
+    pub enum Test where
+        Block = Block,
+        NodeBlock = Block,
+        UncheckedExtrinsic = UncheckedExtrinsic,
+    {
+        System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+        Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+        TransactionPayment: pallet_transaction_payment::{Pallet, Storage},
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>},
+    }
+);
+
+parameter_types! {
+    pub const BlockHashCount: u64 = 250;
+    pub const SS58Prefix: u8 = 42;
+}
+
+impl system::Config for Test {
+    type BaseCallFilter = Everything;
+    type BlockWeights = ();
+    type BlockLength = ();
+    type DbWeight = ();
+    type Origin = Origin;
+    type Call = Call;
+    type Index = u64;
+    type BlockNumber = u64;
+    type Hash = H256;
+    type Hashing = BlakeTwo256;
+    type AccountId = u64;
+    type Lookup = IdentityLookup<Self::AccountId>;
+    type Header = Header;
+    type Event = Event;
+    type BlockHashCount = BlockHashCount;
+    type Version = ();
+    type PalletInfo = PalletInfo;
+    type AccountData = pallet_balances::AccountData<Balance>;
+    type OnNewAccount = ();
+    type OnKilledAccount = ();
+    type SystemWeightInfo = ();
+    type SS58Prefix = SS58Prefix;
+    type OnSetCode = ();
+    type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+parameter_types! {
+    pub const ExistentialDeposit: Balance = 10;
+    pub const MaxLocks: u32 = 50;
+}
+
+impl pallet_balances::Config for Test {
+    type Balance = Balance;
+    type DustRemoval = ();
+    type Event = Event;
+    type ExistentialDeposit = ExistentialDeposit;
+    type AccountStore = System;
+    type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
+    type MaxLocks = MaxLocks;
+    type MaxReserves = ();
+    type ReserveIdentifier = [u8; 8];
+}
+impl pallet_transaction_payment::Config for Test {
+    type OnChargeTransaction = OneshotAccount;
+    type TransactionByteFee = ();
+    type OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
+    type WeightToFee = IdentityFee<u64>;
+    type FeeMultiplierUpdate = ();
+}
+impl pallet_oneshot_account::Config for Test {
+    type Currency = Balances;
+    type Event = Event;
+    type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+}
+
+pub struct HandleFees;
+type NegativeImbalance = <Balances as frame_support::traits::Currency<u64>>::NegativeImbalance;
+impl frame_support::traits::OnUnbalanced<NegativeImbalance> for HandleFees {
+    fn on_nonzero_unbalanced(_amount: NegativeImbalance) {}
+}
+
+// Build genesis storage according to the mock runtime.
+#[allow(dead_code)]
+pub fn new_test_ext() -> sp_io::TestExternalities {
+    GenesisConfig {
+        system: SystemConfig::default(),
+        balances: BalancesConfig::default(),
+    }
+    .build_storage()
+    .unwrap()
+    .into()
+}
diff --git a/pallets/oneshot-account/src/types.rs b/pallets/oneshot-account/src/types.rs
new file mode 100644
index 000000000..753448465
--- /dev/null
+++ b/pallets/oneshot-account/src/types.rs
@@ -0,0 +1,24 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Substrate-Libre-Currency.
+//
+// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Substrate-Libre-Currency is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
+
+use codec::{Decode, Encode};
+use frame_support::pallet_prelude::*;
+
+#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
+pub enum Account<AccountId> {
+    Normal(AccountId),
+    Oneshot(AccountId),
+}
diff --git a/pallets/oneshot-account/src/weights.rs b/pallets/oneshot-account/src/weights.rs
new file mode 100644
index 000000000..ff5e2930e
--- /dev/null
+++ b/pallets/oneshot-account/src/weights.rs
@@ -0,0 +1,52 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Duniter-v2S is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::weights::{constants::RocksDbWeight, Weight};
+
+/// Weight functions needed for pallet_universal_dividend.
+pub trait WeightInfo {
+    fn create_oneshot_account() -> Weight;
+    fn consume_oneshot_account() -> Weight;
+    fn consume_oneshot_account_two_dests() -> Weight;
+}
+
+// Insecure weights implementation, use it for tests only!
+impl WeightInfo for () {
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    fn create_oneshot_account() -> Weight {
+        (45_690_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+    }
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    // Storage: System BlockHash (r:1 w:0)
+    // Storage: System Account (r:1 w:1)
+    fn consume_oneshot_account() -> Weight {
+        (50_060_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(3 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+    }
+    // Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+    // Storage: System BlockHash (r:1 w:0)
+    // Storage: System Account (r:2 w:2)
+    fn consume_oneshot_account_with_remaining() -> Weight {
+        (69_346_000 as Weight)
+            .saturating_add(RocksDbWeight::get().reads(4 as Weight))
+            .saturating_add(RocksDbWeight::get().writes(3 as Weight))
+    }
+}
diff --git a/resources/metadata.scale b/resources/metadata.scale
index 53eb7112a2ef328bbe23045d60c5ef402470ae39..6550da100926ebbbe9df51ce1996f7a69c682a6b 100644
GIT binary patch
delta 10278
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
B{Fnd$

delta 7300
zcmaJ`4O~@Kw%`AK4sbEZtAc`jcu_zwR6axnK}Ds+L_QP^Oz{RsxCvam_hOJ~TKQg7
zrnb?oDJdltCMMmSn}v={AES*{l=v}eYA;J?^jE%@Z*(T7x6Zxb$Jo5z?{d!Cd#|(h
z-fOMB)_*T99rbzPeIM^i&0)T`wat5zf@HBx2}6q5uM9@1IH!Cl&Z+&Bwb&+#8-he{
zuW)P>BfO%hJ<BT++eIDG?}@Emari<U_KL>|af9enVwiUd&Wcj+hj2mcBl;)tH}3>o
z6T^HSRDVGr#8#jFVy#amAYy$civEVaUS7D>KU`EB`odQ{ZAe0(a2Zk%CaxGpAWE2g
z<1k!g`$i+d>G0jvTg-1Ti4Q{tqd|Nh@+=yh*3dcJr-3nUWE;hi@OW$y{|vu$&lbiM
zBV)|KwmGj38Ub4Sy^RCJxQH0M=`4*<(ECmLW+&Upm}=M^#CD4%gChgqMVeNcn4FxD
zlA4-Z^GJoIgt2$U+k<0i(<cYtk9J`i@-jXW9}MXtdR=6K*8!y2ZDQ--f#O``pxz&m
z%!BNpE|VQ*hehwG6jD@H)YCZXJQ6h+I4XXMj#N(g%@V(Q1&Z-)J|b!837nFz<YS;S
zIwlM_>zooh75L7%de|$9&v%S@F?K;5I6PR)8j*ra&KE~S1D8Zb{Alslkp*t*1D{L8
zrm~+z<7Yu4B;go-cK%Po0r>n(-JD%>wkH*G{37=~ib3K;lAm+IxB`8KjbkPPaDI?f
z#JLxKbC~-=o~$5i1dg8ye=&c28NL&rjQ^16=O#RWKH|#>Bk9Q}mXqBUO+1BQ(K%^`
z*pN}#P32Eo?xwz_=ev_<lBD9yP;vQ&kGOBjHbjWSQ(mHHapoGb^^ci<Lag}U{%G3f
zxA&8;#L6Oih`gy<4|#U#3hFRtEyQr=!K{xsdBy7K;}o$VOBIK6@_VO%EMQ{k`pdyG
z#PHlP$P`-cVEioVbHfaoG~jJySz=f2C}cbTD>p_(p71M}ghFRd!C4?%#LpQSoTq<Z
z07gGDWlRh!)H`C>JYjyQzj$%Z{V<EJInmq<(TTXR8kiq~s;st3bA<+|vl!y6)tb%h
zuvn{Zc12d2t4hrdtF6w%!!(kXpKAfe=2YvPX>&A7S-E381F_Z?JFE_K#cz$8Y?@uO
zEzwGUV~|;dQW%9w_d+KW281mJX60(ODHeOR*-=tHxx#F>r`04Sj%RGKd-Y;VVG?S@
z7lqlV6X8X}NK?~_Lg-mmRD@;X4@K!o%b@yAwmzn+v#=6OW%OX~2VVt7KhjY>jI5q0
z#o#`4t4YEAom1zw^MrM}>ehp;hZ^MBr!Hp`U@*?GR%yg;oN2ApRJMs2>9<KF&yPl<
zczAv|I>plY`9yy<KS*r>o%ueCg9I<`AK1w;D%V_5p*ie1HcOeMim^tB7jN_zlZ%4`
znqZu2wwJ>ML%?ivMGXmW64k}QQB5$!mz8QuifuJj4ojtGoJ=wp%7bRs*&SM?%Cdk~
z(V;yodMy~FHUoRzjrs*)YAev}Zg`u+)UKY9r_B-SX<(0gM13Ss51;B~pv&D6R5DDx
z3hZ+?UM-1GZvbuX#*or*)t7fSno0*DU38Q#Rp;^UNw#Pa<YN1^XQ0tQv3PaiFxA3w
z+Wm1{SrQhD1!d2Y**Z!Wh^+Dmbsg`X=F{?UwXsLcR7<4V#Jg8>*fLCQ>0z7wXr$WO
z)A;kFgY~7*JP$95B{>VqipBXw(P{@ri+jdJ6`|@O&vNI93l-t|LWp}|Wq(@lvdTHc
zcJE@{P-e2v#H#i?+U3@@ceG2ZTW)K25k1BhMQWdC%R<{rj?w&M2yE@={BN5-sq1D(
zfOERzE8vjRwB%`BjoTlKPuT-QRw?#@8DX+6T&Pu*YPNmkSy`nFMtg<T(E%7|(wiZE
zp~Yr*6qjpe;@KnoYlHpw06|mTZN~P9q}n7hcUf%=1&p<|MMVF#Hj<QKtQ!*Oj`IJK
z7dph$x@deXtaX_v6npAM@k5XaNeB_%j}O8T89NGL;hjKyMn#47F|AZzhFy2?BlM>l
zf`VA5Fh5>H#yS4@NSt)~EN$Y3lQiZX#7>Ld%f{fG*!EP6FfR9|(E-b!Roxv4;U|HZ
zS*fX$%oXOU5{-6Nr9D>M6B153e|+j?cs}(umTGp#?NWYtW<d9tN30m}AAj}m`o%2L
zwCZ9CI7_teoHPFOL#2EA)aP4?y70Ui=f$)a9*n#QnC8}u9cN06pGTp9o!3{qf?cGS
z65u}jmm%gg6!R+(`x~C~zoHMUa1UICC|IA)uR}DfukCf62KXMZ8xZ|BBvKyA+K}l*
z_+ZWE5!fyEZ%!xO|FU@^<=ONt+3*t2Z<#{RFSf+|rZs$pf8%t@N*}y31t~9W4RAUe
zebCc>XKSiT^tjjZDObJt+5n$E+%Vl<T`YcV4ikr73#8n2<~1|njEwCM5q+sSs@HCH
zHiEgTC#;ay2M^!v@u>i}6UA}_#mtT~&5UITvTG&Qs?yuKSg;6c&Zh4cHpftEThly%
zo^LdpqM~@W=0=a2$5<36y-alLC6-9l5X{3k8-8>0gnu)6^4*i)*2NOUqp#<flHC#$
zbcre45K3DuvD!4_Y^#M124m^s<m=;*Ap+lsLBVU|-{3?~XelDcf4OB5J-v4%B1@#~
zh@^Ble@8IUwL6CTW|L5!$620ub4L;{;Dmpohepn{lvSA>H8u+5DkGRN)o$xPD7TFi
zieYb=df%yld7|*GI5deq?}R#EeTzViPL6b5+iCW==uok8*X15ZE}=N>Vcq+71ynP?
ziJ*pgM2J;w&!frN=bf408rShukaM}SOo2sw|GUYQ4~Mxz34I-j3UU^^MtjjmBljmD
z(|NFcIzlpmxNNQR5zRK)N{NBNRBf|XTkU3=)5B)oU!aB(+o;^`fh-C&wG5%=YGT!o
z%$I-Y$4PbHe-wk0qW8x^Bs=EgH9m$)U2BwU#qST7cONPDi57E?*zain{YVpPoLfE%
z2EjMi=T8E4&M}=Oz%u9NFWZ26k$p53tHrZNi+LR<Xc;VcS1^U1&@L0@>GZBxkGF~C
zUH4G$b6ut<S8`0$N=ohK)YMzXpvA1@emy=B;2yuzimP~!qb8H1uA&c=47a1!^V?JS
z&v#{T!kgq>tLX#&Gh4?++_6WnUaUDblAgammWWMa+t-1@=lGCrYU1%oH&sI8hmKDt
zy8nqN$|7keo*;O-_ry-!DegN~LP<s!()|Am;msbcgr6Fr2cH?IV%(rFbSUEOn%zzS
zt9X%CXTJhWo#|;mNX<DmGNEQ;tmU?8c`MgpZ-~H|0`zgtITNPPtLEF+IB(;kVq%a;
zJU=R|l4Htjtt5?taYjjr?m1bd1m29doOSE@5u70UXFmj!2gY5ACZN#!!hMul#$5=b
z9Tr}A&v4gpw6pHYLGPR04_`C60d%-?^R+{2?4CPJK)&*?C@PpNK)=0W^^L^7O`r=<
zCN)`;8<w(tqVvX7@{@u8e42M~@l$4?c=w;F_*kCpk8saPat`}-BuA`^p!aZkyCJx%
zO~Ez2vBMkjxa9iU8(u0k&-KCp<0bgjB#fp|Snh_enbL3%!jS2Tz6X2nxkI!oWB}|u
z_TxMD5PSC?Vm*5}BwfKspj5hZ2tr+61CgWL+cVisC5O4548tPc!{uW_9!IAv9E?Gp
z{kYZ-Mt|Uli=-^(zDIQLB)xRX=xD^COXfslEKbU2qcKBG;gldcv<zQ&q$JKR895Yl
zX?Vp@tiWkWU!+rOLJVQcbFKw3s0Z!zFR`>tdWYTzy1He$v^e~Z^K)D*uMBpL9*GL@
z^IS&cVqDBc3hR2Lyi8%-?K_0SFVaM+VIP91mlj9OD{k%q^1wp~nWB2WxO&?cS9Ly>
zUB5$-H~3x7aD(_rmp8Z@n8=L@p!_d)CtxVN<cS2th5IU)HPfBK%(lAO`S<7Av?-P{
zf(=ZePi1@(Lgn~GOhJd-kc2pQ40p3xtqbXP>0$VDA|AZwCe&iSdaw4|U--+;Bt)T)
z{9BUSv^q-acNa=xfo~L!0!Of`Xf(c25GGSnu@Vt7VFuCyqZE%uhAX#p7$yD2VI=Bh
z+Bl53H&#JtzGk*ttL%)$%Gz-l<vW}<#G@3JpbH6dt2QvXTT;qxl9J_(aY*b}kI-&}
zMRqa3U@5JalhP3Do1)7}R#>`pq#=oCD57myko;sR?sa{h1`8ztnKA*x`erFG)+8j3
zWh{%n@bY2VvStF(d7eV>XrLT239ER4PDi-@GzmY#w@?>6Phs=qhf`47|LDzYVMdLk
z+-jr9!kF2Wmx)V&CdWU3)OiPKWjBHEL6~y7F)%}<O{=!r9GcBeQEHU#Y5Cn9<Zu?6
zNi}SVVU_xnixoyc>R{lk#!b~JF|1B@J%IbL%tby;S!kuyvakWG<S$t$B|V#`VW@Ev
zM`ZWibq2|;ciSgmr^h^R-m=bWMX$cNew~H{1?${gq3s^7^|z^hy}q1wwn?FzT!`F0
z1AY58Dm@XbQPD5ftWh4EfhA~?6LK&C&GN|{<WZ|L2Q#Rp`^V6xZmYK3Cb~rz-K4Nq
z_x`9d)TU5HCq%xIi^JF>7v*6Q_R8aVc!2Ly<e+?vN!+L0p6xo6yG~aJ$s(KRFH&9*
z`}me;N&j5*#UWQ?J|^n1>6XixJljs8yGPIY?^NW<q39=53NUvdnQN>*a7RX|GS}HF
z<*oucw_UDd1)%Hi$y+j`Pj_$l+-(~^?OxTTAa;%jPObA2h4`LdROE|uF<?UxM&5H-
zVL>obO~QOxrWWCT^@@TOG9n)Va$^yu^}VhjCacs@H`74~k51BMMTZ^Ie=fz=OLF*J
zj7Yk2hmx+|t)#0SCFw6xl963^&z~P~LthqDMn4)bURS7srPr@qeh=dbFZjxOGalsr
zs%TFNl?Ru?&vl>-|DgCR|4#0QKv(;t=*xNVT?}EKkzNsQ!4ztvTqi2<t6ohyQANHS
zEF-MQi}(#av*G$DetS9DGC`#@KhV``#TvC&GWGKmm8Ga+L1u(3bI`d@*Xa<wk|p;$
z(CU}ro-R`jVwtKQ>OH|bFsnOyXREjDohA3zU<|G2e`_#`UiU3Qq#;|MBTHp+Q4JoE
zdzZi}bC#f&EAcTja60dQti$`T$fq7hJ+HiN2039VeEb`3NnfmrggN19l@_XO(C&?t
zos$w7t5FT37TU~}TCu&RS`Q5-PyK|h%EU!!EYy9g8%v0<&egh<5*?PwV^84z3FqK0
zlb{K=U?P_BfXrHl&5V^QeI7#yg9xUYR;l&sYIU8uUTsvH)Mk~fl5xv1P}-LfaH#jt
z`<GE%S?!_K<>b@rbUF>|JzY;PM|7`ED*Q5SW-14{oXgQ>M58>l24npQgPRC%x#72r
zT~3)HeJxCR&8nfi+FcJSrbSiR%_Vw4t+?7;SE*Gw7ECTT+sd?Aw?=Y315}C9cTYF!
z`0ikzvoLKIMY0xoU=2d>tZV*S98i(&8nhmB>6#>$O4#w8{L%9&*+{4$S$?+>G5xX#
zR|dsTs<4(U^3?I{^W>mScpa7UlTFx-8u{4EC`O$;{xVL|@oC#kfqb=mWec7l1%xzW
zCz@Rc8wrfyoWxd4!+AM<D@ITrUAh&^(BZni6&W0d<hWPSj`OZ-uVOPeL520(@esex
z<&WF39saI{X4DW|mAzW<xOX2#WrpFhcMIk5*INjQw7UB3z~dZ&^2MDfqODxqiA0Jj
zp{;lvYOt~xvS}BJ<l43Hk^k65u|CYT;BCxODEsbt2QMH&PI6M@O_xtOk%QB&FP-Ge
zq?ulOFrIQ>)*jHc(N(nv?<vx<7mI=$$T6nmR8?52G*2e8I4qhStU+Ghi{N~HIMj3d
zvQsqhGUm~eSz32bW^J{_rsY%-eDyNwthw1Wc1J#yMje_C-zRAc>1KO#6jXa#D<66v
z<tUIH?_&rG<@xt9iS`-u0Y>qwkn=yl+c3*}+i6gfiS2MG79~Ztw4)4LTz&SD#<5XO
z+>a!(oMS&M^zAqM@f@`*zsGB6kp2fKT|?eDfE2Rom<~+yp~BuaMhE8E4un#wZ0?|;
zRjxA~@a1})Z@awoA^HDmSJ+2b!pYnXpCBKb<PV=vN^O<P|A?p6bdL9=*FkimO&&jp
zSz+@yzF^p+1dJ-EF0)bDs=Lw&Gsg)z^$?;dV_6SDhwnQM;XWUY;}j$RICTi4Dd+V2
zlort>vp*&D)+t~56boq!*FQzO4}p}kjG%A(pD43mlBUBrgOeMMAXHt<aY34nkovM+
z^+(VQa>9|H)6y@>1)pOnewL>{$7p^-k%3<zLapKWNv3{*Z3HL3{sO}&Y)|MSJvPXy
zP7I_2UEfLG&>(kpV!W?zJy4zUnrq}S&}s3P6ThOv-$%9{M<)5xuCEANr^|#B7_3(?
ze~}NJpn&(pQPh&NUOh^m+$p{JZs|=NaE&-dXNSUk-ElJEZ%S|BwB9)<@GXUp)RTzw
zi%|WcudbED*JV42u^dtIt*;TKM5wXytFMvVYq+X2w9&zF^*se6UF+nKZ;(O9LHh=e
zQ4l!&4c(xU<uBhrQ}=O_tewGP+RyPbbQ{W$SI^L)&6Dq(MT%ZmjdJ~X7SE`C%<6>D
z0*9r-;;19jaSWHwoTr^?^0V_~FN;k6o|IoH7k-Z^0jKHT9SDu@DM3u4y3=CU{_iQ6
z660TfpsML&x%>iVqsG;B0bcN_Q!|#Ss!5)^h<;D5QddlzT&|TYnpv%t%qCz+0kU2-
zj&=9Vs_pJsts3LqJr8JAn%!bwr<yz**_1M<I_RidubN`q98|q8(HhkiaqgBU<~6A+
Q{!Os;sd6-{D_Ye51NbQKXaE2J

diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml
index 523f68404..660fa3223 100644
--- a/runtime/common/Cargo.toml
+++ b/runtime/common/Cargo.toml
@@ -39,6 +39,7 @@ std = [
     'pallet-identity/std',
     'pallet-membership/std',
     'pallet-multisig/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-proxy/std',
     'pallet-scheduler/std',
@@ -68,6 +69,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs
index f9e7531c7..10f5feb5c 100644
--- a/runtime/common/src/pallets_config.rs
+++ b/runtime/common/src/pallets_config.rs
@@ -173,13 +173,19 @@ macro_rules! pallets_config {
                 }
             }
         }
+        pub struct OnChargeTransaction;
         impl pallet_transaction_payment::Config for Runtime {
-            type OnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+            type OnChargeTransaction = OneshotAccount;
             type OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
             type WeightToFee = common_runtime::fees::WeightToFeeImpl<Balance>;
             type LengthToFee = common_runtime::fees::LengthToFeeImpl<Balance>;
             type FeeMultiplierUpdate = ();
         }
+        impl pallet_oneshot_account::Config for Runtime {
+            type Currency = Balances;
+            type Event = Event;
+            type InnerOnChargeTransaction = CurrencyAdapter<Balances, HandleFees>;
+        }
 
         // CONSENSUS  //
 
diff --git a/runtime/common/src/weights/pallet_oneshot_account.rs b/runtime/common/src/weights/pallet_oneshot_account.rs
new file mode 100644
index 000000000..c10f189ad
--- /dev/null
+++ b/runtime/common/src/weights/pallet_oneshot_account.rs
@@ -0,0 +1,70 @@
+// Copyright 2021-2022 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Duniter-v2S is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_oneshot_account`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-06-28, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/release/duniter
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_oneshot-account
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --header=./file_header.txt
+// --output=.
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::Weight};
+use sp_std::marker::PhantomData;
+
+/// Weight functions for `pallet_oneshot_account`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_oneshot_account::WeightInfo for WeightInfo<T> {
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	fn create_oneshot_account() -> Weight {
+		(45_690_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	// Storage: System BlockHash (r:1 w:0)
+	// Storage: System Account (r:1 w:1)
+	fn consume_oneshot_account() -> Weight {
+		(50_060_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(3 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+	}
+	// Storage: OneshotAccount OneshotAccounts (r:1 w:1)
+	// Storage: System BlockHash (r:1 w:0)
+	// Storage: System Account (r:2 w:2)
+	fn consume_oneshot_account_with_remaining() -> Weight {
+		(69_346_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(4 as Weight))
+			.saturating_add(T::DbWeight::get().writes(3 as Weight))
+	}
+}
diff --git a/runtime/g1/Cargo.toml b/runtime/g1/Cargo.toml
index ee9d7049b..2ce176950 100644
--- a/runtime/g1/Cargo.toml
+++ b/runtime/g1/Cargo.toml
@@ -52,6 +52,7 @@ std = [
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
+    'pallet-oneshot-account/std',
     'pallet-preimage/std',
     'pallet-proxy/std',
     'pallet-session/std',
@@ -117,6 +118,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/g1/src/lib.rs b/runtime/g1/src/lib.rs
index ec455d7fa..a944041c7 100644
--- a/runtime/g1/src/lib.rs
+++ b/runtime/g1/src/lib.rs
@@ -111,7 +111,7 @@ pub type SignedExtra = (
     frame_system::CheckTxVersion<Runtime>,
     frame_system::CheckGenesis<Runtime>,
     frame_system::CheckEra<Runtime>,
-    frame_system::CheckNonce<Runtime>,
+    pallet_oneshot_account::CheckNonce<Runtime>,
     frame_system::CheckWeight<Runtime>,
     pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
 );
@@ -215,6 +215,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support.
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml
index bfdc5a59a..c28e43875 100644
--- a/runtime/gdev/Cargo.toml
+++ b/runtime/gdev/Cargo.toml
@@ -68,6 +68,7 @@ std = [
     'pallet-grandpa/std',
     'pallet-identity/std',
     'pallet-membership/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
@@ -139,6 +140,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs
index 666469fa6..9f6f5ddcd 100644
--- a/runtime/gdev/src/lib.rs
+++ b/runtime/gdev/src/lib.rs
@@ -115,9 +115,9 @@ pub type SignedExtra = (
     frame_system::CheckTxVersion<Runtime>,
     frame_system::CheckGenesis<Runtime>,
     frame_system::CheckEra<Runtime>,
-    frame_system::CheckNonce<Runtime>,
+    pallet_oneshot_account::CheckNonce<Runtime>,
     frame_system::CheckWeight<Runtime>,
-    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
+    //pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
 );
 
 /// Executive: handles dispatch to the various modules.
@@ -284,6 +284,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
diff --git a/runtime/gdev/tests/integration_tests.rs b/runtime/gdev/tests/integration_tests.rs
index 9a1ca4833..c3c43dc8b 100644
--- a/runtime/gdev/tests/integration_tests.rs
+++ b/runtime/gdev/tests/integration_tests.rs
@@ -430,3 +430,81 @@ fn test_create_new_idty_without_founds() {
             );
         });
 }
+
+#[test]
+fn test_oneshot_accounts() {
+    ExtBuilder::new(1, 3, 4)
+        .with_initial_balances(vec![
+            (AccountKeyring::Alice.to_account_id(), 1_000),
+            (AccountKeyring::Eve.to_account_id(), 1_000),
+        ])
+        .build()
+        .execute_with(|| {
+            run_to_block(6);
+
+            assert_ok!(OneshotAccount::create_oneshot_account(
+                frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
+                MultiAddress::Id(AccountKeyring::Eve.to_account_id()),
+                400
+            ));
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
+                600
+            );
+            run_to_block(7);
+
+            assert_ok!(OneshotAccount::consume_oneshot_account_with_remaining(
+                frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                0,
+                pallet_oneshot_account::Account::Oneshot(MultiAddress::Id(
+                    AccountKeyring::Ferdie.to_account_id()
+                )),
+                pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                    AccountKeyring::Alice.to_account_id()
+                )),
+                300
+            ));
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
+                700
+            );
+            assert_noop!(
+                OneshotAccount::consume_oneshot_account(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                    0,
+                    pallet_oneshot_account::Account::Oneshot(MultiAddress::Id(
+                        AccountKeyring::Ferdie.to_account_id()
+                    )),
+                ),
+                pallet_oneshot_account::Error::<Runtime>::OneshotAccountNotExist
+            );
+            run_to_block(8);
+            // Oneshot account consumption should not increment the nonce
+            assert_eq!(
+                System::account(AccountKeyring::Eve.to_account_id()).nonce,
+                0
+            );
+
+            assert_ok!(OneshotAccount::consume_oneshot_account(
+                frame_system::RawOrigin::Signed(AccountKeyring::Ferdie.to_account_id()).into(),
+                0,
+                pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                    AccountKeyring::Alice.to_account_id()
+                )),
+            ));
+            assert_eq!(
+                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
+                1000
+            );
+            assert_noop!(
+                OneshotAccount::consume_oneshot_account(
+                    frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
+                    0,
+                    pallet_oneshot_account::Account::Normal(MultiAddress::Id(
+                        AccountKeyring::Alice.to_account_id()
+                    )),
+                ),
+                pallet_oneshot_account::Error::<Runtime>::OneshotAccountNotExist
+            );
+        });
+}
diff --git a/runtime/gtest/Cargo.toml b/runtime/gtest/Cargo.toml
index 30be9e5eb..4d40f3b25 100644
--- a/runtime/gtest/Cargo.toml
+++ b/runtime/gtest/Cargo.toml
@@ -49,6 +49,7 @@ std = [
     'pallet-grandpa/std',
     'pallet-identity/std',
     'pallet-membership/std',
+    'pallet-oneshot-account/std',
     'pallet-provide-randomness/std',
     'pallet-im-online/std',
     'pallet-multisig/std',
@@ -117,6 +118,7 @@ pallet-duniter-account = { path = '../../pallets/duniter-account', default-featu
 pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false }
 pallet-identity = { path = '../../pallets/identity', default-features = false }
 pallet-membership = { path = '../../pallets/membership', default-features = false }
+pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-features = false }
 pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false }
 pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false }
 pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false }
diff --git a/runtime/gtest/src/lib.rs b/runtime/gtest/src/lib.rs
index fd21f986d..efb4fb2f4 100644
--- a/runtime/gtest/src/lib.rs
+++ b/runtime/gtest/src/lib.rs
@@ -216,6 +216,7 @@ construct_runtime!(
         // Money management
         Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 6,
         TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 32,
+        OneshotAccount: pallet_oneshot_account::{Pallet, Call, Storage, Event<T>} = 7,
 
         // Consensus support.
         AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>} = 10,
-- 
GitLab