Skip to content

Send a Transaction While Paying the Fee with a Different Token

Polkadot Hub allows users to pay transaction fees using alternative tokens instead of the native token. This tutorial demonstrates how to send a DOT transfer transaction while paying the fees using USDT on Polkadot Hub.

You can follow this tutorial using Polkadot-API (PAPI), Polkadot.js API, or Subxt. Select your preferred SDK in the code tabs below.

Before starting, ensure you have the following installed:

Fork the Polkadot Hub locally using Chopsticks:

Terminal window
chopsticks -c polkadot-asset-hub

This command forks the Polkadot Hub chain, making it available at ws://localhost:8000. When running polkadot-asset-hub, you use the Polkadot Hub fork with the configuration specified in the polkadot-asset-hub.yml file. This configuration defines Alice's account with USDT assets. If you want to use a different chain, ensure the account you use has the necessary assets.

  1. Create a new directory and initialize the project:

    Terminal window
    mkdir fee-payment-tutorial && cd fee-payment-tutorial
  2. Initialize the project:

    Terminal window
    npm init -y
  3. Install dev dependencies:

    Terminal window
    npm install --save-dev @types/node@^22.12.0 ts-node@^10.9.2 typescript@^5.7.3
  4. Install dependencies:

    Terminal window
    npm install --save @polkadot-labs/hdkd@0.0.32 @polkadot-labs/hdkd-helpers@0.0.34 polkadot-api@3.3.0
  5. Create TypeScript configuration:

    Terminal window
    npx tsc --init && npm pkg set type=module
  6. Generate Polkadot API types for Polkadot Hub:

    Terminal window
    npx papi add assetHub -n polkadot_asset_hub
  7. Create a new file called fee-payment-transaction.ts:

    Terminal window
    touch fee-payment-transaction.ts

The following sections cover how to set up imports and constants, create a transaction signer, connect to Polkadot Hub, and send a DOT transfer transaction while paying fees in USDT.

Set up the required imports and define the target address, transfer amount, and USDT asset ID for your transaction:

fee-payment-transaction.ts
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import {
DEV_PHRASE,
entropyToMiniSecret,
mnemonicToEntropy,
} from "@polkadot-labs/hdkd-helpers";
import { getTxCreator } from "polkadot-api/tx-creator";
import { createClient } from "polkadot-api";
import { assetHub } from "@polkadot-api/descriptors";
import { getWsProvider } from "polkadot-api/ws";
import { MultiAddress } from "@polkadot-api/descriptors";
const TARGET_ADDRESS = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3"; // Bob's address
const TRANSFER_AMOUNT = 3_000_000_000n; // 3 DOT
const USDT_ASSET_ID = 1984;

Create a signer using Alice's development account and connect to the local Polkadot Hub:

fee-payment-transaction.ts
const createSigner = async () => {
const entropy = mnemonicToEntropy(DEV_PHRASE);
const miniSecret = entropyToMiniSecret(entropy);
const derive = sr25519CreateDerive(miniSecret);
const hdkdKeyPair = derive("//Alice");
const polkadotSigner = getTxCreator(
hdkdKeyPair.publicKey,
"Sr25519",
hdkdKeyPair.sign
);
return polkadotSigner;
};
const client = createClient(
getWsProvider("ws://localhost:8000") // Chopsticks Polkadot Hub
);
const api = client.getTypedApi(assetHub);

Create a standard DOT transfer transaction that sends 3 DOT to Bob's address while keeping Alice's account alive:

fee-payment-transaction.ts
const tx = api.tx.Balances.transfer_keep_alive({
dest: MultiAddress.Id(TARGET_ADDRESS),
value: BigInt(TRANSFER_AMOUNT),
});

Sign and Submit with Alternative Fee Payment

Section titled “Sign and Submit with Alternative Fee Payment”

The key part of this tutorial is specifying an alternative asset for fee payment. The USDT asset is identified using the XCM location format, where PalletInstance(50) refers to the Assets pallet and GeneralIndex(1984) identifies the USDT asset on Polkadot Hub:

In PAPI, you specify the alternative asset through the asset parameter in the createAndSubmit options:

fee-payment-transaction.ts
const signer = await createSigner();
const result = await tx.createAndSubmit(signer, {
asset: {
parents: 0,
interior: {
type: "X2",
value: [
{ type: "PalletInstance", value: 50 },
{ type: "GeneralIndex", value: BigInt(USDT_ASSET_ID) },
],
},
},
});
const { txHash, ok, block, events } = result;
console.log(`Tx finalized: ${txHash} (ok=${ok})`);
console.log(`Block: #${block.number} ${block.hash} [tx index ${block.index}]`);
console.log("Events:");
for (const ev of events) {
const type = (ev as any).type ?? "unknown";
console.log(`- ${type}`);
}
process.exit(0);
Complete Code
fee-payment-transaction.ts
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import {
DEV_PHRASE,
entropyToMiniSecret,
mnemonicToEntropy,
} from "@polkadot-labs/hdkd-helpers";
import { getTxCreator } from "polkadot-api/tx-creator";
import { createClient } from "polkadot-api";
import { assetHub } from "@polkadot-api/descriptors";
import { getWsProvider } from "polkadot-api/ws";
import { MultiAddress } from "@polkadot-api/descriptors";
const TARGET_ADDRESS = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3"; // Bob's address
const TRANSFER_AMOUNT = 3_000_000_000n; // 3 DOT
const USDT_ASSET_ID = 1984;
const createSigner = async () => {
const entropy = mnemonicToEntropy(DEV_PHRASE);
const miniSecret = entropyToMiniSecret(entropy);
const derive = sr25519CreateDerive(miniSecret);
const hdkdKeyPair = derive("//Alice");
const polkadotSigner = getTxCreator(
hdkdKeyPair.publicKey,
"Sr25519",
hdkdKeyPair.sign
);
return polkadotSigner;
};
const client = createClient(
getWsProvider("ws://localhost:8000") // Chopsticks Polkadot Hub
);
const api = client.getTypedApi(assetHub);
const tx = api.tx.Balances.transfer_keep_alive({
dest: MultiAddress.Id(TARGET_ADDRESS),
value: BigInt(TRANSFER_AMOUNT),
});
const signer = await createSigner();
const result = await tx.createAndSubmit(signer, {
asset: {
parents: 0,
interior: {
type: "X2",
value: [
{ type: "PalletInstance", value: 50 },
{ type: "GeneralIndex", value: BigInt(USDT_ASSET_ID) },
],
},
},
});
const { txHash, ok, block, events } = result;
console.log(`Tx finalized: ${txHash} (ok=${ok})`);
console.log(`Block: #${block.number} ${block.hash} [tx index ${block.index}]`);
console.log("Events:");
for (const ev of events) {
const type = (ev as any).type ?? "unknown";
console.log(`- ${type}`);
}
process.exit(0);
Terminal window
npx ts-node fee-payment-transaction.ts

When you run the script successfully, you should see output similar to:

npx ts-node fee-payment-transaction.ts
Tx finalized: 0xfe4e3fa64d374e256c72463c507743f16672caaf1b4e539fe913026de394009e (ok=true)
Block: #12255461 0xaf315c306304ad175e4e24c5c8cbf97518c1411183bbf81a6107209a49d84f4d [tx index 2]
Events:
- Assets
- Balances
- Assets
- AssetConversion
- Balances
- System
- Balances
- Balances
- Balances
- AssetTxPayment
- System

The key events to look for are:

  • AssetTxPayment: Confirms the fees were paid using the alternative asset.
  • AssetConversion: The alternative asset was swapped to cover native fees.
  • Balances: The transfer was executed and deposit and withdrawal events were emitted.
  • System: The transaction completed successfully.

Paying transaction fees with alternative tokens on Polkadot Hub provides significant flexibility for users and applications.

The key takeaway is understanding how to specify alternative assets using the XCM location format, which opens up possibilities for building applications that can operate entirely using specific token ecosystems while still leveraging the full power of the network.

  • Send Transactions with SDKs


    Learn how to send various types of transactions using different SDKs.

    → Get Started

  • Calculate Transaction Fees


    Understand how to estimate transaction fees before submitting.

    → Get Started

Last updated: