Skip to main content

๐Ÿš€ Integrating Commitments

After installation and configuration, the next step is using pallet-commitment inside your own pallet.

This is where your pallet becomes a usage pallet.

Examples:

  • ๐Ÿ› staking pallet
  • ๐Ÿ—ณ governance pallet
  • ๐Ÿ’ฐ lending pallet
  • ๐Ÿค escrow pallet
  • ๐Ÿ“œ contracts pallet

Your pallet defines:

  • ๐ŸŽฏ why commitments exist
  • ๐Ÿ†” what the digest represents
  • โš–๏ธ what positions mean
  • ๐Ÿ”’ which freeze reasons are used

while pallet-commitment provides:

  • ๐Ÿงพ ownership
  • ๐Ÿ†” digest accounting
  • ๐Ÿ•’ lazy resolution
  • ๐Ÿงบ index + ๐ŸŠ pool support

๐Ÿง  Coupling Modelsโ€‹

There are two recommended ways to integrate Commitment.

Tight Couplingโ€‹

Directly import pallet_commitment::Pallet and use Commitment traits.

Example:

use pallet_commitment::Pallet as Commitment;
use frame_suite::commitment::traits::*;

Then call traits directly:

Commitment::<T>::place_commit(...)

where T is the runtime type implementing the pallet's Config trait.

Best for:

  • internal pallets
  • runtime-owned protocol pallets
  • tightly integrated systems

Use trait adapters through your pallet Config.

Example:

#[pallet::config]
pub trait Config: frame_system::Config {
type MyCommitment: Commitment<Self::AccountId>;
}

Then call:

T::MyCommitment::place_commit(...)

and wire the adapter in runtime:

impl pallet_x::Config for Runtime {
type Commitment = pallet_commitment::Pallet<Runtime>;
}

This keeps your pallet reusable and decoupled.

Recommended for production architecture.


๐ŸงŠ Local Freeze Reasonโ€‹

Inside your usage pallet:

#[pallet::composite_enum]
pub enum LocalFreezeReason {
StakeCommit,
GovernanceDeposit,
}

This is your pallet-local reason system. Each usage pallet can have its own reason variants and shall not be leaked (unless tightly integrated).

Then hook pallet config:

#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeFreezeReason: From<LocalFreezeReason>;
}

This allows:

LocalFreezeReason::StakeCommit.into()

to convert into the global runtime freeze reason.

This is how your pallet safely uses its own local reasons across commitment boundary towards pallet_commitment's expected reason enum.


๐Ÿ†” Digest Accountsโ€‹

Commitment requires

type Digest = frame_system::Config::AccountId

Usually this represents:

  • validator account
  • proposal account
  • vault account
  • contract account

This becomes the shared economic anchor.

This is usage pallet-specific with the only hard-requirement of it being a valid account address.


๐Ÿ”’ Place a Commitmentโ€‹

All commitment related operations are called via traits only defined in frame_suite::commitment.

Now commit funds:

In case of Config type (loosely coupled)

T::MyCommitment::place_commit(
&who, // proprietor
amount, // value locked
LocalFreezeReason::StakeCommit.into(), // coverted local freeze-reason
digest, // to commit digest
)?;

In case of other type T implementing or tightly coupled pallet_commitment::Pallet<T> = T

<T as frame_suite::Commitment<...>>::place_commit(
&who,
amount,
LocalFreezeReason::StakeCommit.into(),
digest,
)?;

This issues the first immutable receipt for the proprietor's commitment.

Until it is resolved using resolve_commit(), the proprietor cannot place another commitment for the same freeze reason.

However, they can still:

  • ๐Ÿ“ˆ raise_commit() on the existing commitment for that same reason
  • ๐Ÿ†• place a new commitment under a different freeze reason

This preserves the core invariant:

(Proprietor, Reason) -> one active commitment

Check Commit Valueโ€‹

Query the active value:

T::MyCommitment::get_commit_value(
&who,
LocalFreezeReason::StakeCommit.into(),
)?;

This returns current value, not original deposit


๐Ÿท๏ธ Use Different Reasonsโ€‹

You can use another reason:

LocalFreezeReason::GovernanceDeposit.into() // Local Reason to Commit Reason Converted

and place another commitment.

Each reason creates:

(Proprietor, Reason) -> one commitment

This allows multiple protocol commitments safely.


๐ŸŽฏ Use Variantsโ€‹

Now commit using a specific variant.

Example:

let position = Disposition::Affirmative;
impl pallet_commitment::Config for Runtime {
type Position = frame_suite::Disposition;
}

or in case of custom positions

impl pallet_commitment::Config for Runtime {
type Position = MyPosition;
}

