Skip to content

Send a Transaction While Paying the Fee with a Different TokenΒΆ

IntroductionΒΆ

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.

Polkadot.js API Maintenance Mode

The Polkadot.js API is no longer actively developed. New projects should use Polkadot-API (PAPI) or Dedot as actively maintained alternatives.

PrerequisitesΒΆ

Before starting, ensure you have the following installed:

Local Polkadot Hub SetupΒΆ

Fork the Polkadot Hub locally using Chopsticks:

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.

Set Up Your ProjectΒΆ

  1. Create a new directory and initialize the project:

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

    npm init -y
    
  3. Install dev dependencies:

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

    npm install --save @polkadot-labs/hdkd@0.0.26 @polkadot-labs/hdkd-helpers@0.0.27 polkadot-api@1.23.3
    
  5. Create TypeScript configuration:

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

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

    touch fee-payment-transaction.ts
    
  1. Create a new directory and initialize the project:

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

    npm init -y && npm pkg set type=module
    
  3. Install dependencies:

    npm install @polkadot/api@16.5.4
    
  4. Create a new file called fee-payment-transaction.js:

    touch fee-payment-transaction.js
    
  1. Create a new Rust project:

    cargo new subxt-fee-payment-example && cd subxt-fee-payment-example
    
  2. Install the Subxt CLI to download chain metadata:

    cargo install subxt-cli@0.44.2
    
  3. Download Polkadot Hub metadata from the local Chopsticks fork:

    mkdir metadata && \
    subxt metadata --url ws://localhost:8000 -o metadata/asset_hub.scale
    

    Note

    Ensure your Chopsticks fork is running at ws://localhost:8000 before downloading the metadata.

  4. Update Cargo.toml with the required dependencies:

    Cargo.toml
    [package]
    name = "subxt-fee-payment-example"
    version = "0.1.0"
    edition = "2021"
    
    [[bin]]
    name = "fee_payment_transaction"
    path = "src/bin/fee_payment_transaction.rs"
    
    [dependencies]
    codec = { package = "parity-scale-codec", version = "3", features = ["derive"] }
    subxt = { version = "0.44.2", features = ["jsonrpsee", "native"] }
    subxt-signer = { version = "0.44.2", features = ["sr25519"] }
    tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
    
  5. Create the source file:

    mkdir -p src/bin && touch src/bin/fee_payment_transaction.rs
    

ImplementationΒΆ

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.

Import Dependencies and Define ConstantsΒΆ

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 { getPolkadotSigner } from "polkadot-api/signer";
import { createClient } from "polkadot-api";
import { assetHub } from "@polkadot-api/descriptors";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/node";
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;
fee-payment-transaction.js
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';

const TARGET_ADDRESS = '14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3'; // Bob's address
const TRANSFER_AMOUNT = 3_000_000_000n; // 3 DOT
const USDT_ASSET_ID = 1984;

In Subxt, you first generate types from the chain metadata using the #[subxt::subxt()] macro. The derive_for_type attribute ensures the Location type implements the traits needed for encoding. You also define a custom AssetHubConfig where type AssetId = Location, which enables specifying an XCM location as the fee payment asset:

src/bin/fee_payment_transaction.rs
use std::str::FromStr;
use subxt::config::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig};
use subxt::utils::AccountId32;
use subxt::{OnlineClient, SubstrateConfig};

// Generate types from the Polkadot Hub metadata with Location trait derives
#[subxt::subxt(
    runtime_metadata_path = "metadata/asset_hub.scale",
    derive_for_type(
        path = "staging_xcm::v5::location::Location",
        derive = "Clone, Eq, PartialEq, codec::Encode",
        recursive
    )
)]
pub mod asset_hub {}

// Import XCM location types from the generated metadata module
use asset_hub::runtime_types::staging_xcm::v5::{
    junction::Junction,
    junctions::Junctions,
    location::Location,
};

// Define a custom config where AssetId is an XCM Location
pub enum AssetHubConfig {}

impl Config for AssetHubConfig {
    type AccountId = <PolkadotConfig as Config>::AccountId;
    type Address = <PolkadotConfig as Config>::Address;
    type Signature = <PolkadotConfig as Config>::Signature;
    type Hasher = <PolkadotConfig as Config>::Hasher;
    type Header = <SubstrateConfig as Config>::Header;
    type ExtrinsicParams = DefaultExtrinsicParams<AssetHubConfig>;
    type AssetId = Location;
}

