Skip to content

Commit d0c1cf4

Browse files
xcm: add v5 changes and guides for v5 with papi
1 parent 2539cbc commit d0c1cf4

File tree

19 files changed

+2116
-836
lines changed

19 files changed

+2116
-836
lines changed

develop/interoperability/.pages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ title: Interoperability
22
nav:
33
- index.md
44
- 'Introduction to XCM': intro-to-xcm.md
5+
- guides
56
- 'XCM Channels': xcm-channels.md
67
- 'XCM Configuration': xcm-config.md
78
- 'Send Messages': send-messages.md
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
title: Guides
2+
nav:
3+
- index.md
4+
- from-apps
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
title: From apps
2+
nav:
3+
- index.md
4+
- papi
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# From apps
2+
3+
This section shows how to interact with XCM from apps.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
title: PAPI
2+
nav:
3+
- index.md
4+
- transfers.md
5+
- transact.md
6+
- claiming-trapped-assets.md
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
title: Claiming trapped assets
3+
description: How to claim assets that become trapped during failed XCM execution.
4+
---
5+
6+
# Claiming trapped assets
7+
8+
When XCM execution fails or succeeds, leftover assets become "trapped" on the destination chain.
9+
These assets are held by the system but not accessible through normal means.
10+
XCM provides mechanisms to claim these trapped assets and recover them.
11+
12+
## What causes trapped assets
13+
14+
Assets become trapped whenever execution halts and there are leftover assets.
15+
This can happen for example if:
16+
17+
- An XCM execution throws an error in any instruction when assets are in holding
18+
- `DepositAsset` can't deposit because the account doesn't exist
19+
- `Transact` can't execute the call because it doesn't exist
20+
- `PayFees` not enough funds or not paying enough for execution
21+
- or others...
22+
- XCM execution finishes successfully but not all assets were deposited
23+
- Funds were withdrawn but some were not deposited
24+
- `Transact` overestimated the weight and `RefundSurplus` got some funds into holding that were never deposited
25+
- Fees in `PayFees` were overestimated and some were kept there until the end
26+
27+
## The ClaimAsset instruction
28+
29+
The `ClaimAsset` instruction allows retriving assets trapped on a chain:
30+
31+
```typescript
32+
XcmV5Instruction.ClaimAsset({
33+
assets: /* Exact assets to claim, these must match those in the `AssetsTrapped` event */,
34+
ticket: /* Additional information about the trapped assets, e.g. the XCM version that was in use at the time */
35+
});
36+
```
37+
38+
### Parameters
39+
40+
- **`assets`**: The trapped assets that want to be claimed. These must be exactly the same as the ones that appear
41+
in the `AssetsTrapped` event.
42+
43+
- **`ticket`**: Additional information about the trapped assets.
44+
- Currently only specifies the XCM version used when the assets got trapped.
45+
Must be of the form `{ parents: 0, interior: XcmV5Junctions.X1(XcmV5Junction.GeneralIndex(<xcm-version-here>)) }`.
46+
47+
### Basic claiming process
48+
49+
When assets are trapped you'll see the `AssetsTrapped` event:
50+
51+
![AssetsTrapped event on Subscan](../../../images/develop/interoperability/assets-trapped-event.png)
52+
53+
To claim these assets, a message like the following needs to be sent from the origin:
54+
55+
```typescript
56+
const claimAssetsXcm = XcmVersionedXcm.V5([
57+
// Claim trapped DOT.
58+
XcmV5Instruction.ClaimAsset({
59+
assets: [{
60+
// USDC.
61+
id: {
62+
parents: 0,
63+
interior: XcmV5Junctions.X2([
64+
XcmV5Junction.PalletInstance(50),
65+
XcmV5Junction.GeneralIndex(1337n),
66+
]),
67+
},
68+
fun: XcmV3MultiassetFungibility.Fungible(49_334n) // 0.049334 units.
69+
}],
70+
// Version 5.
71+
ticket: { parents: 0, interior: XcmV5Junctions.X1(XcmV5Junction.GeneralIndex(5n)) }
72+
}),
73+
XcmV5Instruction.PayFees(/* Pay for fees */),
74+
XcmV5Instruction.DepositAsset(/* Deposit everything to an account */),
75+
]);
76+
```
77+
78+
In this case, the origin is a local account so the `execute()` transaction needs to be submitted by that same account.
79+
The origin could be another chain, in which case the governance of that chain would need to get involved, or an account on another chain,
80+
in which case the `execute()` transaction would need to be submitted on that other chain and a message sent to the chain with trapped funds.
81+
82+
Multiple assets can be claimed with the same message. This is useful when governance needs to get involved.
83+
84+
```typescript
85+
const claimAssetsXcm = XcmVersionedXcm.V5([
86+
// Claim trapped DOT.
87+
XcmV5Instruction.ClaimAsset(/* ... */),
88+
XcmV5Instruction.PayFees(/* Pay for fees */),
89+
XcmV5Instruction.ClaimAsset(/* ... */),
90+
XcmV5Instruction.ClaimAsset(/* ... */),
91+
XcmV5Instruction.ClaimAsset(/* ... */),
92+
XcmV5Instruction.DepositAsset(/* Deposit everything to an account */),
93+
]);
94+
```
95+
96+
## The AssetClaimer hint
97+
98+
The `AssetClaimer` execution hint allows setting a specific location that can claim trapped assets, making the claiming process easier.
99+
This is set after withdrawing assets and before anything else.
100+
101+
```typescript
102+
const failingXcm = XcmVersionedXcm.V5([
103+
// Withdraw 1 DOT (10 decimals).
104+
XcmV5Instruction.WithdrawAsset([
105+
{
106+
id: { parents: 1, interior: XcmV5Junctions.Here() },
107+
fun: XcmV3MultiassetFungibility.Fungible(10_000_000_000n),
108+
},
109+
]),
110+
// Set the asset claimer.
111+
XcmV5Instruction.SetHints({
112+
hints: [
113+
Enum(
114+
'AssetClaimer',
115+
{
116+
location: {
117+
parents: 0,
118+
interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({
119+
id: FixedSizeBinary.fromAccountId32(SS58_ACCOUNT),
120+
network: undefined,
121+
})),
122+
}
123+
}
124+
)
125+
]
126+
}),
127+
// Pay fees.
128+
XcmV5Instruction.PayFees({
129+
asset: {
130+
id: { parents: 1, interior: XcmV5Junctions.Here() },
131+
fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n),
132+
},
133+
}),
134+
// Explicitly trap. We could also not do anything and the assets would still get trapped.
135+
XcmV5Instruction.Trap(0n),
136+
]);
137+
```
138+
139+
This allows this other `SS58_ACCOUNT` to claim the trapped assets.
140+
This could also be done before a transfer.
141+
142+
??? code "Teleport with custom asset claimer example"
143+
144+
```typescript
145+
const setAssetClaimerRemotely = XcmVersionedXcm.V5([
146+
// Withdraw 1 DOT (10 decimals).
147+
XcmV5Instruction.WithdrawAsset([
148+
{
149+
id: { parents: 1, interior: XcmV5Junctions.Here() },
150+
fun: XcmV3MultiassetFungibility.Fungible(10_000_000_000n),
151+
},
152+
]),
153+
// Pay fees.
154+
XcmV5Instruction.PayFees({
155+
asset: {
156+
id: { parents: 1, interior: XcmV5Junctions.Here() },
157+
fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n),
158+
},
159+
}),
160+
// Cross-chain transfer.
161+
XcmV5Instruction.InitiateTransfer({
162+
destination: { parents: 1, interior: XcmV5Junctions.X1(XcmV5Junction.Parachain(1000)) },
163+
remote_fees: Enum(
164+
'Teleport',
165+
XcmV5AssetFilter.Definite([
166+
{
167+
id: { parents: 1, interior: XcmV5Junctions.Here() },
168+
fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n),
169+
},
170+
])
171+
),
172+
preserve_origin: false,
173+
assets: [
174+
Enum(
175+
'Teleport',
176+
XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1))
177+
),
178+
],
179+
remote_xcm: [
180+
// Set the asset claimer on the destination chain.
181+
// If any asset gets trapped, this account will be able to claim them.
182+
XcmV5Instruction.SetHints({
183+
hints: [
184+
Enum(
185+
'AssetClaimer',
186+
{
187+
location: {
188+
parents: 0,
189+
interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({
190+
id: FixedSizeBinary.fromAccountId32(SS58_ACCOUNT),
191+
network: undefined,
192+
})),
193+
}
194+
}
195+
)
196+
]
197+
}),
198+
XcmV5Instruction.DepositAsset({
199+
assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)),
200+
beneficiary: {
201+
parents: 1, interior: XcmV5Junctions.X1(XcmV5Junction.AccountId32({
202+
id: FixedSizeBinary.fromAccountId32(SS58_ACCOUNT),
203+
network: undefined,
204+
})),
205+
}
206+
}),
207+
],
208+
}),
209+
]);
210+
```
211+
212+
## Best practices
213+
214+
1. **Always set a claimer**: Include `SetAssetClaimer` in XCMs with valuable assets
215+
2. **Use accessible locations**: Ensure the claimer location is controlled by someone who can act
216+
3. **Monitor for failures**: Track XCM execution to detect when claiming is needed
217+
4. **Test claiming flows**: Verify your claiming logic works in test environments
218+
5. **Document recovery procedures**: Maintain clear instructions for asset recovery
219+
220+
Setting a custom asset claimer is a good practice for recovering trapped assets without the need for governance intervention.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
title: Fees
3+
description: How to handle fees in XCM.
4+
---
5+
6+
# Fees
7+
8+
In blockchain systems, fees are crucial.
9+
They prevent malicious actors from exhausting the results of the network, making such attacks expensive.
10+
The XCM subsystem has its own way of dealing with fees, flexible enough to allow feeless execution in situations that warrant it.
11+
There are two main types of fees in XCM: **execution** and **delivery**.
12+
13+
## Execution
14+
15+
All XCMs have a weight associated to them.
16+
Each XCM instruction is benchmarked for a particular system (blockchain), which assigns them a weight.
17+
The weight of an XCM is the sum of the weight of all instructions.
18+
It's important to correctly benchmark this with the worst case so that your system is safe from Denial-of-Service attacks.
19+
20+
This generated weight represents how much time, and space, is needed for executing the XCM.
21+
It directly translates to **execution fees**.
22+
23+
## Delivery
24+
25+
XCMs, although capable of performing tasks locally, are meant to be sent to other consensus systems, i.e. blockchains.
26+
**Delivery fees** are charged when a message is sent to a destination.
27+
The delivery fee amount depends on the size of the message, in bytes, and the destination.
28+
29+
## PayFees
30+
31+
In order to keep things simpler, these two fees are dealt in the same way.
32+
The user is asked to input the maximum amount they want to dedicate for fees.
33+
This amount is used **only** for fees.
34+
35+
The amount is specified using the `PayFees` instruction:
36+
37+
```typescript
38+
XcmV5Instruction.PayFees({
39+
asset: {
40+
id: // Asset id.
41+
fun: // Fungibility. You specify the amount if it's fungible or the instance if it's an NFT.
42+
},
43+
})
44+
```
45+
46+
This mechanism is simple and flexible.
47+
The user requires no knowledge of the different types of fees.
48+
New fees might arise in the future and they'll be taken using this same mechanism, without the need for any modification.
49+
50+
## Estimations
51+
52+
The entirety of the asset passed to `PayFees` will be taken from the effective assets and used only for fees.
53+
This means if you overestimate the fees required, you'll be losing efficiency.
54+
55+
It's necessary to have a mechanism to accurately estimate the fee needed so it can be put into `PayFees`.
56+
This is more complicated than it sounds since we're dealing with execution and delivery fees, potentially in multiple hops.
57+
58+
Imagine a scenario where parachain A sends a message to B which forwards another message to C.
59+
60+
``` mermaid
61+
flowchart LR
62+
A(A) --> B(B)
63+
B --> C(C)
64+
```
65+
66+
Execution fees need to be paid on A.
67+
Delivery fees from A to B.
68+
Execution on B.
69+
Delivery from B to C.
70+
Finally, execution on C.
71+
72+
An XCM that does this might look like so.
73+
74+
```typescript
75+
const xcm = XcmVersionedXcm.V5([
76+
XcmV5Instruction.WithdrawAsset(/* some assets */),
77+
XcmV5Instruction.PayFees(/* execution + delivery on A */),
78+
XcmV5Instruction.InitiateTransfer({
79+
// ...
80+
remote_fees: /* execution + delivery on B */,
81+
remote_xcm: [
82+
XcmV5Instruction.InitiateTransfer({
83+
// ...
84+
remote_fees: /* execution on C */,
85+
// ...
86+
}),
87+
],
88+
// ...
89+
}),
90+
])
91+
```
92+
93+
NOTE: paying fees on a remote system is so common that the `InitiateTransfer` instruction doesn't
94+
require putting the instruction in `remote_xcm`, you only need to put them in `remote_fees`.
95+
96+
<!-- TODO: Fee estimation tutorial? -->
97+
The solution is to use the [runtime APIs](/develop/interoperability/xcm-runtime-apis/) to estimate fees.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# PAPI
2+
3+
PAPI is a type-safe typescript library for interacting with Polkadot SDK chains.
4+
This section shows how to use it specifically for doing cross-chain operations.

0 commit comments

Comments
 (0)