pub enum MyPosition {
Happy,
Sad
}

impl frame_suite::PositionIndex for MyPosition { ... }

and associated type equality to use the same position here in our usage pallet

#[pallet::config]
pub trait Config: frame_system::Config {
type MyCommitment: Commitment<Self::AccountId, Position = MyPosition>;
// ^^^^^^^^^^^^^^^^^^^^^
}

Then place:

T::MyCommitment::place_commit_of_variant(
&who,
amount,
LocalFreezeReason::StakeCommit.into(),
digest,
MyPosition::Happy,
)?;

Now the commitment belongs to:

digest + variant

instead of only digest. Then query again using get_commit_value(...).


๐Ÿ“ˆ Raise the Commitmentโ€‹

Increase the same existing commitment:

T::MyCommitment::raise_commit(
&who,
more_amount,
LocalFreezeReason::StakeCommit.into(),
)?;

raise_commit() automatically finds the existing:

  • ๐Ÿ†” digest
  • โš–๏ธ variant (if variant-based)

for that (Proprietor, Reason) commitment and adds more value to it.

You do not provide the digest or variant again.

It creates an additional new immutable receipt, without mutating old receipts.

This preserves:

same commitment identity + new receipt history

Then query get_commit_value(...) and the active value increases.


โš–๏ธ Mutate Digest Balance (Reward / Penalty)โ€‹

Now simulate digest value changes.

First check current digest valueโ€‹

If using variants:

T::MyCommitment::get_digest_value_variant(
digest,
position,
)?;

If not using variants:

T::MyCommitment::get_digest_value(
digest,
)?;

This uses the default / no-op variant internally.

Now update the digest valueโ€‹

T::MyCommitment::set_digest_of_variant(
digest,
position,
new_value,
)?;

or without variants:

T::MyCommitment::set_digest(
digest,
new_value,
)?;

If:

new_value > existing_value

this behaves like a reward.

If:

new_value < existing_value

this behaves like a penalty / slash.

This changes digest value, not receipts, as receipts remain immutable.

Query Active Value Againโ€‹

Now check get_commit_value(...)

You will see commitment value changed, even though receipts never changed.

This is lazy resolution.


๐ŸŒ Global Valuesโ€‹

Reasonโ€‹

Now query:

T::MyCommitment::get_total_value(
LocalFreezeReason::StakeCommit.into()
)?;

This gives total committed value for that freeze reason

Very useful for protocol-wide accounting.

Issuance & Reapโ€‹

When digest value increases (for example: rewards), the commitment value becomes larger than the original deposited amount.

Later a related-commitment during resolution:

withdraw > deposit

which means new assets must be issued.

But this must already be accounted for when the digest changes, not only during resolve_commit().

That is why Commitment tracks AssetToIssue Storage Value.

This stores pending fungible issuance needed to keep accounting equilibrium with the underlying asset system.

It tells the runtime:

how much asset supply must eventually be issued during final resolution

This keeps the system balanced:

Commitment Issue
+ Fungible Total Issuance
- Commitment Reap
----------------------
= Total Asset Units
----------------------

Similarly, if digest value decreases then excess assets must eventually be removed via AssetToReap

let to_be_reaped = pallet_commitment::AssetToReap::get();
let to_be_issued = pallet_commitment::AssetToIssue::get();

These ensure deferred balance resolution remains economically correct.


๐Ÿ Resolve Commitmentโ€‹

Now to resolve:

T::MyCommitment::resolve_commit(
&who,
LocalFreezeReason::StakeCommit.into(),
)?;

This performs:

  • ๐Ÿงพ receipt resolution
  • โš–๏ธ final settlement
  • โž• pending issuance
  • ๐Ÿ’ธ asset transfer

Now check underlying fungible balance.

You will receive:

committed amount + reward

This is final lazy settlement.

Check Equillibriumโ€‹

After:

commit
-> increase digest value
-> resolve_commit()

the pending issuance (as digest mints) is no longer stored inside commitment.

It has now been transferred into the underlying fungible asset system from AssetToIssue, because the pending issuance has been fully settled during resolution.

The reward is now part of real fungible balance and total issuance.


๐Ÿ“Œ Comprehensive Lifecycleโ€‹

place_commit()
-> receipt created

digest evolves
-> value changes

resolve_commit()
-> truth becomes fungible balance

๐Ÿš€ Next Stepโ€‹

Now that direct commitments, variants, digest mutation, and resolution are complete, the next step is building grouped commitment structures.

This is where one commitment can safely control multiple underlying digests through weighted distribution.

๐Ÿ‘‰ Usage -> Integrating Indexes