const POLKADOT_HUB_RPC: &str = "ws://localhost:8000";
const TARGET_ADDRESS: &str = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3";
const TRANSFER_AMOUNT: u128 = 3_000_000_000; // 3 DOT
const USDT_ASSET_ID: u128 = 1984;

Create a Signer and ConnectΒΆ

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 = getPolkadotSigner(
    hdkdKeyPair.publicKey,
    "Sr25519",
    hdkdKeyPair.sign
  );
  return polkadotSigner;
};

const client = createClient(
  withPolkadotSdkCompat(
    getWsProvider("ws://localhost:8000") // Chopsticks Polkadot Hub
  )
);

const api = client.getTypedApi(assetHub);

The cryptoWaitReady() call ensures the underlying WASM cryptographic libraries are initialized before creating the keyring:

fee-payment-transaction.js
async function main() {
  // Wait for crypto libraries to be ready
  await cryptoWaitReady();

  // Create a keyring instance and add Alice's account
  const keyring = new Keyring({ type: 'sr25519' });
  const alice = keyring.addFromUri('//Alice');

  // Connect to the local Chopsticks Polkadot Hub fork
  const wsProvider = new WsProvider('ws://localhost:8000');
  const api = await ApiPromise.create({ provider: wsProvider });
  console.log('Connected to Polkadot Hub (Chopsticks fork)');

Notice that the OnlineClient is parameterized with AssetHubConfig instead of the default PolkadotConfig:

src/bin/fee_payment_transaction.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to the local Chopsticks Polkadot Hub fork
    let api = OnlineClient::<AssetHubConfig>::from_url(POLKADOT_HUB_RPC).await?;
    println!("Connected to Polkadot Hub (Chopsticks fork)");

    // Create Alice's dev keypair
    let alice = subxt_signer::sr25519::dev::alice();
    println!("Sender (Alice): {}", AccountId32::from(alice.public_key()));

Create the TransactionΒΆ

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),
});
fee-payment-transaction.js
// Create the transfer transaction
const tx = api.tx.balances.transferKeepAlive(TARGET_ADDRESS, TRANSFER_AMOUNT);
src/bin/fee_payment_transaction.rs
// Create the balance transfer transaction
let dest = AccountId32::from_str(TARGET_ADDRESS)?;
let tx = asset_hub::tx()
    .balances()
    .transfer_keep_alive(dest.into(), TRANSFER_AMOUNT);

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 signAndSubmit options:

fee-payment-transaction.ts
const signer = await createSigner();

const result = await tx.signAndSubmit(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);

In Polkadot.js, you define the asset as an XCM multi-location object and pass it as the assetId option to signAndSend:

fee-payment-transaction.js
// Define the USDT asset as an XCM multi-location for fee payment
const assetId = {
  parents: 0,
  interior: {
    X2: [{ PalletInstance: 50 }, { GeneralIndex: USDT_ASSET_ID }],
  },
};

// Sign and send the transaction, paying fees with USDT
console.log('Signing and submitting transaction...');
await new Promise((resolve, reject) => {
  let unsubscribe;
  tx
    .signAndSend(
      alice,
      { assetId },
      ({ status, events, txHash, dispatchError }) => {
        if (status.isFinalized) {
          console.log(
            `\nTransaction finalized in block: ${status.asFinalized.toHex()}`
          );
          console.log(`Transaction hash: ${txHash.toHex()}`);

          // Display all events
          console.log('\nEvents:');
          events.forEach(({ event }) => {
            console.log(`  ${event.section}.${event.method}`);
          });

          if (unsubscribe) {
            unsubscribe();
          }

          if (dispatchError) {
            if (dispatchError.isModule) {
              const decoded = api.registry.findMetaError(
                dispatchError.asModule
              );
              const { docs, name, section } = decoded;
              reject(new Error(`${section}.${name}: ${docs.join(' ')}`));
            } else {
              reject(new Error(dispatchError.toString()));
            }
          } else {
            resolve();
          }
        }
      }
    )
    .then((unsub) => {
      unsubscribe = unsub;
    })
    .catch((error) => {
      if (unsubscribe) {
        unsubscribe();
      }
      reject(error);
    });
});

In Subxt, you use DefaultExtrinsicParamsBuilder with tip_of(0, asset_location) to specify the fee asset. The first argument is the tip amount (0), and the second is the XCM Location. Instead of calling sign_and_submit_then_watch_default, you pass the custom tx_params to sign_and_submit_then_watch:

