Promise and RxJS wrappers around the Polkadot JS RPC for interacting with Polkadot and Substrate-based blockchains
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Create, sign, and submit transactions with full lifecycle tracking and event monitoring. The @polkadot/api provides comprehensive transaction handling from construction through finalization.
Dynamic transaction interface generated from runtime metadata.
interface SubmittableExtrinsics<ApiType> {
[section: string]: {
[method: string]: SubmittableExtrinsicFunction<ApiType>;
};
}
interface SubmittableExtrinsicFunction<ApiType> {
/**
* Create a submittable extrinsic
* @param ...args - Method-specific parameters
* @returns Submittable extrinsic instance
*/
(...args: any[]): SubmittableExtrinsic<ApiType>;
/**
* Get method metadata
*/
readonly meta: FunctionMetadataLatest;
/**
* Get method section name
*/
readonly section: string;
/**
* Get method name
*/
readonly method: string;
}Individual transaction with signing and submission capabilities.
interface SubmittableExtrinsic<ApiType> {
/**
* Sign the extrinsic with account
* @param account - Signing account (KeyringPair or address)
* @param options - Signing options
* @returns Signed extrinsic
*/
sign(account: AddressOrPair, options?: Partial<SignerOptions>): SubmittableExtrinsic<ApiType>;
/**
* Sign and submit extrinsic
* @param account - Signing account
* @param callback - Status callback function
* @returns Transaction hash or Observable
*/
signAndSend(
account: AddressOrPair,
callback?: (result: SubmittableResult) => void
): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;
/**
* Sign and submit extrinsic with options
* @param account - Signing account
* @param options - Signing options
* @param callback - Status callback function
* @returns Transaction hash or Observable
*/
signAndSend(
account: AddressOrPair,
options?: Partial<SignerOptions>,
callback?: (result: SubmittableResult) => void
): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;
/**
* Sign the extrinsic asynchronously
* @param account - Signing account
* @param options - Signing options
* @returns Promise or Observable resolving to signed extrinsic
*/
signAsync(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<SubmittableExtrinsic<ApiType>> : Promise<SubmittableExtrinsic<ApiType>>;
/**
* Send the extrinsic without callback
* @returns Transaction hash or Observable
*/
send(): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;
/**
* Send the extrinsic with status callback
* @param callback - Status callback function
* @returns Unsubscribe function or Observable
*/
send(callback: (result: SubmittableResult) => void): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<() => void>;
/**
* Transform results with custom function
* @param transform - Result transformation function
* @returns Same extrinsic instance for chaining
*/
withResultTransform(transform: (input: ISubmittableResult) => ISubmittableResult): SubmittableExtrinsic<ApiType>;
/** Whether dry run functionality is available */
readonly hasDryRun: boolean;
/** Whether payment info functionality is available */
readonly hasPaymentInfo: boolean;
/**
* Get payment info for the extrinsic
* @param account - Account that will sign
* @param options - Signing options
* @returns Payment information
*/
paymentInfo(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<RuntimeDispatchInfo> : Promise<RuntimeDispatchInfo>;
/**
* Dry run the extrinsic
* @param account - Account that will sign
* @param options - Signing options
* @returns Dry run result
*/
dryRun(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<ApplyExtrinsicResult> : Promise<ApplyExtrinsicResult>;
/**
* Get extrinsic hash
* @returns Extrinsic hash
*/
get hash(): Hash;
/**
* Get extrinsic method
* @returns Call method
*/
get method(): Call;
/**
* Get extrinsic era
* @returns Era information
*/
get era(): ExtrinsicEra;
/**
* Get extrinsic nonce
* @returns Nonce value
*/
get nonce(): Compact<Index>;
/**
* Get extrinsic tip
* @returns Tip amount
*/
get tip(): Compact<Balance>;
/**
* Convert to hex string
* @returns Hex representation
*/
toHex(): string;
/**
* Get encoded length
* @returns Byte length
*/
get encodedLength(): number;
}Transaction execution result with status and events.
class SubmittableResult {
/** Dispatch error if transaction failed */
readonly dispatchError?: DispatchError;
/** Dispatch information */
readonly dispatchInfo?: DispatchInfo;
/** Internal error if occurred */
readonly internalError?: Error;
/** Events emitted during transaction */
readonly events: EventRecord[];
/** Current transaction status */
readonly status: ExtrinsicStatus;
/** Transaction hash */
readonly txHash: Hash;
/** Transaction index in block */
readonly txIndex?: number;
/** Block number if included */
readonly blockNumber?: BlockNumber;
/**
* Check if transaction is completed (success or error)
*/
get isCompleted(): boolean;
/**
* Check if transaction has errored
*/
get isError(): boolean;
/**
* Check if transaction is finalized
*/
get isFinalized(): boolean;
/**
* Check if transaction is in block
*/
get isInBlock(): boolean;
/**
* Check if transaction has warnings
*/
get isWarning(): boolean;
/**
* Filter events by section and method
* @param section - Event section
* @param method - Event method or methods
* @returns Filtered event records
*/
filterRecords(section: string, method: string | string[]): EventRecord[];
/**
* Find specific event
* @param section - Event section
* @param method - Event method or methods
* @returns First matching event record
*/
findRecord(section: string, method: string | string[]): EventRecord | undefined;
/**
* Convert to human-readable format
* @param isExtended - Include extended information
* @returns Human-readable object
*/
toHuman(isExtended?: boolean): AnyJson;
}Configuration for transaction signing.
interface SignerOptions {
/** Block hash for mortality */
blockHash?: Hash;
/** Block number for mortality */
blockNumber?: BN;
/** Era configuration */
era?: ExtrinsicEra;
/** Genesis block hash */
genesisHash?: Hash;
/** Account nonce */
nonce?: BN | number;
/** Runtime version */
runtimeVersion?: RuntimeVersion;
/** Transaction tip */
tip?: BN | number;
/** Asset ID for tip payment */
assetId?: BN | number;
/** Transaction version */
version?: number;
/** External signer */
signer?: Signer;
/** Transaction mortality period */
mortalLength?: number;
}
interface Signer {
/**
* Sign transaction payload
* @param payload - Transaction payload
* @returns Signature result
*/
signPayload(payload: SignerPayload): Promise<SignerResult>;
/**
* Sign raw data
* @param raw - Raw data to sign
* @returns Signature result
*/
signRaw?(raw: SignerPayloadRaw): Promise<SignerResult>;
}import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
// Create account from seed
const alice = keyring.addFromUri('//Alice');
const bob = '1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg';
// Create transfer transaction
const transfer = api.tx.balances.transferAllowDeath(bob, 1000000000000);
// Sign and submit with callback
const unsubscribe = await transfer.signAndSend(alice, ({ status, events }) => {
console.log(`Transaction status: ${status.type}`);
if (status.isInBlock) {
console.log(`Included in block: ${status.asInBlock}`);
// Check for transfer events
events.forEach(({ event, phase }) => {
if (api.events.balances.Transfer.is(event)) {
const [from, to, amount] = event.data;
console.log(`Transfer: ${from} -> ${to}: ${amount.toHuman()}`);
}
});
} else if (status.isFinalized) {
console.log(`Finalized in block: ${status.asFinalized}`);
unsubscribe();
}
});import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
// Get current nonce
const nonce = await api.rpc.system.accountNextIndex(alice.address);
// Create transaction with specific options
const transfer = api.tx.balances.transferAllowDeath(
'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',
1000000000000
);
// Get payment info before submission
const paymentInfo = await transfer.paymentInfo(alice);
console.log(`Estimated fee: ${paymentInfo.partialFee.toHuman()}`);
// Sign with custom options
const signedTx = transfer.sign(alice, {
nonce,
tip: 100000000, // 0.1 DOT tip
mortalLength: 64 // 64 block mortality
});
console.log(`Transaction hash: ${signedTx.hash}`);
console.log(`Transaction length: ${signedTx.encodedLength} bytes`);
// Submit signed transaction
const txHash = await api.rpc.author.submitExtrinsic(signedTx);
console.log(`Submitted: ${txHash}`);import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
// Create multiple transfers
const transfers = [
api.tx.balances.transferAllowDeath('1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg', 1000000000000),
api.tx.balances.transferAllowDeath('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5', 2000000000000),
api.tx.balances.transferAllowDeath('14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', 3000000000000)
];
// Create batch transaction
const batchTx = api.tx.utility.batch(transfers);
// Submit batch
const unsubscribe = await batchTx.signAndSend(alice, ({ status, events }) => {
console.log(`Batch status: ${status.type}`);
if (status.isInBlock) {
// Check batch events
events.forEach(({ event, phase }) => {
if (api.events.utility.BatchCompleted.is(event)) {
console.log('Batch completed successfully');
} else if (api.events.utility.BatchInterrupted.is(event)) {
const [index, error] = event.data;
console.log(`Batch interrupted at index ${index}: ${error}`);
}
});
} else if (status.isFinalized) {
console.log('Batch finalized');
unsubscribe();
}
});import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
// Create transaction
const transfer = api.tx.balances.transferAllowDeath(
'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',
1000000000000
);
// Dry run to check if transaction would succeed
try {
const dryRunResult = await transfer.dryRun(alice);
if (dryRunResult.isOk) {
console.log('Transaction would succeed');
const weight = dryRunResult.asOk;
console.log(`Weight: ${weight.weight.toHuman()}`);
// Now submit for real
const txHash = await transfer.signAndSend(alice);
console.log(`Submitted: ${txHash}`);
} else {
console.log('Transaction would fail:', dryRunResult.asErr);
}
} catch (error) {
console.error('Dry run failed:', error);
}import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
const bob = keyring.addFromUri('//Bob');
const charlie = keyring.addFromUri('//Charlie');
// Create multisig account
const threshold = 2;
const signatories = [alice.address, bob.address, charlie.address].sort();
const multisigAddress = api.createType('MultiAddress',
api.createType('AccountId32',
api.registry.createType('MultiAddress', {
Id: api.createType('AccountId32', signatories[0])
})
)
).toString();
// Create call to execute
const call = api.tx.balances.transferAllowDeath(
'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',
1000000000000
);
// Get call hash and timepoint
const callHash = call.method.hash;
const timepoint = null; // First approval
// Alice approves first
const approval1 = api.tx.multisig.approveAsMulti(
threshold,
signatories.filter(addr => addr !== alice.address),
timepoint,
callHash,
0 // max weight
);
await approval1.signAndSend(alice, ({ status }) => {
if (status.isInBlock) {
console.log('Alice approved multisig transaction');
}
});
// Bob executes (second approval)
const execution = api.tx.multisig.asMulti(
threshold,
signatories.filter(addr => addr !== bob.address),
timepoint,
call,
0 // max weight
);
await execution.signAndSend(bob, ({ status, events }) => {
if (status.isInBlock) {
console.log('Bob executed multisig transaction');
events.forEach(({ event }) => {
if (api.events.multisig.MultisigExecuted.is(event)) {
const [approving, timepoint, multisig, callHash, result] = event.data;
console.log(`Multisig executed: ${result.isOk ? 'Success' : 'Failed'}`);
}
});
}
});import { ApiPromise } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
const api = await ApiPromise.create();
const keyring = new Keyring({ type: 'sr25519' });
const stash = keyring.addFromUri('//Alice');
const controller = keyring.addFromUri('//Alice//stash');
// Bond tokens for staking
const bondAmount = api.createType('Compact<Balance>', '1000000000000000'); // 100 DOT
const bond = api.tx.staking.bond(controller.address, bondAmount, 'Staked');
await bond.signAndSend(stash, ({ status, events }) => {
if (status.isInBlock) {
events.forEach(({ event }) => {
if (api.events.staking.Bonded.is(event)) {
const [stash, amount] = event.data;
console.log(`Bonded ${amount.toHuman()} from ${stash}`);
}
});
}
});
// Nominate validators
const validators = [
'1KvKReVmUiTc2LW2a4qyHy3PeGk1RbFw67rZE4TaFvZw6Z3',
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'
];
const nominate = api.tx.staking.nominate(validators);
await nominate.signAndSend(controller, ({ status, events }) => {
if (status.isInBlock) {
events.forEach(({ event }) => {
if (api.events.staking.Nominated.is(event)) {
const [nominator, targets] = event.data;
console.log(`${nominator} nominated ${targets.length} validators`);
}
});
}
});import { ApiRx } from "@polkadot/api";
import { Keyring } from "@polkadot/keyring";
import { switchMap, tap, filter } from "rxjs";
const api$ = ApiRx.create();
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
// RxJS transaction pipeline
api$.pipe(
switchMap(api => {
const transfer = api.tx.balances.transferAllowDeath(
'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',
1000000000000
);
return transfer.signAndSend(alice);
}),
tap(result => console.log(`Status: ${result.status.type}`)),
filter(result => result.status.isFinalized)
).subscribe(result => {
console.log(`Transaction finalized: ${result.txHash}`);
// Process events
result.events.forEach(({ event }) => {
if (event.section === 'balances' && event.method === 'Transfer') {
const [from, to, amount] = event.data;
console.log(`Transfer: ${from} -> ${to}: ${amount.toHuman()}`);
}
});
});Install with Tessl CLI
npx tessl i tessl/npm-polkadot--api