๐งฑ Call Surface
The execution model of pallet-xp is built on a simple architectural rule:
all logic lives in traits all public access happens through thin wrappers
This is called the call surface.
It defines how users, runtime modules, and external pallets reach XP logic without directly touching storage.
The pallet never follows:
User -> Extrinsic -> Direct Storage Write
Instead it follows:
User -> Wrapper Layer -> XP Traits -> Storage
and
Runtime -> Direct Trait Calls -> Storage
This separation is intentional.
Problem Without a Call Surfaceโ
If extrinsics directly handled business logic:
- logic gets duplicated
- runtime integrations become difficult
- testing becomes harder
- pallets cannot safely reuse behavior
- invariants become inconsistent
This creates fragile architecture.
Solution With a Call Surfaceโ
All execution closes into:
XP Trait Engine
and every external interaction becomes only a controlled wrapper.
This gives:
| Benefit | Result |
|---|---|
| ๐ Single mutation engine | predictable behavior |
| โป๏ธ Logic reuse | easier runtime composition |
| ๐งช Better testing | traits tested independently |
| ๐งฉ Modular design | pallets integrate safely |
| โ๏ธ Deterministic mutation | no unsafe storage shortcuts |
This is why the call surface is an architectural boundary, not just an API.
Traits First, Extrinsics Secondโ
XP is not designed as an extrinsic-first pallet.
It is designed as a trait-first system.
The real execution engine is:
| Core Trait | Responsibility |
|---|---|
XpSystem | reads + validation |
XpOwner | ownership control |
XpMutate | XP growth + mutation |
XpReserve | soft constraints |
XpLock | hard constraints |
XpReap | lifecycle termination |
BeginXp | safe lifecycle start |
These traits define:
- who owns XP
- how XP grows
- how constraints work
- how lifecycle starts
- how lifecycle ends
Why Extrinsics Are Thin Wrappersโ
Users cannot call traits directly.
They need runtime access through signed transactions.
That means the pallet must provide:
| User Requirement | Provided By |
|---|---|
| Transaction signing | Extrinsics |
| Origin verification | Extrinsics |
| Ownership checks | Extrinsics |
| Permission enforcement | Extrinsics |
| Event visibility | Extrinsics |
But extrinsics should not own protocol logic.
They should only:
authenticate
-> validate
-> forward
Extrinsics provide access.
Traits provide behavior.
Three Call Surfaces
The architecture exposes three execution paths.
All three eventually reach the same XP trait engine.
1. Public Call Surface (User Extrinsics)โ
This is how external users interact with XP.
Flow:
User
-> Signed Extrinsic
-> Validation Layer
-> XP Trait Execution
-> Storage Mutation
Users never mutate storage directly. They enter only through controlled wrappers.
Core Dispatchable Extrinsicsโ
| Extrinsic | Architectural Role | Final Trait Destination |
|---|---|---|
call() | XP-scoped execution | runtime dispatch |
handover() | ownership transition | XpOwner |
dispose() | lifecycle finalization | XpReap |
force_handover() | governance override | XpOwner |
force_genesis_config() | runtime parameter control | config updates |
These are execution gateways, not business logic functions.
Example handover()โ
The user calls:
handover(origin, xp_id, new_owner)
The extrinsic performs:
| Step | Action |
|---|---|
| 1 | ensure_signed(origin) |
| 2 | verify owner of xp_id |
| 3 | validate destination |
| 4 | call XpOwner::transfer_owner() |
| 5 | emit event |
The actual ownership mutation happens inside the trait. The extrinsic only protects access ๐
2. Internal Call Surface (Runtime Trait Access)โ
Other pallets should not need extrinsics. They need direct composability.
This is why XP traits are runtime-native interfaces.
Flow:
Runtime Module
-> Direct Trait Call
-> XP Mutation
-> Storage
Example:
XpMutate::earn_xp(...)
This means:
- no dispatch
- no signed origin
- no wrapper
- only protocol composition
Common Runtime Operationsโ
| Runtime Action | Trait Function |
|---|---|
| Create XP | begin_xp() |
| Earn XP | earn_xp() |
| Reserve XP | set_reserve() |
| Lock XP | set_lock() |
| Slash XP | slash_xp() |
| Reap XP | try_reap() |
This is where most real execution happens.
Not inside extrinsics.
Runtime Integratorsโ
Typical integrations include:
| System | XP Usage |
|---|---|
| Governance | participation reputation |
| Staking | reserve / lock constraints |
| Missions | contributor progression |
| Validators | commitment reputation |
| Participation Layers | protocol activity tracking |
This is where XP becomes protocol-native.
The Special Case -> call()โ
Among all extrinsics, call() is different.
It is not just a wrapper.
It is the architectural bridge between:
AccountId -> XpId
This is the center of the entire pallet ๐ง
What call() Actually Doesโ
Dispatch Flowโ
ensure_signed(origin)
-> verify owner(origin, xp_id)
-> elevate XpId into execution subject
-> dispatch RuntimeCall as Signed(XpId)
This transforms:
Signed(AccountId)
into:
Signed(XpId)
inside runtime execution. That transformation is what makes XP identity-native.
Without this, XP would only be metadata. With this, XP becomes execution itself.
Authority vs Executionโ
| Layer | Responsibility |
|---|---|
AccountId | signs + authorizes |
XpId | executes + mutates |
Core rule:
๐ค Account authorizes ๐ง XP executes
This is the architectural heart of pallet-xp.
3. Fungible Adapters Surfaceโ
Some pallets do not use XP traits directly.
They expect standard Substrate balance interfaces:
frame_support::traits::fungible::*
Instead of forcing custom integrations, XP exposes fungible adapters.
This creates:
External Pallet
-> Fungible Adapter
-> XP Traits
-> Storage
Again:
Traits remain the real engine. Adapters are only wrappers.
Adapter Mappingโ
| External Operation | Internal XP Trait |
|---|---|
hold() | set_reserve() |
freeze() | set_lock() |
mint_into() | set_xp() |
burn_from() | slash_xp() |
This allows:
- governance pallets
- staking systems
- hold/freeze integrations
- standard Substrate pallets
to work with XP without custom rewrites.
Unified Call Surface Closureโ
All execution paths eventually close into the same place:
XP Trait Engine
That closure guarantees:
| Guarantee | Why It Matters |
|---|---|
| One mutation engine | consistent execution |
| One validation model | safer runtime behavior |
| One lifecycle system | predictable XP rules |
| One execution philosophy | identity-first design |
This prevents fragmented logic. That is what makes the architecture safe.
Full Call Surface Architectureโ
Everything ends in the same place:
XP trait execution
never direct storage mutation.
๐ Next Stepsโ
To start using pallet-xp in your runtime, the next step is setting up the pallet and configuring its required dependencies.
๐ Getting Started -> Installation