Skip to main content

⚙️ Configuration

After installation, pallet-xp exists inside your runtime.

But installation only makes the pallet available.

Configuration defines:

how XP actually behaves

This is where you decide:

  • how much XP exists initially
  • how reputation grows
  • why XP can be locked or reserved
  • how runtime calls execute through XpId
  • whether listeners and events are enabled

This section explains the full runtime configuration layer.

What Gets Configured

There are two major configuration areas:

AreaPurpose
impl pallet_xp::Configruntime execution behavior
pallet_xp::GenesisConfiginitial XP system state

Think of it like this:

Config Trait -> how XP works

GenesisConfig -> how XP starts

Both are required.


1. Config Trait

Inside:

impl pallet_xp::Config for Runtime

you define how XP integrates with your chain.

Default installation usually starts with:

impl pallet_xp::Config for Runtime {
type Xp = u64;
type Pulse = u32;

type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;

type ReserveReason = RuntimeHoldReason;
type LockReason = RuntimeFreezeReason;

type Extensions = frame_suite::Ignore<Xp>;

type EmitEvents = ConstBool<true>;
type WeightInfo = pallet_xp::weights::SubstrateWeight<Runtime>;
}

Now let's explain what each field actually controls.

1.A Scalar Types

These define the fundamental XP accounting model.

TypeMeaningCommon Choice
Xpactual XP value storedBalance, u128
Pulsereputation progression counterBlockNumber, u32

type Xp

type Xp = u64;

This is the actual value stored inside XP identities.

It controls:

  • free XP
  • reserved XP
  • locked XP
  • slashing
  • rewards

Recommended:

u128

or your runtime:

Balance

Why?

Because XP may integrate with fungible adapters and larger values are safer.

Avoid small types like:

u32
u64

for production systems.

type Pulse

type Pulse = u32;

This controls long-term reputation progression.

It is NOT XP value.

It controls:

  • reputation thresholds
  • growth multipliers
  • earning progression

Recommended:

u32

or your runtime:

BlockNumber

because pulse behaves like progression over time.

This makes pulse stable and predictable 📈


1.B Runtime Anchors

These connect XP to the runtime itself.

TypePurpose
RuntimeEventevent emission
RuntimeCallXP-scoped runtime dispatch

type RuntimeEvent

type RuntimeEvent = RuntimeEvent;

This allows XP to emit standard runtime events.

Examples:

  • XP earned
  • XP locked
  • ownership transferred
  • XP reaped

Always use the runtime-generated global enum.

Never create a custom type here.

type RuntimeCall

type RuntimeCall = RuntimeCall;

This is one of the most important configuration fields.

It powers the extrinsic:

call(origin, xp_id, RuntimeCall)

which enables:

Account signs
-> ownership verified
-> XpId executes
-> RuntimeCall dispatched

Without this:

XP cannot act as an execution identity 🧠


1.C Composite Reasons

These define why XP can be constrained.

TypePurpose
ReserveReasonsoft constraint
LockReasonhard constraint
type ReserveReason = RuntimeHoldReason;
type LockReason = RuntimeFreezeReason;

Do NOT manually create local enums unless absolutely necessary.

Why?

Because Substrate already composes all pallets reasons into:

  • RuntimeHoldReason
  • RuntimeFreezeReason

This gives:

  • ✅ native compatibility
  • ✅ fungible adapter support
  • ✅ staking integration
  • ✅ governance integration
  • ✅ bounded execution

This is the production-safe setup 🔒

1.D Extensions

This is often misunderstood.

type Extensions

means:

lifecycle listeners

These are NOT events.

They are runtime hooks triggered during:

  • XP creation
  • XP earning
  • reserve
  • lock
  • slash
  • reap
  • ownership transfer

Default Setup

type Extensions = frame_suite::Ignore<Xp>;

This means:

hooks exist
-> intentionally do nothing

This is the safest starting point.

It prevents accidental business logic coupling.

Later You Can Replace It With

  • governance listeners
  • reward hooks
  • contributor rewards
  • analytics integrations
  • protocol reactions

Example:

on XP earned
-> reward contributor

This is where XP becomes programmable ⚙️. And will be analyzed in advanced sections of pallet-xp.


1.E EmitEvents

type EmitEvents

Controls whether the pallet emits standard internal XP events.

These are events related to individual XP operations such as:

  • earning XP
  • locking XP
  • reserving XP
  • pulse progression
  • internal lifecycle updates
EnvironmentValue
Testing / LocalConstBool<true>
ProductionConstBool<false>

Why?

Because events cost weight.

