Skip to content

Commit a0b705a

Browse files
address feedback
1 parent 16ceb8e commit a0b705a

File tree

18 files changed

+371
-258
lines changed

18 files changed

+371
-258
lines changed

.snippets/code/develop/interoperability/versions/v5/teleport-and-transact.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// `ahp` is the name we gave to `npx papi add`
1+
// `ahp` is the name given to `npx papi add`
22
import {
33
ahp,
44
people,
@@ -15,7 +15,7 @@ import {
1515
} from "@polkadot-api/descriptors";
1616
import { Binary, createClient, Enum, FixedSizeBinary } from "polkadot-api";
1717
// import from "polkadot-api/ws-provider/node"
18-
// if you are running in a NodeJS environment
18+
// if running in a NodeJS environment
1919
import { getWsProvider } from "polkadot-api/ws-provider/web";
2020
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
2121
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
@@ -39,12 +39,12 @@ const polkadotSigner = getPolkadotSigner(
3939
);
4040

4141
// Connect to Polkadot Asset Hub.
42-
// Pointing to localhost since we're using chopsticks in this example.
42+
// Pointing to localhost since this example uses chopsticks.
4343
const client = createClient(
4444
withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")),
4545
);
4646

47-
// We get the typed api, a typesafe API for interacting with the chain.
47+
// Get the typed API, a typesafe API for interacting with the chain.
4848
const ahpApi = client.getTypedApi(ahp);
4949

5050
const PEOPLE_PARA_ID = 1004;
@@ -57,12 +57,12 @@ const DOT = {
5757
// DOT has 10 decimals.
5858
const DOT_UNITS = 10_000_000_000n;
5959

60-
// This is the DOT we withdraw to both pay for fees and send.
60+
// The DOT to withdraw for both fees and transfer.
6161
const dotToWithdraw = {
6262
id: DOT,
6363
fun: XcmV3MultiassetFungibility.Fungible(10n * DOT_UNITS),
6464
};
65-
// This is the DOT we use to pay for fees locally.
65+
// The DOT to use for local fee payment.
6666
const dotToPayFees = {
6767
id: DOT,
6868
fun: XcmV3MultiassetFungibility.Fungible(1n * DOT_UNITS),
@@ -72,10 +72,10 @@ const destination = {
7272
parents: 1,
7373
interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(PEOPLE_PARA_ID)),
7474
};
75-
// We'll pay for fees in the People Chain with teleported DOT.
75+
// Pay for fees on the People Chain with teleported DOT.
7676
// This is specified independently of the transferred assets since they're used
77-
// exclusively for fees. Also because we can decide to pay fees in a different
78-
// asset from all that we're transferring.
77+
// exclusively for fees. Also because fees can be paid in a different
78+
// asset from the transferred assets.
7979
const remoteFees = Enum(
8080
"Teleport",
8181
XcmV5AssetFilter.Definite([
@@ -87,9 +87,9 @@ const remoteFees = Enum(
8787
);
8888
// No need to preserve origin for this example.
8989
const preserveOrigin = false;
90-
// The assets we want to transfer are whatever's left in the
90+
// The assets to transfer are whatever remains in the
9191
// holding register at the time of executing the `InitiateTransfer`
92-
// instruction. DOT in this case. We teleport it.
92+
// instruction. DOT in this case, teleported.
9393
const assets = [
9494
Enum("Teleport", XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1))),
9595
];
@@ -99,8 +99,8 @@ const assets = [
9999
const beneficiary = FixedSizeBinary.fromBytes(keyPair.publicKey);
100100
// The call to be executed on the destination chain.
101101
// It's a simple remark with an event.
102-
// We create the call on Asset Hub since the system pallet is present in
103-
// every runtime, but if using any other pallet, you should connect to
102+
// Create the call on Asset Hub since the system pallet is present in
103+
// every runtime, but if using any other pallet, connect to
104104
// the destination chain and create the call there.
105105
const remark = Binary.fromText("Hello, cross-chain!");
106106
const call = await ahpApi.tx.System.remark_with_event({ remark }).getEncodedData();
@@ -127,7 +127,7 @@ const remoteXcm = [
127127
}),
128128
];
129129

130-
// The message is just assembling all of these parameters we've defined before.
130+
// The message assembles all the previously defined parameters.
131131
const xcm = XcmVersionedXcm.V5([
132132
XcmV5Instruction.WithdrawAsset([dotToWithdraw]),
133133
XcmV5Instruction.PayFees({ asset: dotToPayFees }),
@@ -138,9 +138,25 @@ const xcm = XcmVersionedXcm.V5([
138138
assets,
139139
remote_xcm: remoteXcm,
140140
}),
141+
// Return any leftover fees from the fees register back to holding.
142+
XcmV5Instruction.RefundSurplus(),
143+
// Deposit remaining assets (refunded fees) to the originating account.
144+
// Using AllCounted(1) since only one asset type (DOT) remains - a minor optimization.
145+
XcmV5Instruction.DepositAsset({
146+
assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)),
147+
beneficiary: {
148+
parents: 0,
149+
interior: XcmV5Junctions.X1(
150+
XcmV5Junction.AccountId32({
151+
id: beneficiary, // The originating account.
152+
network: undefined,
153+
}),
154+
),
155+
},
156+
}),
141157
]);
142158

