From c89b598eb60ad6282fcafe82c1aa9fe5f30b6358 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pascal=20Eng=C3=A9libert?= <tuxmain@zettascript.org>
Date: Tue, 9 Aug 2022 02:11:19 +0200
Subject: [PATCH] feat(runtime): add pallet oneshot accounts (!51)

* run real benchmarks for pallet oneshot

* fix(oneshot-account): use benchmarking

* fix: metadata should comply subxt & polkadotjs expectations

* feat(oneshot-account): Pallet oneshot-account
---
 Cargo.lock                                    |  22 +
 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            |  94 +++++
 pallets/oneshot-account/src/benchmarking.rs   | 138 +++++++
 pallets/oneshot-account/src/check_nonce.rs    |  86 ++++
 pallets/oneshot-account/src/lib.rs            | 380 ++++++++++++++++++
 pallets/oneshot-account/src/mock.rs           | 124 ++++++
 pallets/oneshot-account/src/types.rs          |  24 ++
 pallets/oneshot-account/src/weights.rs        |  52 +++
 resources/metadata.scale                      | Bin 122124 -> 125644 bytes
 runtime/common/Cargo.toml                     |   2 +
 runtime/common/src/apis.rs                    |   2 +
 runtime/common/src/pallets_config.rs          |   8 +-
 .../src/weights/pallet_oneshot_account.rs     |  71 ++++
 runtime/g1/Cargo.toml                         |   2 +
 runtime/g1/src/lib.rs                         |   3 +-
 runtime/gdev/Cargo.toml                       |   3 +
 runtime/gdev/src/lib.rs                       |   3 +-
 runtime/gdev/tests/integration_tests.rs       |  78 ++++
 runtime/gtest/Cargo.toml                      |   2 +
 runtime/gtest/src/lib.rs                      |   1 +
 25 files changed, 1335 insertions(+), 4 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..6e5f707fe 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,24 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-oneshot-account"
