CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-polkadot--api

Promise and RxJS wrappers around the Polkadot JS RPC for interacting with Polkadot and Substrate-based blockchains

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

transaction-submission.mddocs/

Transaction Submission

Create, sign, and submit transactions with full lifecycle tracking and event monitoring. The @polkadot/api provides comprehensive transaction handling from construction through finalization.

Capabilities

Submittable Extrinsics Interface

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;
}

Submittable Extrinsic Class

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 Result Class

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;
}

Signing Options

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>;
}

Usage Examples

Basic Transaction Creation and Submission

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();
  }
});

Advanced Transaction Handling

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}`);

Batch Transactions

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();
  }
});

Transaction Dry Run

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);
}

Multi-signature Transactions

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'}`);
      }
    });
  }
});

Staking Transactions

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`);
      }
    });
  }
});

RxJS Transaction Handling

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

docs

api-initialization.md

blockchain-queries.md

events-metadata.md

index.md

network-providers.md

rpc-operations.md

transaction-submission.md

tile.json