143-
// We need to know the weight of the XCM to set the `max_weight` parameter
159+
// The XCM weight is needed to set the `max_weight` parameter
144160
// on the actual `PolkadotXcm.execute()` call.
145161
const weightResult = await ahpApi.apis.XcmPaymentApi.query_xcm_weight(xcm);
146162

@@ -151,14 +167,14 @@ if (weightResult.success) {
151167

152168
console.dir(weight);
153169

154-
// The actual transaction we will submit.
170+
// The actual transaction to submit.
155171
// This tells Asset Hub to execute the XCM.
156172
const tx = ahpApi.tx.PolkadotXcm.execute({
157173
message: xcm,
158174
max_weight: weight,
159175
});
160176

161-
// We sign it and propagate it to the network.
177+
// Sign and propagate to the network.
162178
const result = await tx.signAndSubmit(polkadotSigner);
163179
console.log(stringify(result));
164180
}

.snippets/code/develop/interoperability/versions/v5/teleport.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// `ahp` is the name we gave to `npx papi add`
1+
// `ahp` is the name given to `npx papi add`
22
import {
33
ahp,
44
XcmV3Junction,
@@ -13,7 +13,7 @@ import {
1313
} from "@polkadot-api/descriptors";
1414
import { createClient, Enum, FixedSizeBinary } from "polkadot-api";
1515
// import from "polkadot-api/ws-provider/node"
16-
// if you are running in a NodeJS environment
16+
// if running in a NodeJS environment
1717
import { getWsProvider } from "polkadot-api/ws-provider/web";
1818
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
1919
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
@@ -36,12 +36,12 @@ const polkadotSigner = getPolkadotSigner(
3636
);
3737

3838
// Connect to Polkadot Asset Hub.
39-
// Pointing to localhost since we're using chopsticks in this example.
39+
// Pointing to localhost since this example uses chopsticks.
4040
const client = createClient(
4141
withPolkadotSdkCompat(getWsProvider("ws://localhost:8000")),
4242
);
4343

44-
// We get the typed api, a typesafe API for interacting with the chain.
44+
// Get the typed API, a typesafe API for interacting with the chain.
4545
const ahpApi = client.getTypedApi(ahp);
4646

4747
const PEOPLE_PARA_ID = 1004;
@@ -54,12 +54,12 @@ const DOT = {
5454
// DOT has 10 decimals.
5555
const DOT_UNITS = 10_000_000_000n;
5656

57-
// This is the DOT we withdraw to both pay for fees and send.
57+
// The DOT to withdraw for both fees and transfer.
5858
const dotToWithdraw = {
5959
id: DOT,
6060
fun: XcmV3MultiassetFungibility.Fungible(10n * DOT_UNITS),
6161
};
62-
// This is the DOT we use to pay for fees locally.
62+
// The DOT to use for local fee payment.
6363
const dotToPayFees = {
6464
id: DOT,
6565
fun: XcmV3MultiassetFungibility.Fungible(1n * DOT_UNITS),
@@ -69,10 +69,10 @@ const destination = {
6969
parents: 1,
7070
interior: XcmV3Junctions.X1(XcmV3Junction.Parachain(PEOPLE_PARA_ID)),
7171
};
72-
// We'll pay for fees in the People Chain with teleported DOT.
72+
// Pay for fees on the People Chain with teleported DOT.
7373
// This is specified independently of the transferred assets since they're used
74-
// exclusively for fees. Also because we can decide to pay fees in a different
75-
// asset from all that we're transferring.
74+
// exclusively for fees. Also because fees can be paid in a different
75+
// asset from the transferred assets.
7676
const remoteFees = Enum(
7777
"Teleport",
7878
XcmV5AssetFilter.Definite([
@@ -84,9 +84,9 @@ const remoteFees = Enum(
8484
);
8585
// No need to preserve origin for this example.
8686
const preserveOrigin = false;
87-
// The assets we want to transfer are whatever's left in the
87+
// The assets to transfer are whatever remains in the
8888
// holding register at the time of executing the `InitiateTransfer`
89-
// instruction. DOT in this case. We teleport it.
89+
// instruction. DOT in this case, teleported.
9090
const assets = [
9191
Enum("Teleport", XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1))),
9292
];
@@ -111,7 +111,7 @@ const remoteXcm = [
111111
}),
112112
];
113113

114-
// The message is just assembling all of these parameters we've defined before.
114+
// The message assembles all the previously defined parameters.
115115
const xcm = XcmVersionedXcm.V5([
116116
XcmV5Instruction.WithdrawAsset([dotToWithdraw]),
117117
XcmV5Instruction.PayFees({ asset: dotToPayFees }),
@@ -122,9 +122,25 @@ const xcm = XcmVersionedXcm.V5([
122122
assets,
123123
remote_xcm: remoteXcm,
124124
}),
125+
// Return any leftover fees from the fees register back to holding.
126+
XcmV5Instruction.RefundSurplus(),
127+
// Deposit remaining assets (refunded fees) to the originating account.
128+
// Using AllCounted(1) since only one asset type (DOT) remains - a minor optimization.
129+
XcmV5Instruction.DepositAsset({
130+
assets: XcmV5AssetFilter.Wild(XcmV5WildAsset.AllCounted(1)),
131+
beneficiary: {
132+
parents: 0,
133+
interior: XcmV5Junctions.X1(
134+
XcmV5Junction.AccountId32({
135+
id: beneficiary, // The originating account.
136+
network: undefined,
137+
}),
138+
),
139+
},
140+
}),
125141
]);
126142

127-
// We need to know the weight of the XCM to set the `max_weight` parameter
143+
// The XCM weight is needed to set the `max_weight` parameter
128144
// on the actual `PolkadotXcm.execute()` call.
129145
const weightResult = await ahpApi.apis.XcmPaymentApi.query_xcm_weight(xcm);
130146

@@ -135,14 +151,14 @@ if (weightResult.success) {
135151

136152
console.dir(weight);
137153

138-
// The actual transaction we will submit.
154+
// The actual transaction to submit.
139155
// This tells Asset Hub to execute the XCM.
140156
const tx = ahpApi.tx.PolkadotXcm.execute({
141157
message: xcm,
142158
max_weight: weight,
143159
});
144160

145-
// We sign it and propagate it to the network.
161+
// Sign and propagate to the network.
146162
const result = await tx.signAndSubmit(polkadotSigner);
147163
console.log(stringify(result));
148164
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
title: Fundamentals
2+
nav:
3+
- index.md
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Fundamentals
2+
3+
This section covers the fundamentals of XCM.

develop/interoperability/guides/from-apps/papi/claiming-trapped-assets.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ Assets become trapped whenever execution halts and there are leftover assets.
1515
This can happen for example if:
1616

1717
- 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...
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+
- and others...
2222
- 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
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
2626

2727
## The ClaimAsset instruction
2828

29-
The `ClaimAsset` instruction allows retriving assets trapped on a chain:
29+
The [`ClaimAsset`](https://paritytech.github.io/polkadot-sdk/master/xcm/v5/instruction/enum.Instruction.html#variant.ClaimAsset){target=\_blank} instruction allows retrieving assets trapped on a chain:
3030

3131
```typescript
3232
XcmV5Instruction.ClaimAsset({
@@ -48,7 +48,7 @@ in the `AssetsTrapped` event.
4848

4949
When assets are trapped you'll see the `AssetsTrapped` event:
5050

51-
![AssetsTrapped event on Subscan](../../../images/develop/interoperability/assets-trapped-event.png)
51+
![AssetsTrapped event on Subscan](/images/develop/interoperability/assets-trapped-event.png)
5252

5353
To claim these assets, a message like the following needs to be sent from the origin:
5454

@@ -75,6 +75,8 @@ const claimAssetsXcm = XcmVersionedXcm.V5([
7575
]);
7676
```
7777

78+
Note that this example uses the claimed USDC assets to pay for the execution fees of the claiming message. If the trapped asset cannot be used for fee payment on the destination chain, you need a different approach: first `WithdrawAsset` (with fee-eligible assets), then `PayFees`, then `ClaimAsset`, and finally `DepositAsset`.
79+
7880
In this case, the origin is a local account so the `execute()` transaction needs to be submitted by that same account.
7981
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,
8082
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.
@@ -131,7 +133,7 @@ const failingXcm = XcmVersionedXcm.V5([
131133
fun: XcmV3MultiassetFungibility.Fungible(1_000_000_000n),
132134
},
133135
}),
134-
// Explicitly trap. We could also not do anything and the assets would still get trapped.
136+
// Explicitly trap. Alternatively, doing nothing would still result in the assets getting trapped.
135137
XcmV5Instruction.Trap(0n),
136138
]);
137139
```

develop/interoperability/guides/from-apps/papi/fees.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ description: How to handle fees in XCM.
88
In blockchain systems, fees are crucial.
99
They prevent malicious actors from exhausting the results of the network, making such attacks expensive.
1010
The XCM subsystem has its own way of dealing with fees, flexible enough to allow feeless execution in situations that warrant it.
11+
12+
It's important to distinguish between **transaction fees** and **XCM fees**. Transaction fees are paid when submitting extrinsics to a blockchain. XCM fees, on the other hand, are charged for processing XCM instructions and consist of execution fees (computational costs) and delivery fees (message transport costs). While a transaction can include XCM fees, as happens with `palletXcm.execute()`, they are separate fee systems. When a chain receives and processes an XCM message, it charges XCM fees but no transaction fees, since no extrinsic is being submitted.
13+
1114
There are two main types of fees in XCM: **execution** and **delivery**.
1215

1316
## Execution
@@ -38,7 +41,7 @@ The amount is specified using the `PayFees` instruction:
3841
XcmV5Instruction.PayFees({
3942
asset: {
4043
id: // Asset id.
41-
fun: // Fungibility. You specify the amount if it's fungible or the instance if it's an NFT.
44+
fun: // Fungibility. Specify the amount if fungible or the instance if NFT.
4245
},
4346
})
4447
```
@@ -47,13 +50,19 @@ This mechanism is simple and flexible.
4750
The user requires no knowledge of the different types of fees.
4851
New fees might arise in the future and they'll be taken using this same mechanism, without the need for any modification.
4952

53+
Which assets can be used for fee payment depends on the destination chain's configuration. For example, on Asset Hub, fees can be paid with any asset that has a liquidity pool with DOT, allowing the chain to automatically convert the fee asset to DOT for actual fee payment. Other chains may have different fee payment policies, so it's important to understand the specific requirements of the destination chain before selecting fee assets.
54+
55+
??? note "Sufficient assets vs fee payment assets"
56+
57+
It's important to distinguish between "sufficient" assets and assets eligible for fee payment. Sufficient assets can be used to satisfy the Existential Deposit requirement for account creation and maintenance, but this doesn't automatically make them eligible for fee payment. While sufficient assets are generally also usable for fee payment, this isn't guaranteed and depends on the chain's specific configuration. The terms are related but serve different purposes in system.
58+
5059
## Estimations
5160

5261
The entirety of the asset passed to `PayFees` will be taken from the effective assets and used only for fees.
5362
This means if you overestimate the fees required, you'll be losing efficiency.
5463

5564
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.
65+
This is more complicated than it sounds since the process involves execution and delivery fees, potentially in multiple hops.
5766

5867
Imagine a scenario where parachain A sends a message to B which forwards another message to C.
5968

@@ -90,8 +99,7 @@ const xcm = XcmVersionedXcm.V5([
9099
])
91100
```
92101

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`.
102+
Paying fees on a remote system is so common that the `InitiateTransfer` instruction provides the `remote_fees` parameter for this purpose. When `remote_fees` is specified, it automatically generates a `PayFees` instruction on the destination chain using the specified fees, eliminating the need to manually add `PayFees` to the `remote_xcm` parameter.
95103

96104
<!-- TODO: Fee estimation tutorial? -->
97-
The solution is to use the [runtime APIs](/develop/interoperability/xcm-runtime-apis/) as shown in [the fee estimation tutorial]().
105+
The solution is to use the [runtime APIs](/develop/interoperability/xcm-runtime-apis/) as shown in [the fee estimation tutorial]().

develop/interoperability/guides/from-apps/papi/transact.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ XcmV5Instruction.Transact({
2525

2626
- **`origin_kind`**: Specifies how the origin should be interpreted on the destination chain:
2727

28-
- `SovereignAccount`: Execute as the sovereign account of the origin
29-
- `Superuser`: Execute with root privileges (requires special configuration)
30-
- `Xcm`: Execute as a generic XCM origin
28+
- [`SovereignAccount`](https://paritytech.github.io/polkadot-sdk/master/xcm/v2/enum.OriginKind.html#variant.SovereignAccount){target=\_blank}: Execute as the sovereign account of the origin
29+
- [`Superuser`](https://paritytech.github.io/polkadot-sdk/master/xcm/v2/enum.OriginKind.html#variant.Superuser){target=\_blank}: Execute with root privileges (requires special configuration)
30+
- [`Xcm`](https://paritytech.github.io/polkadot-sdk/master/xcm/v2/enum.OriginKind.html#variant.Xcm){target=\_blank}: Execute as a generic XCM origin
3131

3232
- **`fallback_max_weight`**: Optional weight limit for execution:
3333

@@ -38,7 +38,7 @@ XcmV5Instruction.Transact({
3838

3939
Unlike other XCM instructions like `DepositAsset` or `WithdrawAsset` which are generic, `Transact` requires detailed knowledge of the destination chain:
4040

41-
### What you need to know
41+
### Required Knowledge
4242

4343
1. **Runtime metadata**: The specific pallets, calls, and their parameters available on the destination chain
4444
2. **Call encoding**: How to properly encode the call data for the destination runtime

0 commit comments

Comments
 (0)