Skip to content

XCM — Cross-Consensus Messaging

What is XCM?

XCM (Cross-Consensus Messaging) is a message format (not a transport protocol) that defines what a message receiver should DO when it receives a message. XCM works between:

  • Parachains (via XCMP/HRMP)
  • Parachains and Relay Chain (via UMP/DMP)
  • Smart contracts and pallets (within same chain)
  • Across bridges to external networks

Message Passing Channels

ProtocolDirectionStatus
UMP (Upward Message Passing)Parachain → Relay ChainActive
DMP (Downward Message Passing)Relay Chain → ParachainActive
HRMP (Horizontal Relay-routed Message Passing)Parachain ↔ Parachain (via Relay)Active (interim)
XCMP (Cross-Chain Message Passing)Parachain ↔ Parachain (direct)Planned

Note: XCMP is the intended final solution for parachain-to-parachain messaging. Until fully activated, HRMP (which routes through the Relay Chain) is used. HRMP is functional but has higher latency and resource cost.

Common XCM Operations

Transfer Assets to Another Parachain

rust
// Send DONG tokens to address on Parachain B
use xcm::prelude::*;

let message = Xcm(vec![
    // Withdraw DONG from sender's account
    WithdrawAsset((Here, 100 * DONG).into()),

    // Pay for XCM execution on destination
    BuyExecution {
        fees: (Here, 1 * DONG).into(),
        weight_limit: Limited(Weight::from_parts(1_000_000_000, 0)),
    },

    // Deposit to recipient on destination chain
    DepositAsset {
        assets: All.into(),
        beneficiary: AccountId32 {
            network: None,
            id: recipient_account_id.into(),
        }.into(),
    },
]);

// Send via HRMP to Parachain B (paraId: 2001)
pallet_xcm::Pallet::<Runtime>::send_xcm(
    Here,
    (Parent, Parachain(2001)).into(),
    message,
)?;

Execute Cross-Chain Contract Call

rust
// Call a smart contract on another parachain
let call_data = encode_call("transfer(address,uint256)", (recipient, amount));

let message = Xcm(vec![
    WithdrawAsset((Here, fee_amount).into()),
    BuyExecution { fees: (Here, fee_amount).into(), weight_limit: Unlimited },

    Transact {
        origin_kind: OriginKind::SovereignAccount,
        require_weight_at_most: Weight::from_parts(5_000_000_000, 0),
        call: RuntimeCall::Contracts(pallet_revive::Call::call {
            dest: contract_address,
            value: 0,
            gas_limit: Weight::from_parts(1_000_000_000, 0),
            storage_deposit_limit: None,
            data: call_data,
        }).encode().into(),
    },
]);

Asset Multi-Location

XCM uses MultiLocation to identify assets across chains:

rust
// DONG native token on Dong Chain (when sending from Dong Chain)
let dong_here = MultiLocation { parents: 0, interior: Here };

// DONG from the perspective of another parachain
let dong_from_para = MultiLocation {
    parents: 1,
    interior: X1(Parachain(DONG_CHAIN_PARA_ID)),
};

// Relay Chain token (DOT) from Dong Chain
let dot = MultiLocation { parents: 1, interior: Here };

// Ethereum ERC-20 (via bridge)
let usdc_on_eth = MultiLocation {
    parents: 2,
    interior: X2(
        GlobalConsensus(Ethereum { chain_id: 1 }),
        AccountKey20 { network: None, key: USDC_ADDRESS },
    ),
};

XCM Asset Registry

Dong Chain maintains a registry of recognized foreign assets:

rust
// In runtime configuration
parameter_types! {
    pub const DotLocation: MultiLocation = MultiLocation::parent();
    pub DotPerSecond: (AssetId, u128, u128) = (
        AssetId(DotLocation::get()),
        1_000_000_000_000,  // DOT price in units per second
        1024,               // DOT price per byte
    );
}