+version = "3.0.0"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "pallet-balances",
+ "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..cde1128d8
--- /dev/null
+++ b/pallets/oneshot-account/Cargo.toml
@@ -0,0 +1,94 @@
+[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/runtime-benchmarks",
+	"pallet-balances",
+]
+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"] }
+
+# benchmarks
+pallet-balances = { git = 'https://github.com/duniter/substrate', branch = 'duniter-substrate-v0.9.23', optional = true, default-features = false }
+
+# 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'
+
+[dev-dependencies.pallet-balances]
+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..a72c65851
--- /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::Lookup as StaticLookup>::Source>::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::Lookup as StaticLookup>::Source>::Normal(recipient1_lookup),
+        Account::<<T::Lookup as StaticLookup>::Source>::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..d6325ab4d
--- /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 = "CheckNonce";
+
+    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..0a65c42ba
--- /dev/null
+++ b/pallets/oneshot-account/src/lib.rs
@@ -0,0 +1,380 @@
+// 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 benchmarking;
+mod check_nonce;
+#[cfg(test)]
+mod mock;
+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..35b69d7ae
--- /dev/null
+++ b/pallets/oneshot-account/src/mock.rs
@@ -0,0 +1,124 @@
+// 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,
+};
+use sp_std::convert::{TryFrom, TryInto};
+
+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 OperationalFeeMultiplier = frame_support::traits::ConstU8<5>;
+    type WeightToFee = IdentityFee<u64>;
+    type LengthToFee = 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..a4cbfbe78
--- /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_with_remaining() -> 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..10f4232185e31dcb87b88cd33a9731c8efca1265 100644
GIT binary patch
delta 10281
zcmb_i4^&lE)<3`dE&_`B4A2KW@S=cVh@fIXprT+>qN1q%LwLXiUOnD}_eZ5KLu#5*
zbJUJ@w6t*2M!%$Fp3aNX8Leha(~2oqSveD}oT*io&eUY9Y3AGa-Uos<<9ut)TC8`^
zpMB2RXP^Cl^T>;#Cr^hCYE-?pVMjX$Z8M>ex0~Wo%ztefg9hGXdL=(+zEkxyxV(im
zH7=jiE3=K*+GKM$RBxrrn5ncGQ+avC%T=d0e<6X{8`Wtnl$TXTVLN|8&cQBzQBKA_
zJ}M*$`+0syGG67?A?f&#ZwSf6C;SMtKjr5`a`6QpH|SP;%~ub~#ozghgR-O$@Gl14
z5)uwXgI9%)<Z{>qeqX3X8mdi9=kJDI7c!#X<a9*yqOi$G<SWB+<uqWmXst&Ke<SQV
zWbvM`bma13;VCHaXNNyGeE6*cufyAi#abfD2VQ%YTeW%B+6dk_IEMdW*pT=m`Q>?e
zvwUv1>a1x|Y#ybzt&Q`I1<{jXt<e^_+zgpC7NlQEEo1D~D>ipIJ-)`^?%9Z{RXyIU
z%>YvSuS>tiiBU7s)0nY%(pIElev{Yka^?^I|7|iNJ#Fwb2Bs{vdmC!qwiRLj`IRM!
z@vNEljLnS6Va&p|$`%6&SuV$Bgqi0N$I$i7i(HMW%m@sOZ5Ov?V=NDeQP9p)Vjf1j
z|M{4uQfND4l9}z|L#>(E!>3ul7_x^klbM!>v3;U54>A6E@zEe=s!f=V1O8nJCJaA7
zi(X(aXy+fYutU7*@fiN*=poTZkgwKG%FfQp&C6Tht9ICH7(2qh8l6U57^BR_YrIN%
z9B=XOmDAMjPRznd-s6j;-%(@IhQCF-zQf+pZ}lE~k6%0<!yCpZ#Jg+8Jb;h*f_)Z$
z^4P(kW@b_nF>~fK3EY$P9zGSV#fbISCC7u<`ogoZ{wGs1fD8Up<DN8yUtlbRv9BX)
zg?S!g#6NC9IDhQ=8*s`0#`P({CEhk+D!+chV!icJ=p_P|+28rBj4vg?KWX9-kVMd+
z#7IE0UXS%>PMt0x+~1Vbrk(N5lo`+@q;F=#fSG8B7b_9Z%Wj-QqVf2R^|-*L{8y;`
zyZoDId|1Im`khwLASHq)yl>%03SL1nZ#$pBmrc*&Z%=dd^`7f%W%4^_Y^L6Y8H=Pe
zh*kAS;`YKPF@cB8d{mmCkHqo7Ob^N7^|M|_Hve{33SBs%s2J2)_O~>?zE};mtg}~B
zYsc(5<oe~CUX`Q*hz(An=O-nKrl~?o7Y{Ec0!Rug(LP>Q%I&2$pp5S<9YZAI_jX&u
zSs6_YGPA|}v(hQJ&7UwYRYnEBY2i$${<RA~0&e4XmrsnT&~~ta(H}8SYARE;fmCMa
z{_;D~$jysWq(<;=q~|n)MZn^8xf^YcW<V}yNOv`<Zkr}=eM3o&HfOCS{w)R!Yv$V*
zFNBYOwK$%Bhc2;3`+%IFlrB~6^$p%>jQRMqCCO;vwM)ih72mKVR$8T>%HLfwS!&ao
zqxjgR*?5rOwsibx(h*aF>UNRWZFkl)B-tCA9F<O6qe>KLUwS*%@b8xvOkM*yVwP)#
zCW-68WJW(s|B*I<(H~uU3(RZ_wM=2UkZtqdee3U~tQ{I1c7p9he`(&0tHgOX0n*=Y
z?z|&K*^Bg<4p+_H^L&lfsvG7h3v`p=neVpO+nqSa58SaZb}wL_MdJ)PjkcC*wUUTv
zch%Z!4szwrtf+Gm1}<Z4FIVr(T-X8g9Gj;B3c{k6*c?96B^~-ZkZRE;WTqGp8a^Ee
z%dD?emsh$CvlKkhVOiAT@v4n7TMTsYo@ICPSrwz@PM}Ngyj&4at&b}1m3w-J4lPTR
zJ_Em4y^I%BMn_*|7{3EfX=`H#YWH+yph1)&;k16n{Hj6x^~xkET;dliTQP!vRaME~
zs!AZa`>N_b;^Pa|mHd>=Dm6>Ir(zi|sZNxA5{~POPgHAca-n*Syh_4ZedtWhWcfh}
zXY`JvHjSQM+u@tqG16}Bh-G|*njm!=n|-4u$p?EeX{j489X3Er^-0oEty#$*u1~~o
z^)131(&X;mogZyTkxxt5s~=;rTj_KS@y+(pxTr%%-ZfI{gh)&xWm0rk0S@w)8W$4$
zFsFr|zw6L|ezarbfc}Waz5V*9sr`vFnO<VBs|1I6N7Gbv``5V=(bxH;=~Lje|CIXy
zZLG|jS$Py;CAByXW`^Rbt5cn|s{1(cE16!HJr0-m6!G06`jTO;v%5Xs$_CX&Fh>E)
z9KFRb#iQ1^oV8cau%rAV?_?rNlrNQDsK9qSwYU0`q~j26DTp*j*qWLwy*5T>h8;OY
zh(+_k%SWJ_TbCC@<<-llNT>BBaooRrG|q?#X^1CMUR)kI_zdx_9A>ewvwYYJAA0yt
zR!qb>|A7@drLc1}H^{<1;}!SZfb%@-<qVp6^`3`iy^}gvajGjS>)fh(j<>d2X>NP#
z<1prUWm`z!lFe<SjA2^OKe+$%Ajnq_3?bX`{R6L<f&+hksDoNt9=6fre*5q(jo$#u
z9lo3!l}VWuq(|6A4WHHQ8~T`{bj9%<BCc?$dObvo;c4k2NScTdpzb57SGtm6Fl$bb
zem&(Ja4m0ZWUJ+f;i4%vrxNr>6>o#htJKhd%jsxQs#S%aRCly!phji;Cn#0gDOGt&
zd4o!0x}SS_wM$=OIDQ%7An0nF!$vEE3xYkxOMtDa;&t1co;ua7)@mc(2D|6VEi;(c
z(0_er5X{|blUK1h?90`RJA?RLO^wWIO_>rmYF5IG9P$hMsaIlaom@YVHxzU;^ZA0-
zv909DMFF#nTPwCJ@gZ6fu0gaFyVs-iyV7MkvLE!j_rrh)Q0nt|HTu{Tw^~D9zoKKP
zICXuWp%c{6YHzfA6}n@Uj;yAdXK67+m11`ifmEf*=J94IsXDC)jL6pHR<#&{Hf?g)
z$glQ#Z8dj$(jFf8RNAZAow~j4C1r+=q&87$_XG*psy%H<o#Iiw+TK-Ow@<A~*G?uL
zxr!KsfWeHlbZn3_GL&M6M@<j*lm_ziwb!O=I~8@MUBjZj4}+~&K3_?Y5i3`aFJ0+Y
z8*O$cEimJn&#B$x%Gb2gYi9OsQt5SF32;3DSc5P4AIMB}tCSl4Z<s?TQy+JD$Yob-
zjoR~RB)H5S>HBpqqSx%hZ2+8$_Hp;oh#k?y$Y7;botv-8ONCGjcKUB~lg=LHg!q5R
zPXBFgYW?@@v?s{(lY;8Tut-0OY`D}5dztyKjcu!o6ps<QYeIS@^G~W8!2Oe|uEGAc
zei||>h;uK)3{Z`R(oI*!5oah4wa)8WP8?FLYO>VJZ-x@pAt&+nXXmy4@Gp%@l((fx
z^;m<7PfiLp$OSl0M&B||_tRxYoy${Xr>sFQ?aCRlV~`@Mf;j=3&)eW~+r8qhY?%2&
zYo<vd5<j+P?NExkm}#|c@4_X49K`aKYsX4MCB9?r%E3cvRyqncLee7DQGDvUI7Isw
zt}70q)Wp_}892m`Z7c{UujJMInYL<`cW=z#H*OkC!RPEvrQ|kk-c&@tXEvn<1JQUs
z^3l0+qC^Y#H^uVRkB*Z{CH{yjfv0Xh!S89BG++*;{_xFtG9^Hs-#U`tzBNB4S;Cy+
zW}khzt43Fw`E$~hc@%CW6GV=jCQ(MDbXz8m+m=Cr)y>;(MH)|hJd%e!aoylU@)Ast
z<V6&+B|R}_!Xd+viqbu&0tuGXCF#l{$~;&M2JJV*CeS9Ow9fT-Dn)Xx6Qk&N|Kmz>
zt`zhkrcS9~ELS7t44sq()THE&lqIpk0SFZT3j|88MxfsiE7mvp=@a=%iN15OwsWZz
zX7w~x*0|iNd5OzTULj*;{NpEX#A2SXJr(w+Z`v+Vd(M-$Q*ik7lXuf^=Tnh<%u}N&
za-8{8Qk+e~f+cEA{`@9evrjGcIlOjTZLOQ=S;5<$8W(P(!z5K=Dy^F=*(J&-r0|IL
zVR9oq1J67WmDwmo^x|Okxb@6L(jt33*)*S<QY11Fr9X+9d3Ad_I{X{jXKK;PhIXs}
z%bi}se;dcwK7BsuthMh72~NMf%K-{ACq6S%ABg80o>^;5@h^8q`b&P11hn{_dsdq$
z%>3%vS!m;#e;OGY)#Rqs-ADcf`{rpoJ=K|oGJkjHT#&Du;E(AWy34;<rlBK&Sn?J>
z2t-QM`Kl{5mh6tjh-JxGIaFso8NO;V8ntEqtQR+g4r$XU!`OpTzpO_n|NLmNegVxD
zt$Nwh*EYSf6KnjtUx}fR3I5-_+6t`qPybyFu*v`QF^bi8@WsdTu#;~*UWxVm!tu!z
zsVALKu!$F+NYhzvOrA#bAc^zxF4Gt+s<@`sW0Tny2{Tl!*?*dl+`?Z!k#O_2zK6&r
zUfo9Hrb3-pcS!vMVv5AC%x^(cC5-K)C4@c&#+k3zP!#m$n-l5xpKnegVrHI<<`E~y
z>aE;hzxCuint$WuT<XW2N=7FyJasQo`PZlRfx;nelP-Oeu4`=4MFffEW4kkCQdSXS
zS~e`av3r=285tiFLUdWAdOVc0xJ7O8EOk@hqPiJ7-2c3z{Ij>45%0g_op=*{THbwH
zqIf6%{ZfNp2AOLpe%BZ>-oNYp2~yl8MEJ6%G8E}%ztW?#G$~Nkqw!Y?#jZ>Llwzzp
z#iyT*r+ZbLeIe}H>-<aq^yQ#`3s!YrL9)N?{Ga8t;{%>1`Rb=hHlF4b-}IMBksY9v
zVi}F}W)Zq2Eps)7U;0ZjWv&wb`T)i2KmY3j<qU~^FkeFs_yV_UWlK5P9&)?-kzwN3
z84|Mn%l<ZjK3Yc0fAG>Jt@+&dnUv}J<M$zQYBuzgaNiE(rrRk>UD-=Li{a>(ZHb_e
zu<YP?bPf4W4z~tIN5CVcof&X_I_yf$%r>rnR=hL}S?Ce_EwBc{hhx5JST72FH?juq
zzYY$m7ivf}$|Rf<6{9ek7?d<oVB08+1bPCrOS2T-qp>@Y^qd$Uhg5tf7R8ZuJTD%J
z!#ufEqAW+xvO+Q1iZGf>wc-w%`-v5+rHhi5-Lr^<cqEM>Jwk?2kGAI5<(H=`B}C=)
zYSpRM*=y`JcZ(Jpjti`dM>VOYz?TVh5Be$!=4fAgB7P;|n}CvppF;Xh5<N3;<G3Hl
zh-kVMCRjA}T}}%72SQg4I}Qtt65`Yi5g*MUG!_&iP86gdv>?16c(?{6v!SN`vOmIf
z6@^C-91(ehNlOY$LbN!Lf^n4O{V)aTCK4sFAQhuR2q;>Ih!Zo%p$Ml08On6Cru&yH
zoK5WEs$(oB2=Rw;xFw=5b;06;!?aOJaN+QQ3lqiZR3sx=e4k3xF%k@sXNmO+9S+wD
zwN^`kco<9jMMWCv*9n1T*W(W+WC;R}6bmN8iEND&`O&#1gKmZW2$5@mjm?A=Tf~e^
zWDF}X!Maejc_`dutU%nGi7DZQ8ohE&toSlfqf3I@mG;}NMB8ps<Q7<iuTBpVhA?~W
z7BM>uY2l^XJ|!krCRS!)vb5O5m)&C#zi?t$;O#8fCEO+oreJ(zg$ZU~)}$P=#IzzL
zl-W$;_4{EJk4=Hns+z8mnTRTOyIpQQHDR4=1g(0IQsSKL3tDEcKCBoLs`<^qShh|F
zDC)PU+~u0@An!uHGq}VEc(gTMmrHRuYUdVFoEP+0=efMaT0}|g^{_U4MSQE&x>S$i
zB$r)}JLl-I6e-(njv}?m<*|F2N&foB7HUz35fxIB!a)jO%ZmiVS7QT;DfAuYmQZ@6
zP7^R1555DF<?V}HK6ed8f5BKm+tA(*vNvncG%)X3=%Ca|ZHtkXsioS*p4<DQcV)W8
zW`G;l^ITCO>m$w}d2<k(f$PQRi*O(8A}|H*Xbjw%jS*6Kv&J+&6Y~l84OltyqrPl=
zq3+d_Mq#YwS2_3sSS8NqBCp~dV!FPZ;X6>~2h)cPNtBUrxxK2}(?)4&O?xa14v-RQ
zql4tIJ*n(L?Zh=EMt?NGq9wLoZ*4NAvQ6SAdAJ^10{ino9?Uiom5;};Lp+<0TI>|&
z0*o_vNJt7=7|lE1smovF3x@a|xU98y2OcXxi3xjkfVkHTfR26w)uCbX8tW7{6e8og
zgQi{+chIC2EZ9M@q7ci`CB7`gbQ~5LGf_sp)iW`VdYaUybOpuqXun;LYP)us*l+b)
zQ8Dhg$eM*uN&mza;cj$`pBCXJY!z=7;Rc-Ullcia6S%J!GbmBrHx?JA7+4pR%h;=p
zd&HnQaK#ddbF`tpP);+kPFstoO>CHhand=HR!lR*|D!qBBatcTQ`%|ggNR@3hxmCN
z;Y${FQM&>4zA^RlKu?H9tpe9G7ZZp4U}6@^KH9vtA4FU!X3LPNe%xJzDA7`io39I(
zky=vgZCT_cU!4@)4<@Z1D84Kudw59<n}>|ako#MQ5cw+WKy^Hg_p}djiJ67V!Zi;I
zBZty}G(u+dM-%37nMDWQnTIVQgxp=_xJ8PWwQ`<u@jsU1dr3-^d2`N4@t37=OUYV$
zbl`9W{sJlOD$oi2rUz#kXUi-*&{c_l0=c5gM#eK)NYyAy{E-)61=^B`Qxa8n3uUb&
z7<i-_o8<6f4eJt_m56)mNz=}(M=naW_FE#yj%x8-1Ni~j;@t*Jp-+SzNl_KrlG|iP
z-Zx!Tl`Tw_H5)xjH@nex*<g)E`7&$R#X39g3#Wn&lk75U4qSH^$aD9JJr2Bx7E#lP
zHPWhny9mXJ(4p;@cW9HjqdXzM(M}a7@&e4{s!pCnv4|WtrOr+DvPzGyNt1EKs8wl|
zdI}BztDf%8A@6@p;Ac*{i?m+mJ>DpB(uJ()=ZzW`I&e9F!zL-JcqQdBh&;4MD!G=_
zV6w7B-X`ymcgnlv4!KJ{Oxug$&4?9Cnvh8vL2vJDA}z7QX#cs1yoQ}xJ0H7^p}J<I
z4B0CqqS$Stf|!m;;MrzuH{aPI|10yBUelmb{@AJIy;EmtZ7Rjrs9H6!Q^W9}3`dX#
zdR2`6wV@A4cau6r>;s6yK?8^C`;job3{c1oF3}uU#=69Y`;kYw<HY?ah$Qzdv!DA$
zs_c=#)CX`xMoHk?hj52P46<k~JRuk8^inZ>9ma|;*J3U%h#S{oH44R{bx4g|49u})
z>ekZ;Dm`}b?K(U|&;8^h*pD_be?2Pkpm=^g&XDx2`5Ec*ZDRQb+)EVv>jvyYXP|u}
z`JebkoY{n%@tsI|6d9zi7CnkpI2GuA6opg-6Xbau!gqnUw%`d!Atuo%aH}-bB#sH}
zLSn$N6+RQm#>Y?K9{N;lry^=s;PdUcM?#vYdkVJ`=0~3*Pp>EN^-~0c3Bt7#uSr>^
z$2hIO{pXluBIaMe8;_t^#6Ckxw@lpr4Cdpr!2do&$`Xsk$G^Zd@?#SBAPKg>>^*qF
zH0z2YBUvz`N)gnIa{j=+hrA7vPvSLDHF6_^Y6=zqcougTwbNM9e5b?iR1NEA_fk*>
zrj5^?@AKBXDE)TTjI~tN>bKgqA!fVi*oPRji(~r`AFeF~By1Pw_n`reVo3+aqFLP2
zftf^%!yTAHg-!9-4m?lK_4IQzxk?;+4qj86saQ;V9`)E0c;<O}4D1r8_hT}>W_Ty;
zwARy!we+a}=)}`>+RuN9ugHOZ)K6+<y*TN|&7mVC>|^A1#vdT>bDPLJKvO#c-UA4i
z<ai1DMcc1Po^}U*`711^LZwLS!a{V4d%N)1;NP0eWVh(QNTNks7c#}%7jd&pA<e7e
z@fXpJ<HGS0%Hx|Qe8_Oh6g6dWQ@xw2gGS+o!6)GparPyokg<~w5yzhv(+}bL&=v`w
zGIFw;4`C`<xjlynr4I4&A@b^b#JIz#qwD?bFb;)Mwe|}}0aV$q$qTq74*nW{O#IA5
zVsI<z9jd7;E}}%bx}3_|dZNw)s_1?#4*rH{d|M#l2zG0{{OVD<-*@7hqxc^R=SB0&
zm?{mG#om{ZD6f(5ck#x{<Of9y*DDxLwxjzP>a98lETC#AJ#_F{8|+Q`%kCBNuM*R>
zi<Z|g&3M_}5<&t;UL%ni950VI&r;o9n1ylz2}l&vPoNkjqVRVhFGRHd4rAm_`p*;b
z*$Lbwh8@F7l97jwQCxEEqr0v?`k;iN#?d5OLrxH9{OE~?DVEeu{Fr3z<m*U}$dY5A
zG38MS(E<4+awzH+1#ckPlqKg1^$lbXE|4{V-%2EsyWW6V8gwaaFq6OFXAg}M-EUE_
zR4BfE3oA%OUEOrkV)3(Xs9NPaO?=ypX3|>r-)kYcc<A?}FKnXdZRBdT@T@@F+xUq*
zqFJ79UF@|x?A{iJvy@d8Bi|!3_(auv!~reh*n9MDtHh=EP!x4hBI;Q)Das?~+vb=_
z<>t1)f-|K23I5*q!HHwcA7BX{4Ag&s5Xfuf!u6u*LyWB3BCnpIE9gZ{YRwXoPs-tJ
zlg&B$SozA}$PU?@sgK-5VYA2X*(oaqL@60LsyKREcFRgXYtSLDPS@cLUE3wT_z-a+
Khvn6s^8W!<$o(k*

delta 7293
zcmaJ`3tW}Owx9oeA1}q+KG2N<ZWIs{l!q@AL{wBX1W`16ggf}eoxsMuHwK!fr@SXB
zQyX+NB_*T6Bc<&)JM)p8w4<dZUGZ_!(r%iaqrdXryvNSX(_P=U!N<{ie?R>8nwd3g
zX4YD>)_<1ATm4@8(9gG2b6Fp0+~d1h!BDYX2}QCvsPsjVxTJh6E~(v=b=WQnYRsau
zPZ&0dK|Ybxp5YULH$)ZDABb%}u{bG?`^4j{xJ~qVF~Bz&7e$fpW4I~~5dD+*yKe$+
zi2;67)L#(<vCXfCSm&1kh#0>yqKAK1A0OQ9j}ztoT@fIj^-n^O@c1VqRNU|%gh*it
zh((;p3W!32+ZFImXYqKAMSR@5FKWb<-p`}PZ4a5n{c0HFCbmhO2#d#7@z1bp9k(*3
zm>6UJY`gnrpFyC#KT_LMj0%s&+wP)p1)blfIlI^{##H~^X0}@_=^GLBK2o%z#GykI
zl1Glrsd%EqR>;`<;@!S6bm((^Ct$y@^m`SD#lC*6M6ZuX@HvDOr$cP(+e=)E=+pTy
z$vn!A>N44Jc3gChOeRHTMm~#E?h}!Hfm7nAs0ih3;0*DbPmmbB$4?~nKa2D7wOsUa
zM@5GM7v1AyCIR2M*9>?~@%xT3AI7eVL&y7y8H18>&HeJADBzk%j~^!fHaO3q_W4~S
zHkJJ(YCkuN-U+Aiv-`gi4#Dqd>gMd0dw)_s$FK6hljtMPCI!0ZkIK_m*fe|$0QbJ6
z*_`{}p~E}?@>~g7BWUy_bP|t`F2;A_lhGd&{X*JAbP->s4W=(2vykkzXv}#8i<Yrd
z#m4l~HY#`Q3WNHVzVDBlPLc{TLd5mkeq!MG?Fbjg$G<|~f{e9f>mM^dLyXurA&QRq
z?F90b7&)6hV%j9FoxC_{C3RRc7a-1kH1jYguUPZwC`HWARK@Y^+|J1$3s_jH{^Mfl
zA}(h*GK7}X7e9;YoKXJ^8t^r-Oz}?65M;UkBPUwLG!d9L7WwY%yo*4Vh@Uk$c$z*x
z4~+iEl+n>FU+;)!^Mv)W9^&O$6JQmsv!b{aq6M)dG_XLJ%j}L)Yl#M^GZ<p+<(k9l
zvf0b-bVZa}%ZjWnyQ9j>!!(kXn_~k;WS8rlDYG<N@j};V24bx&aM@kfl82329GX*e
zEYXS{GRSN~$&W;-vC*{r?xBl;89AC`yv<o|brmigS7LQKQ!0`YM>DqA*uB`ApM(l=
zGCvDdB5d{m($wVHz3E##dp4GfKg~`}S`IaEoc$?Poq<(gDx(iGK72J8{gIBUVPe%p
zDgMT#TSE%&;hr>SKTlY%t8N3>2B>E5IdwUk0e{m}dznV;rs?)lO=X*jk^VM|p^rzQ
zR?K}o3@u{W<GDnC{<v9f0Nwd6i_L-;^ayI<7?NWxDbZZcY=^DbR>oK@#LKsPh;aqM
z-RoeQWOXit1^(S<T1zTOc%3LO2#&0Se|&L~wxqyOQRcFhYNl}{gJGe0dX>|qm8vWg
zXcW!bT+v~EAGIFXYc#6ohpLT0z0vTshN`XYBhOmH)eAtAF`_;Zq=!%SI?!r#m<tD}
zH-Q62<MqOD^)|4_X!I)zQv-NgqpqkAQblvoGIbtrTV$&iPA+y(dkz{66o}Us3{Y(x
z7mUfV#YtE!<`+LtW@|2*FEST~tLu5&GM_FCQ)}DBOtM9&b-Zmi$87`DhIY1@Pe!PX
z?Tx=Y*;n5RtuuE~49S^aTp%tlic*_78jKYem4v9ryxW~8u9k%98zJrmr9EiB%S&ew
z+ars0Ls`i_W6Jj5*IsB}cVD}xyy2d9E78Lpk)-x{j!f)%#Wjo{fxxzI?*DXjB6Z#A
z=<a^h_3ywjw`Iw*x*FejDn7Xh{+UHM0A_;4zF>h?R-`!&kY{BUF_@esc2_fCnohqA
z@e6DYr>kJ0W+k2`(Wx@HQxjmGWV9J;5=oUwWbWe1XbKqXDrXb@x5@}ohN-GwkP+qo
zB`-9KNmWreBJ5Qe$QMmjL-;Ysge3G9zDxVygp3)2(6AODKE0&G{*+dvZ^NlO_zC)_
z`kT$HMOc?ska5l|9gK5szh!mY{~V3^n%M=hd--r&65D?lElewXX|(5x=T)O4A?zFw
zJ!9m^vDOl6S)oQJE7P7TXb%bJ+&}*ARd}cRnu;{1>s~3}Ki9Ku%qvz5`nSJ%c>|*t
zX<B&!1)L??ckZdb|5#~Te&madL|uK+ipyg1OH(3#04#GVMvSr~#?PZrz%J`MUdeu-
zAEmo-_pd|Dt0~|&AP&~N(CLOgu+kW~36ZxUmHz@!v!SxXFEqfLz-~kI*qBIpD05?m
z58;EgTLxjbIJhO1bpPv?F_dRhw`Rddyts8deNS$UeyBA9M5o$EDJ$)JZ9I}++1B0d
zuJuEE`@L-=Ria1j$faEM@{XQ<UAX_F&hi5BV|}PNwj+pg*Ec(?gfr6Lc#P<4^^qNR
zt1}VIRXt(#ezR}fZm&;uH=HPj!yG*`)-pYY!R*uu?PW#xbg^JzuFs-*3+kgOwXLo1
zN#D2XEs>GDO>@JB%wsH)lU~LcdWj)Y^$+HuoW<Q)JmLRXeCUIV-_yks#FKAkTZS4E
z6Lg8m+&_elT4;A@rkQpdT@1!j#kn^}AzcK$6^*<dqu=60r!~wb$A7h95q*7kCL&WL
z?~I^y`1sCXqAPdy56B{+d>Uud#M?WQcpfMG6V*Rrx~;g(>Z)*1AXgc|j7d&M+eNu&
zBwq~pgQfHR3YaJI{}78h(dE4m_v?QkP@|KB-M4mGy)N2cta|5qyCWA;oc6NreYXUv
zmES>71H2-{>OC)_&fVp`>EIgI@s!!U!d<MuCa(N(9Oc6So)AJ`CnC-60?#lXnl$)e
z0y5l3_dkl>89;27R{DhI7-y%%z+fqN*vsusE3N5evmVS-Lx^oiPTN2xg_=r+5NkQH
z>R0B=KXv1zx+{mHaZYqTVkX(qN7nlJm+D%hTq{00zOd~|88=$YI^n#p{f`rMsBmxn
zJQxJuJYPHmRJn(@6avfLTfW=_REw-rBe6z2f2x32ae|h?g0}`!=m}}HP@Yb0jq!S$
zSkc;%dS7U@JhO^pj8<6Ww2mBk*BG>!RXniWC%PNs_gir_Z+Fz8<fyA@f|71HYBj$%
zh5!6O1}D5p-nE7%@SoXwE@DqVfem8q>B02<=yW1Bi|t<p3BNP_+Nd#SA`GgK#*dwO
zl;|F3BPoldoPC<$>E5%ubf*}2x{#8LE~Nhd7sBhkS_wNpNDn?!&qo`eFQh->oto20
z0IOh;R^_|_Oq%X(Pod^42boZFFxGI-vb>S&u(!9sH+ksdp7l+rLO&JX?%;e67bRoN
zBJuK&&{B@^Gqu7L3dZS$g}Uct77=(e-F4P&mj`iz=%0TdOdc3}BZ`1R=c@xLwT!wN
zN+-;}`houg!%^<48%KTbbkDtIF#vR!d&{k3YE098CLmw=R}>XY7NEbqV$JQuu63XX
zPzE(wodHYP0nu`M68TB5e?H5bx%ep~NWA~gkvJkR_CT2TCb<XvHkc#EL(n^pehmoj
z*`wf=-q`7jcwF;*<qIE`nwL7Dr|B93D-woLC|qd3*9__35uwQNM0Ld8j`xZ7^zI2K
zk2!MR8DbthLyY$f$D}7136x5A^+Slqrx&u7N7@&=qvTM}Goe_-+qwKKn8wi}^ZTNY
z_dK2rebECr;UOuDdB6$XJ4r7sGAatuXqDMf7=d&0`6x_PlQ|`bW-UFyh?K<HDkJ)1
z4h^sDkCnI}X+|ow(xM4lUh>S3Mm6Z9e~qDS(r?H>(9<o$qs8KnoL}N%MQN~S*kF`^
zU*<AA2cx2Yps=n-%Ig%y4c{Rg{sS$f`X7LqdTDdiykT(nl!qQe@A0a4#?5<X+|>D0
z_RD>Wyv-kQhTFtPy1dN|U?MjqfbzfGoq+!Ek!KSS8y28o#&jcvSshg~b0_3DwDGoL
zf(=ZesWLtZA#!vg#-mwoOhT*?!wojOeF42Ly$pXz#FUPApcV_zd$otp=p<W`5Q#4G
z?@5Mfb(GZY0hGjo-pU^Wj$qI1Vfb1>s7xM-RS1^}Q;`xBsdzOKr`*+HqzoK|!Kjug
zqcG@^7zH7@n$>A9b21hqD@S2SKpY*2M=C5q7h*P48#J^{Qt~~LhRWNckl3vnA#DhY
z?BWl9TT!(fn}V2tWL?fsg{4YY3X*ubBK9md%TJc!5ziMXuu&3_$!QqSEmMK1A|Y`E
zW0^F=$B$(x(z=e+`AQlre40Wash8|G7OQ!lPKSFw8;c(ikgtoMr?7eQ<MF8Maq7<F
zFulUH(C(o4!kE=FEd$p8O^%+3k@Jqy{_eovqp)PRAz_9HhgNQPxHN~8V$~4c-*VeJ
z$n9*jlIp)Tnw9EHE>;-*Q3nHO6$VwMM6)W{IuQf0+(VvCxoDNtGO-b><*%72B5hkI
zqrYi0M?~8Tb}GrOHmuZrm)A&d-?h;iMX$klew&O01?vs2ko{h+4fhyegT9^pY_me|
zx!&@Psp#6ZR%wr8wTk|{X0`I@R4hTAOv^?Z>g6-pm`1IZY)qw=?j`-}+HBi!kLU(n
zbe+N)jq_1`Xpcg5o!;`b92`fJTr>@fuvea$hKc-uBKzcGc;W%&-kjI0JaEUFNfy~e
z{~_g>*^#^cB|GJyD~@?;b1_DbP<I{A;yrdUy+GQdK#L+*^+z|EoQFBR$Xp}zf&21G
znYGGUD&NVY+uQ0nod<dZpSvqF>O$LrFWqzC3&yUlnb{>GIJGWI<l_qeL6I-dLC=k|
zF}UM(g_&WZ%7pd0961{k)Ef#`%J5utmz!o|a@SuJL}wPcs;0XL=h1z-uIS)HcA7&G
z_L_{FgF#6*?o-mu2bFZwt0eu0lw@MR80+VDzpZZzDx*IdF#V!X9ZRobc>?F+X&(g0
zYAdGjPO8|S6e5o<L!jqSG5&|5wEPFTAA&slpF~&AgCAfB^^SB1Hw06_jr5!?!Ebs^
z>1-MKa<B}yV_Nt_^vvS)DS>-A*)l<;WZ%ovXvbQ$!%*ty$tp`$#r%wLS?r?QpQ_Wn
z^?H^(=t5&)y0Kh_YGxU#9`L;(JSej*hG(gF?VTwPR$w^o=f5j3gnkAtL4<#nzDB0X
z<e~~ZA@?qUU1l#q2T$TtsNr<q|6GL+VUxdGifUeZ&k8ba8T>lc+?Bpq6$!J#Qc7)9
z-=Nc*C`TtHGFGAb4_V-_mTCpgigG<PSiDsfdN30gC9@FYZ8w5&UzMkE8KpZcm#3e`
zgtSXAiY2JSU9gDdyn9Ba%VEVTm8M4%&LD!Rmep#tx<*~EZcuC0I<;P9tL2~-=p`$b
z6MU%l(ubB)Tv_9#eOHiAuh;1mZ18riUV*3%n^gp6IIL6@@-(f$9usQi*K08%kdU~A
zP?rJ1<-iq`AJW!QMy{9h*CBN<J&@zuVt4j}lo=F~8r1viJ|Vf@`VVcGJ@47Q4u@2v
zdKPcM9C`}L6B15*C(}0>KTVq`^ADB7HY2)QCSgZ&{MZtE;UaIX&N)vm-i$X<D$TEA
zH!9?@S5bf}8M6iF=yE7qDNL`CKW)X+biC49>_WY#*J}hga7j+zhRL`rU)zR3lq*kc
z!*Voxrff$#$1%C)b?nDw&*UB00#0z?`#10y|Aou+dc1*7o-6gJAV@0bHeji57e!_M
zanjmA$>ml9p^Zk5wi8P^g5<?rm`z6+)rdrj8D))F3N=_+40+=n%#|0{5e&$B7ez{_
zvQoB?3O%RZMWRB9*3XTXkRWT^6kb#1DL1ll!4vU5IV>q?ZWBgRn%mL@dLeoaHQ@tA
z?%Rt+!8K(4@!4f1wld9|u52!w<^-#eX&)kZx;`A@eP}u9%KMn6(au>)TTe!1xy_+v
zml0C+G3l&1SrtxKE>%HYnvT@RY76KQduJ3>W?L61(+;DD+_ww;<aZyEBJ*UoedvdL
zIeZ_+(#e<Z!w`OxFwZ``3#**Jp9VGg+<v$eo02U5XFrOu)l+zYrec$P<sg#CmY*Di
zjpoLGgcqo_?<4F$jVw4s=@~M+8Odbl>Sj##qjKJMMi=BS%?P2?_;-D1wI|_E2;h2k
z?+rQnF!}o$kNq%~aB_evpCA{TW%8dXg*M8Qf5x+FD#r(M)={)zkBm8n8KLtyPBJtp
z-G}6r7dxm})mGnxmE){@^%$ZkLp2|R4%&Y`hJk(>$9YB`Gw4$cqa34sN}H&Y+dd`C
z)*`?A6btAGQ$E9fKY}I~83Eo$j#DPTCLPD|P53!7fch8(0jJB7H=YX1+7hal8g*+Y
zp=Df^juTi&M*ivq>OtPR>I*v25AwY)una%T!6z|{-&SPtNrbBv96!mIPGUPbRa^@O
zQ1o8kO6skVhg#5!?)6tK<S#WFzr^SO-MpZh<t@*u)5t`JPU-;DIL+aLNnSidFOe?t
zw=>8<>c&%qw^QZlQ|PN#HGh>m&r<mN{1ht5xhJ;L)cY5ye_)Y14tQ3crmIFV|Fbh>
z--pR=;I!$6v-p-m$xG)D8yK#3g1*N_jsVZ$a~Q!9DQ|y;NF`j2k&)*yv_qV#Gwh*j
z<|#T46Fu}~#n(uuTjTi}Pf^Gi{0*ICsGRx@H1z-{$)0bpm`)ROk={q?a^gk0;?tyh
z3CVh;HPVxQ3D2ortZG_Fp37EZb5)U9IpXB$%XCgnTCR{SZSwb5$O5JEgDV){{Q~_5
z2twl9ixOk061CW4`kn$SF{WIlI_hG1@+xMc!qfi;_&}{v)0fNmzoN&2)#}PI;}&X#
zi>8-rg)<2u()+ktHH|R(W>mKItWizzM$bg8Omo_t>s5=FBa3ne6$@Qe8&pfQ!9m6R
aLakO^8Edq>;jvEc`YU?-)T=8S)c*zLFX%S_

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/apis.rs b/runtime/common/src/apis.rs
index e6002a5d1..1aaf5690c 100644
--- a/runtime/common/src/apis.rs
+++ b/runtime/common/src/apis.rs
@@ -238,6 +238,7 @@ macro_rules! runtime_apis {
                     list_benchmark!(list, extra, frame_system, SystemBench::<Runtime>);
 					list_benchmark!(list, extra, pallet_balances, Balances);
 					list_benchmark!(list, extra, pallet_multisig, Multisig);
+					list_benchmark!(list, extra, pallet_oneshot_account, OneshotAccount);
 					list_benchmark!(list, extra, pallet_proxy, Proxy);
 					list_benchmark!(list, extra, pallet_scheduler, Scheduler);
 					list_benchmark!(list, extra, pallet_timestamp, Timestamp);
@@ -284,6 +285,7 @@ macro_rules! runtime_apis {
 					add_benchmark!(params, batches, frame_system, SystemBench::<Runtime>);
 					add_benchmark!(params, batches, pallet_balances, Balances);
 					add_benchmark!(params, batches, pallet_multisig, Multisig);
+					add_benchmark!(params, batches, pallet_oneshot_account, OneshotAccount);
 					add_benchmark!(params, batches, pallet_proxy, Proxy);
 					add_benchmark!(params, batches, pallet_scheduler, Scheduler);
 					add_benchmark!(params, batches, pallet_timestamp, Timestamp);
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..8a50c7664
--- /dev/null
+++ b/runtime/common/src/weights/pallet_oneshot_account.rs
@@ -0,0 +1,71 @@
+// 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-08-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Interpreted, CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./duniter
+// benchmark
+// pallet
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_oneshot_account
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=interpreted-i-know-what-i-do
+// --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 {
+		(941_602_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 {
+		(971_453_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 {
+		(1_500_781_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..77ff1f259 100644
--- a/runtime/gdev/Cargo.toml
+++ b/runtime/gdev/Cargo.toml
@@ -36,6 +36,7 @@ runtime-benchmarks = [
     'pallet-provide-randomness/runtime-benchmarks',
     'pallet-im-online/runtime-benchmarks',
     'pallet-multisig/runtime-benchmarks',
+    'pallet-oneshot-account/runtime-benchmarks',
     'pallet-preimage/runtime-benchmarks',
     'pallet-proxy/runtime-benchmarks',
     'pallet-scheduler/runtime-benchmarks',
@@ -68,6 +69,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 +141,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..1c494414a 100644
--- a/runtime/gdev/src/lib.rs
+++ b/runtime/gdev/src/lib.rs
@@ -115,7 +115,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>,
 );
@@ -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