Send Transactions with SDKs¶
Introduction¶
Sending transactions on Polkadot SDK-based blockchains involves constructing an extrinsic (transaction), signing it with your account's private key, and submitting it to the network. Each SDK provides different methods for transaction construction, signing, and submission.
This guide demonstrates how to send transactions using five popular SDKs:
- Polkadot API (PAPI): Modern TypeScript library with type-safe APIs
- Polkadot.js API: Comprehensive JavaScript library (maintenance mode)
- Dedot: Lightweight TypeScript library optimized for performance
- Python Substrate Interface: Python library for Substrate chains
- Subxt: Rust library with compile-time type safety
Select your preferred SDK below to see complete, runnable examples that send balance transfer transactions on Polkadot Hub.
Prerequisites¶
- Access to a Polkadot-SDK-compatible wallet, with its mnemonic phrase or private key.
- A funded account on Polkadot Hub, with some testnet tokens. You can use the Polkadot Faucet to obtain test tokens.
Send Transactions¶
Warning
Never share your mnemonic phrase or private keys. The examples below use mnemonics for demonstration purposes only. In production, use secure key management solutions.
Prerequisites
- Node.js v18 or higher
- npm, pnpm, or yarn package manager
Environment Setup
-
Create and initialize a new project:
-
Install dependencies:
-
Generate types for Polkadot Hub TestNet:
Send Balance Transfer
The following example constructs, signs, and submits a balance transfer transaction.
Create a file named send-transfer.ts and add the following code to it:
import { createClient } from 'polkadot-api';
import { getWsProvider } from 'polkadot-api/ws-provider';
import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat';
import { polkadotTestNet } from '@polkadot-api/descriptors';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { Keyring } from '@polkadot/keyring';
import { getPolkadotSigner } from 'polkadot-api/signer';
const POLKADOT_TESTNET_RPC = 'INSERT_WS_ENDPOINT';
const SENDER_MNEMONIC = 'INSERT_MNEMONIC';
const DEST_ADDRESS = 'INSERT_DEST_ADDRESS';
const AMOUNT = 1_000_000_000n; // 1 PAS (adjust decimals as needed)
async function main() {
try {
await cryptoWaitReady();
const keyring = new Keyring({ type: 'sr25519' });
const sender = keyring.addFromMnemonic(SENDER_MNEMONIC);
console.log(`Sender address: ${sender.address}`);
// Create the client connection
const client = createClient(
withPolkadotSdkCompat(getWsProvider(POLKADOT_TESTNET_RPC))
);
// Get the typed API
const api = client.getTypedApi(polkadotTestNet);
console.log('Connected to Polkadot Testnet');
// Create signer using getPolkadotSigner
const signer = getPolkadotSigner(
sender.publicKey,
'Sr25519',
async (input) => sender.sign(input)
);
// Construct and submit the transfer transaction
const tx = api.tx.Balances.transfer_keep_alive({
dest: { type: 'Id', value: DEST_ADDRESS },
value: AMOUNT,
});
console.log('\nSigning and submitting transaction...');
const { txHash } = await tx.signAndSubmit(signer);
console.log(`Transaction submitted with hash: ${txHash}`);
await client.destroy();
console.log('Disconnected');
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();
Note
Ensure to replace INSERT_WS_ENDPOINT with the proper WebSocket endpoint, INSERT_SENDER_MNEMONIC with your account's mnemonic phrase, and INSERT_DEST_ADDRESS with the recipient address. For this example, you can use Polkadot Hub (wss://polkadot-asset-hub-rpc.polkadot.io).
Run the script:
You should see output similar to:
Maintenance Mode Only
The Polkadot.js API is no longer actively developed. New projects should use PAPI or Dedot as actively maintained alternatives.
Prerequisites
- Node.js v18 or higher
- npm, pnpm, or yarn package manager
Environment Setup
-
Create and initialize a new project:
-
Install dependencies:
Send Balance Transfer
The following example constructs, signs, and submits a balance transfer transaction.
Create a file named send-transfer.js and add the following code:
import { ApiPromise, WsProvider } from '@polkadot/api';
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';
const POLKADOT_TESTNET_RPC = 'INSERT_WS_ENDPOINT';
const SENDER_MNEMONIC = 'INSERT_MNEMONIC';
const DEST_ADDRESS = 'INSERT_DEST_ADDRESS';
const AMOUNT = 1_000_000_000n; // 1 PAS (adjust decimals as needed)
async function main() {
// Wait for crypto to be ready
await cryptoWaitReady();
// Create a WebSocket provider
const wsProvider = new WsProvider(POLKADOT_TESTNET_RPC);
// Initialize the API
const api = await ApiPromise.create({ provider: wsProvider });
console.log('Connected to Polkadot Testnet');
// Set up keyring and get sender account
const keyring = new Keyring({ type: 'sr25519' });
const sender = keyring.addFromMnemonic(SENDER_MNEMONIC);
const senderAddress = sender.address;
console.log(`Sender address: ${senderAddress}`);
console.log(`Recipient address: ${DEST_ADDRESS}`);
console.log(`Amount: ${AMOUNT} (${Number(AMOUNT) / 1_000_000_000} PAS)\n`);
// Get sender's account info to check balance
const accountInfo = await api.query.system.account(senderAddress);
console.log(
`Sender balance: ${accountInfo.data.free.toString()} (${
Number(accountInfo.data.free.toBigInt()) / 1_000_000_000
} PAS)`
);
// Construct and sign the transfer transaction
console.log('\nSigning and submitting transaction...');
await new Promise((resolve, reject) => {
api.tx.balances
.transferKeepAlive(DEST_ADDRESS, AMOUNT)
.signAndSend(sender, ({ status, txHash, dispatchError }) => {
if (status.isInBlock) {
console.log(
`Transaction included in block: ${status.asInBlock.toHex()}`
);
} else if (status.isFinalized) {
console.log(
`Transaction finalized in block: ${status.asFinalized.toHex()}`
);
console.log(`Transaction hash: ${txHash.toHex()}`);
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 {
console.log('Transaction successful!');
resolve(txHash.toHex());
}
}
})
.catch(reject);
});
// Disconnect from the node
await api.disconnect();
}
main().catch(console.error);
Note
Ensure to replace INSERT_WS_ENDPOINT with the proper WebSocket endpoint, INSERT_SENDER_MNEMONIC with your account's mnemonic phrase, and INSERT_DEST_ADDRESS with the recipient address. For this example, you can use Polkadot Hub (wss://polkadot-asset-hub-rpc.polkadot.io).
Run the script:
You should see output similar to:
Prerequisites
- Node.js v18 or higher
- npm, pnpm, or yarn package manager
Environment Setup
-
Create and initialize a new project:
-
Install dependencies:
Send Balance Transfer
The following example constructs, signs, and submits a balance transfer transaction.
Create a file named send-transfer.ts and add the following code to it:
import { DedotClient, WsProvider } from 'dedot';
import type { PolkadotAssetHubApi } from '@dedot/chaintypes';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { Keyring } from '@polkadot/keyring';
const POLKADOT_TESTNET_RPC = 'INSERT_WS_ENDPOINT';
const SENDER_MNEMONIC = 'INSERT_MNEMONIC';
const DEST_ADDRESS = 'INSERT_DEST_ADDRESS';
const AMOUNT = 1_000_000_000n; // 1 PAS (adjust decimals as needed)
async function main() {
// Wait for crypto to be ready
await cryptoWaitReady();
// Initialize provider and client with Asset Hub types
const provider = new WsProvider(POLKADOT_TESTNET_RPC);
const client = await DedotClient.new<PolkadotAssetHubApi>(provider);
console.log('Connected to Polkadot Testnet');
// Set up keyring and get sender account
const keyring = new Keyring({ type: 'sr25519' });
const sender = keyring.addFromMnemonic(SENDER_MNEMONIC);
const senderAddress = sender.address;
console.log(`Sender address: ${senderAddress}`);
console.log(`Recipient address: ${DEST_ADDRESS}`);
console.log(`Amount: ${AMOUNT} (${AMOUNT / 1_000_000_000n} PAS)\n`);
// Get sender's account info to check balance
const accountInfo = await client.query.system.account(senderAddress);
console.log(`Sender balance: ${accountInfo.data.free}`);
// Sign and submit the transfer transaction
console.log('\nSigning and submitting transaction...');
// Wait for transaction to complete using a Promise
await new Promise<void>((resolve, reject) => {
client.tx.balances
.transferKeepAlive(DEST_ADDRESS, AMOUNT)
.signAndSend(sender, async ({ status, txHash, dispatchError }) => {
console.log(`Transaction status: ${status.type}`);
// Log transaction hash immediately
if (txHash) {
console.log(
`Transaction hash: ${
typeof txHash === 'string' ? txHash : txHash.toHex()
}`
);
}
if (status.type === 'BestChainBlockIncluded') {
console.log(
`Transaction included in block: ${status.value.blockHash}`
);
}
if (status.type === 'Finalized') {
console.log(
`Transaction finalized in block: ${status.value.blockHash}`
);
// Check for dispatch errors
if (dispatchError) {
if (dispatchError.type === 'Module') {
const decoded = client.registry.findMetaError(
dispatchError.value
);
console.error(
`Dispatch error: ${decoded.section}.${decoded.name}: ${decoded.docs}`
);
reject(
new Error(
`Transaction failed: ${decoded.section}.${decoded.name}`
)
);
} else {
console.error(`Dispatch error: ${dispatchError.type}`);
reject(new Error(`Transaction failed: ${dispatchError.type}`));
}
} else {
console.log('Transaction successful!');
resolve();
}
}
})
.catch(reject);
});
// Disconnect the client after transaction completes
await client.disconnect();
console.log('Disconnected from Polkadot Hub');
}
main().catch(console.error);
Note
Ensure to replace INSERT_WS_ENDPOINT with the proper WebSocket endpoint, INSERT_SENDER_MNEMONIC with your account's mnemonic phrase, and INSERT_DEST_ADDRESS with the recipient address. For this example, you can use Polkadot Hub (wss://polkadot-asset-hub-rpc.polkadot.io).
Run the script:
You should see output similar to:
Prerequisites
- Python 3.8 or higher
- pip package manager
Environment Setup
-
Create a new project directory and set up a virtual environment:
-
Install the substrate-interface package:
Send Balance Transfer
The following example constructs, signs, and submits a balance transfer transaction.
Create a file named send_transfer.py and add the following code to it:
from substrateinterface import SubstrateInterface, Keypair
POLKADOT_TESTNET_RPC = "INSERT_WS_ENDPOINT"
SENDER_MNEMONIC = "INSERT_MNEMONIC"
DEST_ADDRESS = "INSERT_DEST_ADDRESS"
AMOUNT = 1_000_000_000 # 1 PAS (adjust decimals as needed)
def main():
# Connect to Polkadot Hub
substrate = SubstrateInterface(url=POLKADOT_TESTNET_RPC)
print("Connected to Polkadot Testnet")
# Create keypair from mnemonic
keypair = Keypair.create_from_mnemonic(SENDER_MNEMONIC)
sender_address = keypair.ss58_address
print(f"Sender address: {sender_address}")
print(f"Recipient address: {DEST_ADDRESS}")
print(f"Amount: {AMOUNT} ({AMOUNT / 1_000_000_000} PAS)\n")
# Get sender's account info to check balance
account_info = substrate.query(
module="System", storage_function="Account", params=[sender_address]
)
print(f"Sender balance: {account_info.value['data']['free']}")
# Compose the transfer call
call = substrate.compose_call(
call_module="Balances",
call_function="transfer_keep_alive",
call_params={"dest": DEST_ADDRESS, "value": AMOUNT},
)
# Create a signed extrinsic
print("\nSigning and submitting transaction...")
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair)
# Submit and wait for inclusion
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
if receipt.is_success:
print("\nTransaction successful!")
print(f"Extrinsic Hash: {receipt.extrinsic_hash}")
print(f"Block Hash: {receipt.block_hash}")
else:
print(f"\nTransaction failed: {receipt.error_message}")
# Close connection
substrate.close()
if __name__ == "__main__":
main()
Note
Ensure to replace INSERT_WS_ENDPOINT with the proper WebSocket endpoint, INSERT_SENDER_MNEMONIC with your account's mnemonic phrase, and INSERT_DEST_ADDRESS with the recipient address. For this example, you can use Polkadot Hub (wss://polkadot-asset-hub-rpc.polkadot.io).
Run the script:
You should see output similar to:
Subxt is a Rust library that provides compile-time type safety through code generation from chain metadata.
Prerequisites
- Rust toolchain (latest stable)
- Cargo package manager
Environment Setup
-
Create a new Rust project:
-
Install the Subxt CLI:
-
Download the Polkadot Hub metadata:
-
Update
Cargo.tomlwith the required dependencies:Cargo.toml[package] name = "subxt-send-tx-example" version = "0.1.0" edition = "2021" [[bin]] name = "send_transfer" path = "src/bin/send_transfer.rs" [dependencies] subxt = { version = "0.35", default-features = false, features = ["macros"] } subxt-signer = { version = "0.4", features = ["bip39"] } tokio = { version = "1", features = ["rt", "macros"] }
Send Balance Transfer
The following example constructs, signs, and submits a balance transfer transaction.
Create a file at src/bin/send_transfer.rs and add the following code to it:
use std::str::FromStr;
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::{bip39::Mnemonic, sr25519::Keypair};
// Generate an interface from the node's metadata
#[subxt::subxt(runtime_metadata_path = "asset_hub_metadata.scale")]
pub mod asset_hub {}
const ASSET_HUB_RPC: &str = "INSERT_WS_ENDPOINT";
const SENDER_MNEMONIC: &str = "INSERT_SENDER_MNEMONIC";
const DEST_ADDRESS: &str = "INSERT_DEST_ADDRESS";
const AMOUNT: u128 = 1_000_000_000_000; // 1 DOT (12 decimals)
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize the Subxt client
let api = OnlineClient::<PolkadotConfig>::from_url(ASSET_HUB_RPC).await?;
println!("Connected to Polkadot Hub");
// Load the sender's keypair from a mnemonic phrase
let mnemonic = Mnemonic::parse(SENDER_MNEMONIC)?;
let sender_keypair = Keypair::from_phrase(&mnemonic, None)?;
let sender_address = AccountId32::from(sender_keypair.public_key());
println!("Sender address: {}", sender_address);
println!("Recipient address: {}", DEST_ADDRESS);
println!("Amount: {} ({} DOT)\n", AMOUNT, AMOUNT / 1_000_000_000_000);
// Get sender's account info to check balance
let storage_query = asset_hub::storage().system().account(sender_address);
let account_info = api
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.await?;
if let Some(info) = account_info {
println!("Sender balance: {}", info.data.free);
}
// Convert the recipient address into an AccountId32
let dest = AccountId32::from_str(DEST_ADDRESS)?;
// Build the balance transfer extrinsic
let balance_transfer_tx = asset_hub::tx()
.balances()
.transfer_keep_alive(dest.into(), AMOUNT);
// Sign and submit the extrinsic, then wait for it to be finalized
println!("\nSigning and submitting transaction...");
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &sender_keypair)
.await?
.wait_for_finalized_success()
.await?;
// Check for a successful transfer event
if let Some(event) = events.find_first::<asset_hub::balances::events::Transfer>()? {
println!("\nTransaction successful!");
println!("Transfer event: {:?}", event);
}
Ok(())
}
Note
Ensure to replace INSERT_WS_ENDPOINT with the proper WebSocket endpoint, INSERT_SENDER_MNEMONIC with your account's mnemonic phrase, and INSERT_DEST_ADDRESS with the recipient address. For this example, you can use Polkadot Hub (wss://polkadot-asset-hub-rpc.polkadot.io).
Run the script:
You should see output similar to:
Where to Go Next¶
-
Guide Query On-Chain State
Learn how to query storage and runtime data with the SDKs used in this guide.
-
Guide Calculate Transaction Fees
Estimate fees before sending transactions
| Created: January 14, 2026