pub type AssetTransactors = (
    // Handle native DONG
    CurrencyAdapter<Balances, IsConcrete<SelfReserve>, LocationToAccountId, AccountId, ()>,
    // Handle foreign assets (DOT, etc.)
    FungiblesAdapter<Assets, ConvertedConcreteId<...>, LocationToAccountId, AccountId, ...>,
);

HRMP Channel Setup

Before two parachains can exchange XCM messages, HRMP channels must be established:

bash
# Parachain A requests channel to Parachain B
polkadot-js-api tx.hrmp.hrmpInitOpenChannel \
  --recipient <para_b_id> \
  --proposedMaxCapacity 1000 \
  --proposedMaxMessageSize 102400

# Parachain B accepts
polkadot-js-api tx.hrmp.hrmpAcceptOpenChannel \
  --sender <para_a_id>

Both operations require bond deposits on the Relay Chain.

XCM Fee Calculation

XCM messages require fees on the destination chain. Fee estimation:

typescript
// JavaScript: estimate XCM fee
const xcmWeight = await api.call.xcmPaymentApi.queryXcmWeight({
  v3: [
    { withdrawAsset: [[(Here, transferAmount)]] },
    { buyExecution: { fees: [(Here, 1)], weightLimit: 'Unlimited' } },
    { depositAsset: { assets: { wild: 'All' }, beneficiary: recipientLocation } }
  ]
});

const xcmFee = await api.call.xcmPaymentApi.queryWeightToAssetFee(
  xcmWeight,
  { v3: DotLocation }
);

Dong Chain XCM Config

rust
// XCM execution configuration
pub struct XcmConfig;

impl xcm_executor::Config for XcmConfig {
    type RuntimeCall = RuntimeCall;
    type XcmSender = XcmRouter;
    type AssetTransactor = AssetTransactors;
    type OriginConverter = XcmOriginToTransactDispatchOrigin;

    // Who can reserve-transfer assets
    type IsReserve = MultiNativeAsset<AbsoluteAndRelativeReserveProvider<SelfLocation>>;

    // Who can teleport assets
    type IsTeleporter = NativeAsset;  // Only DONG can be teleported

    // Weight calculation
    type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;

    // Fee handling
    type Trader = UsingComponents<
        WeightToFee,
        SelfReserve,
        AccountId,
        Balances,
        ToAuthor<Runtime>,
    >;

    type ResponseHandler = PolkadotXcm;
    type AssetTrap = PolkadotXcm;
    type AssetLocker = PolkadotXcm;
    type AssetExchanger = ();
    type AssetClaims = PolkadotXcm;
    type SubscriptionService = PolkadotXcm;
    type PalletInstancesInfo = AllPalletsWithSystem;
    type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
    type FeeManager = ();
    type MessageExporter = ();
    type UniversalAliases = Nothing;
    type CallDispatcher = RuntimeCall;
    type SafeCallFilter = Everything;
    type Aliasers = Nothing;
}

Gaming Asset Cross-Chain Transfer

Moving an ERC-721 character NFT from Dong Chain to a Gaming Parachain via XCM:

rust
// Transfer ERC-721 character NFT via XCM
// (requires XCM NFT standard — work in progress in Polkadot ecosystem)
let nft_asset = MultiAsset {
    id: AssetId(MultiLocation {
        parents: 0,
        interior: X2(
            PalletInstance(20),  // pallet-revive index
            AccountKey20 { network: None, key: CHARACTER_CONTRACT },
        ),
    }),
    fun: Fungibility::NonFungible(AssetInstance::Index(character_token_id)),
};

let message = Xcm(vec![
    WithdrawAsset(nft_asset.clone().into()),
    BuyExecution { fees: (Here, fee).into(), weight_limit: Unlimited },
    DepositAsset {
        assets: nft_asset.into(),
        beneficiary: player_location_on_gaming_para,
    },
]);

Released under the Apache 2.0 License.