src/bin/fee_payment_transaction.rs
// Define the USDT asset location in XCM format
let asset_location = Location {
    parents: 0,
    interior: Junctions::X2([
        Junction::PalletInstance(50),
        Junction::GeneralIndex(USDT_ASSET_ID),
    ]),
};

// Build transaction params to pay fees with the alternative asset
let tx_params = DefaultExtrinsicParamsBuilder::<AssetHubConfig>::new()
    .tip_of(0, asset_location)
    .build();

// Sign, submit, and watch for finalization
println!("Signing and submitting transaction...");
let progress = api
    .tx()
    .sign_and_submit_then_watch(&tx, &alice, tx_params)
    .await?;

let in_block = progress.wait_for_finalized().await?;
let block_hash = in_block.block_hash();
let events = in_block.wait_for_success().await?;

Full CodeΒΆ

Complete Code
fee-payment-transaction.ts
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import {
  DEV_PHRASE,
  entropyToMiniSecret,
  mnemonicToEntropy,
} from "@polkadot-labs/hdkd-helpers";
import { getPolkadotSigner } from "polkadot-api/signer";
import { createClient } from "polkadot-api";
import { assetHub } from "@polkadot-api/descriptors";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/node";
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 = getPolkadotSigner(
    hdkdKeyPair.publicKey,
    "Sr25519",
    hdkdKeyPair.sign
  );
  return polkadotSigner;
};

const client = createClient(
  withPolkadotSdkCompat(
    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.signAndSubmit(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.js
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';

const TARGET_ADDRESS = '14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3'; // Bob's address
const TRANSFER_AMOUNT = 3_000_000_000n; // 3 DOT
const USDT_ASSET_ID = 1984;

async function main() {
  // Wait for crypto libraries to be ready
  await cryptoWaitReady();

  // Create a keyring instance and add Alice's account
  const keyring = new Keyring({ type: 'sr25519' });
  const alice = keyring.addFromUri('//Alice');

  // Connect to the local Chopsticks Polkadot Hub fork
  const wsProvider = new WsProvider('ws://localhost:8000');
  const api = await ApiPromise.create({ provider: wsProvider });
  console.log('Connected to Polkadot Hub (Chopsticks fork)');

  // Create the transfer transaction
  const tx = api.tx.balances.transferKeepAlive(TARGET_ADDRESS, TRANSFER_AMOUNT);

  // Define the USDT asset as an XCM multi-location for fee payment
  const assetId = {
    parents: 0,
    interior: {
      X2: [{ PalletInstance: 50 }, { GeneralIndex: USDT_ASSET_ID }],
    },
  };

  // Sign and send the transaction, paying fees with USDT
  console.log('Signing and submitting transaction...');
  await new Promise((resolve, reject) => {
    let unsubscribe;
    tx
      .signAndSend(
        alice,
        { assetId },
        ({ status, events, txHash, dispatchError }) => {
          if (status.isFinalized) {
            console.log(
              `\nTransaction finalized in block: ${status.asFinalized.toHex()}`
            );
            console.log(`Transaction hash: ${txHash.toHex()}`);

            // Display all events
            console.log('\nEvents:');
            events.forEach(({ event }) => {
              console.log(`  ${event.section}.${event.method}`);
            });

            if (unsubscribe) {
              unsubscribe();
            }

            if (dispatchError) {
              if (dispatchError.isModule) {
                const decoded = api.registry.findMetaError(
                  dispatchError.asModule
                );
                const { docs, name, section } = decoded;
                reject(new Error(`${section}.${name}: ${docs.join(' ')}`));
              } else {
                reject(new Error(dispatchError.toString()));
              }
            } else {
              resolve();
            }
          }
        }
      )
      .then((unsub) => {
        unsubscribe = unsub;
      })
      .catch((error) => {
        if (unsubscribe) {
          unsubscribe();
        }
        reject(error);
      });
  });

  // Disconnect from the node
  await api.disconnect();
}

main().catch(console.error);
Complete Code
src/bin/fee_payment_transaction.rs
use std::str::FromStr;
use subxt::config::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig};
use subxt::utils::AccountId32;
use subxt::{OnlineClient, SubstrateConfig};

// Generate types from the Polkadot Hub metadata with Location trait derives
#[subxt::subxt(
    runtime_metadata_path = "metadata/asset_hub.scale",
    derive_for_type(
        path = "staging_xcm::v5::location::Location",
        derive = "Clone, Eq, PartialEq, codec::Encode",
        recursive
    )
)]
pub mod asset_hub {}

