XCM Configuration
EduChain uses the relay chain token (PAS) as its native currency. This leverages Polkadot’s XCM (Cross-Consensus Messaging) for secure, reserve-backed asset transfers.
Configuration File
The complete XCM configuration is in xcm_config.rs.
Reserve Transfer Mechanism
In this model, the relay chain (or Asset Hub) acts as the reserve location.
- Reserve Chain: Holds the actual tokens (locked).
- Parachain: Mints/burns wrapped representations.
How it works
-
Inbound (Reserve -> Parachain):
- User sends tokens from Reserve.
- Reserve locks tokens.
- XCM message sent to Parachain.
- Parachain mints equivalent amount.
-
Outbound (Parachain -> Reserve):
- User sends tokens from Parachain.
- Parachain burns tokens.
- XCM message sent to Reserve.
- Reserve releases tokens to user.
Asset Hub Migration
While EduChain currently uses the relay chain as the reserve, Asset Hub is the recommended reserve location for the future. The configuration is similar.
Sovereign Account
The sovereign account represents the parachain on the reserve chain. View EduChain's Sovereign Account
Configuration Details
Reserve Chain Location
EduChain recognizes the relay chain as its parent.
parameter_types! {
pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::Polkadot);
pub UniversalLocation: InteriorLocation = [
GlobalConsensus(NetworkId::Polkadot),
Parachain(ParachainInfo::parachain_id().into()),
].into();
}
Location to AccountId Mapping
EduChain uses standard converters to map XCM locations to local account IDs:
pub type LocationToAccountId = (
// The parent (Relay-chain) origin converts to the parent `AccountId`.
ParentIsPreset<AccountId>,
// Sibling parachain origins convert to `AccountId` via the `ParaId::into`.
SiblingParachainConvertsVia<Sibling, AccountId>,
// Straight up local `AccountId32` origins just alias directly to `AccountId`.
AccountId32Aliases<RelayNetwork, AccountId>,
);
This configuration handles conversions for:
* Parent relay chain accounts
* Sibling parachain accounts
* Direct AccountId32 mappings
Asset Transactor for PAS Tokens
The AssetTransactor handles incoming PAS tokens. The configuration recognizes the network's native token by its location identifier:
parameter_types! {
pub ParentRelayLocation: Location = Location::parent();
}
pub type FungibleTransactor = xcm_builder::FungibleAdapter<
Balances,
xcm_builder::IsConcrete<ParentRelayLocation>,
LocationToAccountId,
AccountId,
()
>;
Even though PAS is identified by the relay chain's location (Parent), the actual reserve is Asset Hub.
Reserve Asset Definition
Asset Hub is set as the reserve location for PAS tokens:
pub struct PasFromAssetHub;
impl ContainsPair<Asset, Location> for PasFromAssetHub {
fn contains(asset: &Asset, location: &Location) -> bool {
let is_pas = match asset {
Asset {
id: AssetId(asset_id),
fun: Fungible(_),
} => {
// The identifier of PAS (Paseo native token).
// The relative location from this parachain to the Relay Chain.
// Even though PAS is identified by the location of the Relay Chain,
// the reserve is Asset Hub.
matches!(asset_id.unpack(), (1, []))
},
_ => false,
};
let is_from_asset_hub = matches!(
location.unpack(),
// The relative location of Asset Hub on Paseo (Parachain 1000).
(1, [Parachain(1000)])
);
is_pas && is_from_asset_hub
}
}
pub type IsReserve = PasFromAssetHub;
This configuration ensures that:
* PAS is identified by the relay chain location (1, [])
* But the reserve is Asset Hub at (1, [Parachain(1000)])
* Transfers must come from Asset Hub to be recognized as reserve transfers
Barrier: Allow Paid Execution
The barrier configuration controls which XCM messages are allowed to execute:
pub type Barrier = TrailingSetTopicAsId<
DenyThenTry<
DenyReserveTransferToRelayChain,
(
TakeWeightCredit,
WithComputedOrigin<
(
AllowTopLevelPaidExecutionFrom<Everything>,
AllowExplicitUnpaidExecutionFrom<ParentOrParentsExecutivePlurality>,
// Parent and its exec plurality get free execution
),
UniversalLocation,
ConstU32<8>,
),
),
>,
>;
Key features:
- Blocks reserve transfers to the relay chain (use Asset Hub instead)
- Allows paid execution from any location
- Free execution for the parent relay chain and its executive body
Enabling XCM Execution and Transfers
Critical Update: XCM execution is now enabled, which is necessary for sending tokens back to Paseo or Asset Hub:
impl pallet_xcm::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmRouter = XcmRouter;
type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
type XcmExecuteFilter = Everything;
// ^ Enable XCM execution for transfers (changed from Nothing)
type XcmExecutor = XcmExecutor<XcmConfig>;
type XcmTeleportFilter = Everything;
type XcmReserveTransferFilter = Everything;
// ...remaining config...
}
Important Change
XcmExecuteFilter is now set to Everything instead of Nothing. This allows:
- Users to send PAS tokens back to Paseo
- Users to send PAS tokens to Asset Hub
- Bidirectional token transfers between EduChain and other chains
Without this setting, outbound transfers would fail, as it required a local XCM to be executed on your chain.
Finalized Configuration
To view this configuration, please visit the EduChain repository and view xcm_config.rs.
Common Issues
- Unrecognized asset: Asset location or format is incorrect. Use the correct
parentsandinteriorfields. - Filtered: If
XcmExecuteFilteris set toNothing, outbound transfers will fail. - Reserve not recognized: Ensure transfers come from Asset Hub (Parachain 1000), not directly from the relay chain.
Testing Transfers
To test the configuration, you can use the polkadotXcm.execute extrinsic to send XCM messages.
From EduChain to Asset Hub
To send PAS tokens from EduChain to Asset Hub, construct an XCM message with the following instructions:
| Instruction | Purpose | Parameters |
|---|---|---|
WithdrawAsset |
Withdraw PAS from sender's account | Asset ID: { parents: 1, interior: Here } (relay chain token)Amount: Transfer amount in plancks |
InitiateReserveWithdraw |
Send tokens to Asset Hub (the reserve) | Assets: Wild(AllCounted(1))Reserve: { parents: 1, interior: X1(Parachain(1000)) } (Asset Hub)XCM Program: See sub-instructions below |
BuyExecution |
Pay for execution on Asset Hub | Fees: Same asset, ~50% of transfer amount Weight Limit: Unlimited |
DepositAsset |
Deposit tokens to beneficiary | Assets: Wild(AllCounted(1))Beneficiary: { parents: 0, interior: X1(AccountId32({ id: beneficiary_bytes })) } |
Parameter Details:
beneficiary_bytes- The recipient's account ID as a 32-byte array. In PAPI Dev Console or Polkadot.JS UI, this is the decoded SS58 address.- Transfer amount should be in plancks (smallest unit). For example, 1 PAS = 10^10 plancks.
Max Weight: { ref_time: 2_000_000_000, proof_size: 200_000 }
From Asset Hub to EduChain
To send PAS tokens from Asset Hub to EduChain, construct an XCM message with the following instructions:
| Instruction | Purpose | Parameters |
|---|---|---|
WithdrawAsset |
Withdraw PAS from sender's account on Asset Hub | Asset ID: { parents: 1, interior: Here } (relay chain token)Amount: Transfer amount in plancks |
DepositReserveAsset |
Send tokens to EduChain (Asset Hub holds reserve) | Assets: Wild(AllCounted(1))Destination: { parents: 1, interior: X1(Parachain(educhain_id)) } (EduChain)XCM Program: See sub-instructions below |
BuyExecution |
Pay for execution on EduChain | Fees: Same asset, ~50% of transfer amount Weight Limit: Unlimited |
DepositAsset |
Deposit tokens to beneficiary | Assets: Wild(AllCounted(1))Beneficiary: { parents: 0, interior: X1(AccountId32({ id: beneficiary_bytes })) } |
Parameter Details:
educhain_id- EduChain's parachain ID on Paseo (e.g., 4883).beneficiary_bytes- The recipient's account ID as a 32-byte array. In PAPI Dev Console or Polkadot.JS UI, this is the decoded SS58 address.- Transfer amount should be in plancks (smallest unit). For example, 1 PAS = 10^10 plancks.
Max Weight: { ref_time: 2_000_000_000, proof_size: 200_000 }
Key Differences
- From EduChain: Use
InitiateReserveWithdraw(tokens going to reserve) - From Asset Hub: Use
DepositReserveAsset(tokens leaving reserve) - Both require
XcmExecuteFilter = Everythingon EduChain for execution
Verification Steps
After sending transfers:
-
From EduChain to Asset Hub:
- Check balance decreases on EduChain
- Check balance increases on Asset Hub
-
From Asset Hub to EduChain:
- Check balance decreases on Asset Hub
- Check balance increases on EduChain
Resources
- Polkadot XCM Transfer Guide (from Polkadot Docs)
- Configuring a parachain to use Relay Chain native token (Rustdocs Guide)
- Changing the DOT reserve from Relay Chain to Asset Hub (HackMD Guide) - Source of the
PasFromAssetHubimplementation