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:
- The sibling parachain's sovereign account on Moonbeam must be sufficiently funded to cover the asset deposit and the transaction fees. It's recommended that you have an extra buffer of additional funds for any subsequent transactions. See this guide to calculating a sovereign account
- Your parachain should support XCM V4
- Your parachain needs bidirectional XCM channels with Moonbeam. See this guide for information on opening XCM channels with Moonbeam
Assemble Your Asset Details¶
Before you register your sibling-parachain token on Moonbeam, you'll need to gather four pieces of information:
AssetID
: A deterministicu128
derived from the token'smultilocation
(see below).Decimals
: How many decimal places the token uses (for example,18
).Symbol
: A short ticker such asxcTEST
. The ticker should be prepended withxc
.Name
: A human-readable name such asTest 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 F
s 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.
| Created: May 8, 2025