// Import XCM location types from the generated metadata module
use asset_hub::runtime_types::staging_xcm::v5::{
    junction::Junction,
    junctions::Junctions,
    location::Location,
};

// Define a custom config where AssetId is an XCM Location
pub enum AssetHubConfig {}

impl Config for AssetHubConfig {
    type AccountId = <PolkadotConfig as Config>::AccountId;
    type Address = <PolkadotConfig as Config>::Address;
    type Signature = <PolkadotConfig as Config>::Signature;
    type Hasher = <PolkadotConfig as Config>::Hasher;
    type Header = <SubstrateConfig as Config>::Header;
    type ExtrinsicParams = DefaultExtrinsicParams<AssetHubConfig>;
    type AssetId = Location;
}

const POLKADOT_HUB_RPC: &str = "ws://localhost:8000";
const TARGET_ADDRESS: &str = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3";
const TRANSFER_AMOUNT: u128 = 3_000_000_000; // 3 DOT
const USDT_ASSET_ID: u128 = 1984;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to the local Chopsticks Polkadot Hub fork
    let api = OnlineClient::<AssetHubConfig>::from_url(POLKADOT_HUB_RPC).await?;
    println!("Connected to Polkadot Hub (Chopsticks fork)");

    // Create Alice's dev keypair
    let alice = subxt_signer::sr25519::dev::alice();
    println!("Sender (Alice): {}", AccountId32::from(alice.public_key()));

    // Create the balance transfer transaction
    let dest = AccountId32::from_str(TARGET_ADDRESS)?;
    let tx = asset_hub::tx()
        .balances()
        .transfer_keep_alive(dest.into(), TRANSFER_AMOUNT);

    // Define the USDT asset location in XCM format
    let asset_location = Location {
        parents: 0,
        interior: Junctions::X2([
            Junction::PalletInstance(50),
            Junction::GeneralIndex(USDT_ASSET_ID),
        ]),
    };

    // Build transaction params to pay fees with the alternative asset
    let tx_params = DefaultExtrinsicParamsBuilder::<AssetHubConfig>::new()
        .tip_of(0, asset_location)
        .build();

    // Sign, submit, and watch for finalization
    println!("Signing and submitting transaction...");
    let progress = api
        .tx()
        .sign_and_submit_then_watch(&tx, &alice, tx_params)
        .await?;

    let in_block = progress.wait_for_finalized().await?;
    let block_hash = in_block.block_hash();
    let events = in_block.wait_for_success().await?;

    // Display transaction results
    println!("\nTransaction finalized in block: {:?}", block_hash);

    println!("\nEvents:");
    for event in events.iter() {
        let event = event?;
        println!(
            "  {}.{}",
            event.pallet_name(),
            event.variant_name()
        );
    }

    Ok(())
}

Run the ScriptΒΆ

npx ts-node fee-payment-transaction.ts
node fee-payment-transaction.js
cargo run --bin fee_payment_transaction

Expected OutputΒΆ

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

node fee-payment-transaction.js
Connected to Polkadot Hub (Chopsticks fork)
Signing and submitting transaction...

Transaction finalized in block: 0x1f4849218bb4c04564a6c6f69c9e40a3940dcdabdc089da01bb49fb471a2c049 Transaction hash: 0x9c967bb79fd09579f5e530a0446ce0171efe9241ba5957d6bcba80bccd5f66da

Events: assets.Withdrawn balances.Withdraw assets.Deposited assetConversion.SwapCreditExecuted balances.Upgraded system.NewAccount balances.Endowed balances.Transfer balances.Deposit assetTxPayment.AssetTxFeePaid system.ExtrinsicSuccess

cargo run --bin fee_payment_transaction
Connected to Polkadot Hub (Chopsticks fork)
Sender (Alice): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
Signing and submitting transaction...

Transaction finalized in block: 0x90c92e4dca64631ab4ccaabb14273cebbb3d59205db437dfcf9ace91452b1434

Events: Assets.Withdrawn Balances.Withdraw Assets.Deposited AssetConversion.SwapCreditExecuted Balances.Upgraded System.NewAccount Balances.Endowed Balances.Transfer Balances.Deposit AssetTxPayment.AssetTxFeePaid System.ExtrinsicSuccess

The key events to look for are:

ConclusionΒΆ

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.

Where to Go NextΒΆ

  • Guide Send Transactions with SDKs


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

    Get Started

  • Guide Calculate Transaction Fees


    Understand how to estimate transaction fees before submitting.

    Get Started

Last update: February 20, 2026
| Created: October 3, 2025