For production systems, emitting events for every individual XP operation is usually avoided to reduce unnecessary execution overhead.

Even if these internal events are disabled:

listeners still execute

This is important.

Also:

extrinsics will still emit events

because extrinsic execution is user-controlled and should remain visible at the runtime level.

What is typically disabled in production are the fine-grained internal events for each individual XP option, not the standard extrinsic-level runtime visibility.

Production systems often prefer listeners over internal events.


1.F WeightInfo

type WeightInfo

Used for:

  • benchmarking
  • dispatch weight calculation
  • fee correctness

Recommended:

type WeightInfo =
pallet_xp::weights::SubstrateWeight<Runtime>;

This uses the default Substrate benchmark weights provided by the pallet.

However:

never use these benchmark weights for production blindly

Your runtime configuration, hardware environment, execution paths, and optimization choices may produce different benchmark results.

Production runtimes should always generate their own benchmarks using their actual runtime configuration and target hardware assumptions.

If your runtime requires different extrinsic weight benchmarks, you should provide your own custom WeightInfo implementation instead.

This ensures runtimes with specialized execution costs or benchmarking requirements can hook into XP safely.

See Advanced -> Weights for custom benchmarking instructions.


2. GenesisConfig

Now we configure how XP starts from block zero.

Inside:

runtime/src/genesis_config_presets.rs

you define:

pallet_xp::GenesisConfig

This controls the initial XP economy.

Not runtime behavior.

Initial state.


GenesisConfig Structure

pub struct GenesisConfig {
pub min_pulse,
pub init_xp,
pub pulse_factor,
pub genesis_acc,
}

Each field matters.

FieldMeaning
init_xpstarting XP for new identities
min_pulsepulse threshold before real XP earning
pulse_factorhow pulse grows
genesis_accpre-created XP identities

These define the XP economy from block zero.

2.A pub init_xp

init_xp: 10

Meaning:

new XpId -> starts with 10 XP

This affects every new identity created through:

XpMutate::create_xp()

Higher values = easier early participation.

Lower values = stricter onboarding.


2.B pub min_pulse

min_pulse: 3

min_pulse defines the minimum pulse required before XP rewards are actually credited.

It creates the boundary between reputation building and reward earning

if pulse < min_pulse:
earn_xp() -> pulse increases only

if pulse >= min_pulse:
earn_xp() -> pulse increases + XP rewards applied

This means a new XP identity does not immediately receive valuable XP.

Instead:

  1. pulse must grow first
  2. trust must be established
  3. only then XP rewards begin

You can think of it as:

pulse = trust score
xp = earned value

This is important because XP is designed as reputation, not instant value.

Without this threshold:

new accounts could farm XP immediately

which breaks the entire reputation model.

If too low:

XP farming becomes cheap

If too high:

new users struggle to participate

Hence,

min_pulse defines the minimum trust cost before value creation

This improves:

  • spam prevention
  • sybil resistance
  • governance safety
  • contributor reputation integrity

2.C pub pulse_factor

pulse_factor: Stepper::new(50, 10).unwrap()

While min_pulse defines when rewards begin pulse_factor defines how fast pulse grows.

It controls the speed of trust formation.

Pulse does not increase directly by +1 per action.

Instead, the runtime uses a DiscreteAccumulator:

step += per_count

if step >= threshold:
pulse += 1
step resets

with:

Stepper::new(threshold, per_count)

So for:

Stepper::new(50, 10)

the logic becomes:

each valid earn_xp() call
-> step += 10

when step reaches 50
-> pulse += 1

which means:

5 valid actions -> +1 pulse

because:

10 + 10 + 10 + 10 + 10 = 50

This matches the accumulator implementation and pallet behavior.

  • easy onboarding
  • strong abuse resistance
  • healthy contributor progression
  • good governance safety

2.D pub genesis_acc

These are pre-created XP identities.

Each entry creates:

AccountId--owns-> XpId

from block zero.

Used for:

  • validators
  • governance actors
  • treasury operators
  • protocol maintainers

No manual initialization needed.

Example Development Setup

xp: pallet_xp::GenesisConfig {
min_pulse: 3,
init_xp: 10,
pulse_factor: Stepper::new(50, 10).unwrap(),
genesis_acc: genesis_accounts,
}

This means:

  • start with 10 XP
  • need 3 pulses before rewards accumulation
  • predefined identities exist

Ready for hacking 🚀

After this:

XP is not just installed it is fully configured as a runtime-native execution system


🚀 Next Steps

Now that XP is configured, the next step is creating and managing your first XP identity SAFELY.

👉 Getting Started -> First XP