Skip to content

Self-Serve Asset Registration for Sibling Parachains

Introduction

Registering your parachain's native tokens on Moonbeam or Moonriver lets your community enjoy ERC‑20–style UX and deep EVM integrations while retaining full on‑chain provenance. This guide shows sibling Polkadot parachain teams how to self‑register a foreign asset using the new ForeignAssetOwnerOrigin introduced in Moonbeam Runtime 3600.

Why a New Origin?

Moonbeam introduced a new dedicated origin called ForeignAssetOwnerOrigin, which only permits an XCM message whose origin contains the asset's multilocation to execute calls in the evm‑foreign‑assets pallet. In practice, that means only the sovereign account of the parachain that owns the asset, or Moonbeam governance, can create, freeze, unfreeze, or relocate it. Alongside this, a configurable runtime constant called ForeignAssetCreationDeposit is reserved from the caller's sovereign account at creation time. The deposit discourages spam registrations.

Required Deposits

To prevent spam, a ForeignAssetCreationDeposit is required and locked for the lifetime of the asset. The deposit is funded from the sibling parachain's sovereign account on the Moonbeam network, which thus needs to be sufficiently funded to cover the asset deposit and the associated transaction fees. If the asset is destroyed through governance, the deposit is unreserved and returned to the original sovereign account.

Deposits are network‑specific and can be adjusted by Moonbeam governance via the parameters pallet:

Variable Value
Foreign Asset Deposit 10,000 GLMR
Variable Value
Foreign Asset Deposit 1,000 MOVR
Variable Value
Foreign Asset Deposit 100 DEV

Prerequisites

There are a few prerequisites to be aware of:

Assemble Your Asset Details

Before you register your sibling-parachain token on Moonbeam, you'll need to gather four pieces of information:

  • AssetID: A deterministic u128 derived from the token's multilocation (see below).
  • Decimals: How many decimal places the token uses (for example, 18).
  • Symbol: A short ticker such as xcTEST. The ticker should be prepended with xc.
  • Name: A human-readable name such as Test Token.
const ASSET_ID = 42259045809535163221576417993425387648n;
const DECIMALS = 18n;
const SYMBOL   = "xcTEST";
const NAME     = "Test Token";

How to Calculate Asset ID

To generate a token's asset ID, you'll first need to know its multilocation. assetLocation is a SCALE‑encoded multilocation that pinpoints the existing token on your sibling parachain. There are various ways to define assets and your multilocation may including parachain ID, the pallet that manages assets there, and the local asset index. Because the extrinsic executes on Moonbeam, you describe the path from Moonbeam's perspective: first hop up one level to the Relay ("parents": 1), then down into your parachain (Parachain: <paraId>), the pallet, and the asset index. Moonbeam uses this to verify that the caller actually "contains" the asset before allowing any registration or updates.

Once you've constructed your multilocation, keep it handy, as you'll need it in the next step. A typical asset multilocation looks like this:

{
  "parents": 1,          // Up to Relay
  "interior": {
    "X3": [              // Down to sibling para asset
      { "Parachain": 4 },
      { "PalletInstance": 12 },
      { "GeneralIndex": 15 }  // Arbitrary example values
    ]
  }
}

The XCM tools repo has a helpful Calculate External Asset Info script that you can use to generate the asset ID programmatically. The script takes two parameters, namely, the multilocation of your asset and the target network (Moonbeam or Moonriver). Call the calculate-external-asset-info.ts helper script with your asset's multilocation and target network, as shown below, to easily generate its asset ID.

ts-node scripts/calculate-external-asset-info.ts \
  --asset '{"parents":1,"interior":{"X3":[{"Parachain":4},{"PalletInstance":12},{"GeneralIndex":15}]}}' \
  --network moonbeam

The script will return the assetID you are now ready to pass to evmForeignAssets.createForeignAsset.

Derive the XC-20 Address

Convert assetID to hex, left-pad it to 32 hex chars, and prepend eight Fs as follows:

xc20Address = 0xFFFFFFFF + hex(assetId).padStart(32, '0')

The XC-20 address of xcDOT as an example can be calculated like so:

const xc20Address = `0xFFFFFFFF${hex(assetId).padStart(32, "0")}`;
0xFFFFFFFF1FCACBD218EDC0EBA20FC2308C778080

Generate the Encoded Call Data

The snippet below shows how to build the call that needs to be sent to Moonbeam that creates the foreign asset. Save the resulting hex string because you will embed it inside a subsequent XCM Transact call dispatched from your sibling parachain.

import '@moonbeam-network/api-augment';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { blake2AsHex } from '@polkadot/util-crypto';

const moonbeam = await ApiPromise.create({
  provider: new WsProvider(MOONBEAM_WSS),
});

const tx = moonbeam.tx.evmForeignAssets.createForeignAsset(
  ASSET_ID,
  assetLocation,
  DECIMALS,
  SYMBOL,
  NAME
);

// SCALE-encoded call data (includes call index 0x3800)
const encodedCall = tx.method.toHex();
console.log('Encoded call data:', encodedCall);

// Optional: 32-byte call hash (blake2_256)
console.log('Call hash:', blake2AsHex(encodedCall));

Dispatch the Call with XCM Transact

To register your asset, wrap the SCALE‑encoded createForeignAsset bytes in a single Transact instruction executed from your parachain's sovereign account. The basic structure of the call is outlined below:

Transact {
  originKind: SovereignAccount,
  requireWeightAtMost: <weight>,
  call: <encodedCall>
}

Send the transact instruction via xcmPallet.send, targeting parachain 2004 for Moonbeam (or 2023 for Moonriver).

xcmPallet.send(
  dest: { Parachain: 2004 },
  message: VersionedXcm::V4(INSERT_TRANSACT_INSTRUCTION)
);

Finally, look for the following event emitted successfully on Moonbeam:

EvmForeignAssets.ForeignAssetCreated(assetId, location, creator)

Its presence confirms the XC-20 asset is live.

Managing an Existing Foreign Asset

After a foreign asset has been created, the following extrinsics can be used to update it. Note that in the case of the sovereign account sending a call, the sovereign account and location must still be inside the origin. Otherwise, the only other authorized origin is Root from a Moonbeam governance action.

Extrinsic Who can call? Notes
changeXcmLocation Sibling sovereign account or Moonbeam governance Requires deposit already reserved.
freezeForeignAsset / unfreezeForeignAsset Sibling sovereign account or Moonbeam governance freeze optionally destroys the asset's metadata.

FAQs

How do I reclaim the deposit?

Deposits remain reserved for the life of the asset. If the asset is destroyed through governance, the deposit is unreserved and returned to the original sovereign account.

Can a normal EOA register an asset?

No. Calls from non‑sovereign, non‑governance accounts fail with BadOrigin.

What happens if my XCM location is outside my origin?

The call is rejected with LocationOutsideOfOrigin. Double‑check the Parachain, PalletInstance, and GeneralIndex fields.

Is there a limit to how many assets can be created?

Yes, there is a limit of 256 foreign assets per network (e.g., Moonbeam, Moonriver). Attempts beyond this return TooManyForeignAssets. If this threshold is approached, a revision can be made in a future runtime upgrade to lift this limit.

Last update: June 3, 2025
| Created: May 8, 2025