or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-initialization.mdblockchain-queries.mdevents-metadata.mdindex.mdnetwork-providers.mdrpc-operations.mdtransaction-submission.md

transaction-submission.mddocs/

0

# Transaction Submission

1

2

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

3

4

## Capabilities

5

6

### Submittable Extrinsics Interface

7

8

Dynamic transaction interface generated from runtime metadata.

9

10

```typescript { .api }

11

interface SubmittableExtrinsics<ApiType> {

12

[section: string]: {

13

[method: string]: SubmittableExtrinsicFunction<ApiType>;

14

};

15

}

16

17

interface SubmittableExtrinsicFunction<ApiType> {

18

/**

19

* Create a submittable extrinsic

20

* @param ...args - Method-specific parameters

21

* @returns Submittable extrinsic instance

22

*/

23

(...args: any[]): SubmittableExtrinsic<ApiType>;

24

25

/**

26

* Get method metadata

27

*/

28

readonly meta: FunctionMetadataLatest;

29

30

/**

31

* Get method section name

32

*/

33

readonly section: string;

34

35

/**

36

* Get method name

37

*/

38

readonly method: string;

39

}

40

```

41

42

### Submittable Extrinsic Class

43

44

Individual transaction with signing and submission capabilities.

45

46

```typescript { .api }

47

interface SubmittableExtrinsic<ApiType> {

48

/**

49

* Sign the extrinsic with account

50

* @param account - Signing account (KeyringPair or address)

51

* @param options - Signing options

52

* @returns Signed extrinsic

53

*/

54

sign(account: AddressOrPair, options?: Partial<SignerOptions>): SubmittableExtrinsic<ApiType>;

55

56

/**

57

* Sign and submit extrinsic

58

* @param account - Signing account

59

* @param callback - Status callback function

60

* @returns Transaction hash or Observable

61

*/

62

signAndSend(

63

account: AddressOrPair,

64

callback?: (result: SubmittableResult) => void

65

): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;

66

67

/**

68

* Sign and submit extrinsic with options

69

* @param account - Signing account

70

* @param options - Signing options

71

* @param callback - Status callback function

72

* @returns Transaction hash or Observable

73

*/

74

signAndSend(

75

account: AddressOrPair,

76

options?: Partial<SignerOptions>,

77

callback?: (result: SubmittableResult) => void

78

): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;

79

80

/**

81

* Sign the extrinsic asynchronously

82

* @param account - Signing account

83

* @param options - Signing options

84

* @returns Promise or Observable resolving to signed extrinsic

85

*/

86

signAsync(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<SubmittableExtrinsic<ApiType>> : Promise<SubmittableExtrinsic<ApiType>>;

87

88

/**

89

* Send the extrinsic without callback

90

* @returns Transaction hash or Observable

91

*/

92

send(): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<Hash>;

93

94

/**

95

* Send the extrinsic with status callback

96

* @param callback - Status callback function

97

* @returns Unsubscribe function or Observable

98

*/

99

send(callback: (result: SubmittableResult) => void): ApiType extends 'rxjs' ? Observable<SubmittableResult> : Promise<() => void>;

100

101

/**

102

* Transform results with custom function

103

* @param transform - Result transformation function

104

* @returns Same extrinsic instance for chaining

105

*/

106

withResultTransform(transform: (input: ISubmittableResult) => ISubmittableResult): SubmittableExtrinsic<ApiType>;

107

108

/** Whether dry run functionality is available */

109

readonly hasDryRun: boolean;

110

111

/** Whether payment info functionality is available */

112

readonly hasPaymentInfo: boolean;

113

114

/**

115

* Get payment info for the extrinsic

116

* @param account - Account that will sign

117

* @param options - Signing options

118

* @returns Payment information

119

*/

120

paymentInfo(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<RuntimeDispatchInfo> : Promise<RuntimeDispatchInfo>;

121

122

/**

123

* Dry run the extrinsic

124

* @param account - Account that will sign

125

* @param options - Signing options

126

* @returns Dry run result

127

*/

128

dryRun(account: AddressOrPair, options?: Partial<SignerOptions>): ApiType extends 'rxjs' ? Observable<ApplyExtrinsicResult> : Promise<ApplyExtrinsicResult>;

129

130

/**

131

* Get extrinsic hash

132

* @returns Extrinsic hash

133

*/

134

get hash(): Hash;

135

136

/**

137

* Get extrinsic method

138

* @returns Call method

139

*/

140

get method(): Call;

141

142

/**

143

* Get extrinsic era

144

* @returns Era information

145

*/

146

get era(): ExtrinsicEra;

147

148

/**

149

* Get extrinsic nonce

150

* @returns Nonce value

151

*/

152

get nonce(): Compact<Index>;

153

154

/**

155

* Get extrinsic tip

156

* @returns Tip amount

157

*/

158

get tip(): Compact<Balance>;

159

160

/**

161

* Convert to hex string

162

* @returns Hex representation

163

*/

164

toHex(): string;

165

166

/**

167

* Get encoded length

168

* @returns Byte length

169

*/

170

get encodedLength(): number;

171

}

172

```

173

174

### Transaction Result Class

175

176

Transaction execution result with status and events.

177

178

```typescript { .api }

179

class SubmittableResult {

180

/** Dispatch error if transaction failed */

181

readonly dispatchError?: DispatchError;

182

183

/** Dispatch information */

184

readonly dispatchInfo?: DispatchInfo;

185

186

/** Internal error if occurred */

187

readonly internalError?: Error;

188

189

/** Events emitted during transaction */

190

readonly events: EventRecord[];

191

192

/** Current transaction status */

193

readonly status: ExtrinsicStatus;

194

195

/** Transaction hash */

196

readonly txHash: Hash;

197

198

/** Transaction index in block */

199

readonly txIndex?: number;

200

201

/** Block number if included */

202

readonly blockNumber?: BlockNumber;

203

204

/**

205

* Check if transaction is completed (success or error)

206

*/

207

get isCompleted(): boolean;

208

209

/**

210

* Check if transaction has errored

211

*/

212

get isError(): boolean;

213

214

/**

215

* Check if transaction is finalized

216

*/

217

get isFinalized(): boolean;

218

219

/**

220

* Check if transaction is in block

221

*/

222

get isInBlock(): boolean;

223

224

/**

225

* Check if transaction has warnings

226

*/

227

get isWarning(): boolean;

228

229

/**

230

* Filter events by section and method

231

* @param section - Event section

232

* @param method - Event method or methods

233

* @returns Filtered event records

234

*/

235

filterRecords(section: string, method: string | string[]): EventRecord[];

236

237

/**

238

* Find specific event

239

* @param section - Event section

240

* @param method - Event method or methods

241

* @returns First matching event record

242

*/

243

findRecord(section: string, method: string | string[]): EventRecord | undefined;

244

245

/**

246

* Convert to human-readable format

247

* @param isExtended - Include extended information

248

* @returns Human-readable object

249

*/

250

toHuman(isExtended?: boolean): AnyJson;

251

}

252

```

253

254

### Signing Options

255

256

Configuration for transaction signing.

257

258

```typescript { .api }

259

interface SignerOptions {

260

/** Block hash for mortality */

261

blockHash?: Hash;

262

263

/** Block number for mortality */

264

blockNumber?: BN;

265

266

/** Era configuration */

267

era?: ExtrinsicEra;

268

269

/** Genesis block hash */

270

genesisHash?: Hash;

271

272

/** Account nonce */

273

nonce?: BN | number;

274

275

/** Runtime version */

276

runtimeVersion?: RuntimeVersion;

277

278

/** Transaction tip */

279

tip?: BN | number;

280

281

/** Asset ID for tip payment */

282

assetId?: BN | number;

283

284

/** Transaction version */

285

version?: number;

286

287

/** External signer */

288

signer?: Signer;

289

290

/** Transaction mortality period */

291

mortalLength?: number;

292

}

293

294

interface Signer {

295

/**

296

* Sign transaction payload

297

* @param payload - Transaction payload

298

* @returns Signature result

299

*/

300

signPayload(payload: SignerPayload): Promise<SignerResult>;

301

302

/**

303

* Sign raw data

304

* @param raw - Raw data to sign

305

* @returns Signature result

306

*/

307

signRaw?(raw: SignerPayloadRaw): Promise<SignerResult>;

308

}

309

```

310

311

## Usage Examples

312

313

### Basic Transaction Creation and Submission

314

315

```typescript

316

import { ApiPromise } from "@polkadot/api";

317

import { Keyring } from "@polkadot/keyring";

318

319

const api = await ApiPromise.create();

320

const keyring = new Keyring({ type: 'sr25519' });

321

322

// Create account from seed

323

const alice = keyring.addFromUri('//Alice');

324

const bob = '1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg';

325

326

// Create transfer transaction

327

const transfer = api.tx.balances.transferAllowDeath(bob, 1000000000000);

328

329

// Sign and submit with callback

330

const unsubscribe = await transfer.signAndSend(alice, ({ status, events }) => {

331

console.log(`Transaction status: ${status.type}`);

332

333

if (status.isInBlock) {

334

console.log(`Included in block: ${status.asInBlock}`);

335

336

// Check for transfer events

337

events.forEach(({ event, phase }) => {

338

if (api.events.balances.Transfer.is(event)) {

339

const [from, to, amount] = event.data;

340

console.log(`Transfer: ${from} -> ${to}: ${amount.toHuman()}`);

341

}

342

});

343

} else if (status.isFinalized) {

344

console.log(`Finalized in block: ${status.asFinalized}`);

345

unsubscribe();

346

}

347

});

348

```

349

350

### Advanced Transaction Handling

351

352

```typescript

353

import { ApiPromise } from "@polkadot/api";

354

import { Keyring } from "@polkadot/keyring";

355

356

const api = await ApiPromise.create();

357

const keyring = new Keyring({ type: 'sr25519' });

358

const alice = keyring.addFromUri('//Alice');

359

360

// Get current nonce

361

const nonce = await api.rpc.system.accountNextIndex(alice.address);

362

363

// Create transaction with specific options

364

const transfer = api.tx.balances.transferAllowDeath(

365

'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',

366

1000000000000

367

);

368

369

// Get payment info before submission

370

const paymentInfo = await transfer.paymentInfo(alice);

371

console.log(`Estimated fee: ${paymentInfo.partialFee.toHuman()}`);

372

373

// Sign with custom options

374

const signedTx = transfer.sign(alice, {

375

nonce,

376

tip: 100000000, // 0.1 DOT tip

377

mortalLength: 64 // 64 block mortality

378

});

379

380

console.log(`Transaction hash: ${signedTx.hash}`);

381

console.log(`Transaction length: ${signedTx.encodedLength} bytes`);

382

383

// Submit signed transaction

384

const txHash = await api.rpc.author.submitExtrinsic(signedTx);

385

console.log(`Submitted: ${txHash}`);

386

```

387

388

### Batch Transactions

389

390

```typescript

391

import { ApiPromise } from "@polkadot/api";

392

import { Keyring } from "@polkadot/keyring";

393

394

const api = await ApiPromise.create();

395

const keyring = new Keyring({ type: 'sr25519' });

396

const alice = keyring.addFromUri('//Alice');

397

398

// Create multiple transfers

399

const transfers = [

400

api.tx.balances.transferAllowDeath('1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg', 1000000000000),

401

api.tx.balances.transferAllowDeath('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5', 2000000000000),

402

api.tx.balances.transferAllowDeath('14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', 3000000000000)

403

];

404

405

// Create batch transaction

406

const batchTx = api.tx.utility.batch(transfers);

407

408

// Submit batch

409

const unsubscribe = await batchTx.signAndSend(alice, ({ status, events }) => {

410

console.log(`Batch status: ${status.type}`);

411

412

if (status.isInBlock) {

413

// Check batch events

414

events.forEach(({ event, phase }) => {

415

if (api.events.utility.BatchCompleted.is(event)) {

416

console.log('Batch completed successfully');

417

} else if (api.events.utility.BatchInterrupted.is(event)) {

418

const [index, error] = event.data;

419

console.log(`Batch interrupted at index ${index}: ${error}`);

420

}

421

});

422

} else if (status.isFinalized) {

423

console.log('Batch finalized');

424

unsubscribe();

425

}

426

});

427

```

428

429

### Transaction Dry Run

430

431

```typescript

432

import { ApiPromise } from "@polkadot/api";

433

import { Keyring } from "@polkadot/keyring";

434

435

const api = await ApiPromise.create();

436

const keyring = new Keyring({ type: 'sr25519' });

437

const alice = keyring.addFromUri('//Alice');

438

439

// Create transaction

440

const transfer = api.tx.balances.transferAllowDeath(

441

'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',

442

1000000000000

443

);

444

445

// Dry run to check if transaction would succeed

446

try {

447

const dryRunResult = await transfer.dryRun(alice);

448

449

if (dryRunResult.isOk) {

450

console.log('Transaction would succeed');

451

const weight = dryRunResult.asOk;

452

console.log(`Weight: ${weight.weight.toHuman()}`);

453

454

// Now submit for real

455

const txHash = await transfer.signAndSend(alice);

456

console.log(`Submitted: ${txHash}`);

457

} else {

458

console.log('Transaction would fail:', dryRunResult.asErr);

459

}

460

} catch (error) {

461

console.error('Dry run failed:', error);

462

}

463

```

464

465

### Multi-signature Transactions

466

467

```typescript

468

import { ApiPromise } from "@polkadot/api";

469

import { Keyring } from "@polkadot/keyring";

470

471

const api = await ApiPromise.create();

472

const keyring = new Keyring({ type: 'sr25519' });

473

474

const alice = keyring.addFromUri('//Alice');

475

const bob = keyring.addFromUri('//Bob');

476

const charlie = keyring.addFromUri('//Charlie');

477

478

// Create multisig account

479

const threshold = 2;

480

const signatories = [alice.address, bob.address, charlie.address].sort();

481

const multisigAddress = api.createType('MultiAddress',

482

api.createType('AccountId32',

483

api.registry.createType('MultiAddress', {

484

Id: api.createType('AccountId32', signatories[0])

485

})

486

)

487

).toString();

488

489

// Create call to execute

490

const call = api.tx.balances.transferAllowDeath(

491

'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',

492

1000000000000

493

);

494

495

// Get call hash and timepoint

496

const callHash = call.method.hash;

497

const timepoint = null; // First approval

498

499

// Alice approves first

500

const approval1 = api.tx.multisig.approveAsMulti(

501

threshold,

502

signatories.filter(addr => addr !== alice.address),

503

timepoint,

504

callHash,

505

0 // max weight

506

);

507

508

await approval1.signAndSend(alice, ({ status }) => {

509

if (status.isInBlock) {

510

console.log('Alice approved multisig transaction');

511

}

512

});

513

514

// Bob executes (second approval)

515

const execution = api.tx.multisig.asMulti(

516

threshold,

517

signatories.filter(addr => addr !== bob.address),

518

timepoint,

519

call,

520

0 // max weight

521

);

522

523

await execution.signAndSend(bob, ({ status, events }) => {

524

if (status.isInBlock) {

525

console.log('Bob executed multisig transaction');

526

527

events.forEach(({ event }) => {

528

if (api.events.multisig.MultisigExecuted.is(event)) {

529

const [approving, timepoint, multisig, callHash, result] = event.data;

530

console.log(`Multisig executed: ${result.isOk ? 'Success' : 'Failed'}`);

531

}

532

});

533

}

534

});

535

```

536

537

### Staking Transactions

538

539

```typescript

540

import { ApiPromise } from "@polkadot/api";

541

import { Keyring } from "@polkadot/keyring";

542

543

const api = await ApiPromise.create();

544

const keyring = new Keyring({ type: 'sr25519' });

545

const stash = keyring.addFromUri('//Alice');

546

const controller = keyring.addFromUri('//Alice//stash');

547

548

// Bond tokens for staking

549

const bondAmount = api.createType('Compact<Balance>', '1000000000000000'); // 100 DOT

550

const bond = api.tx.staking.bond(controller.address, bondAmount, 'Staked');

551

552

await bond.signAndSend(stash, ({ status, events }) => {

553

if (status.isInBlock) {

554

events.forEach(({ event }) => {

555

if (api.events.staking.Bonded.is(event)) {

556

const [stash, amount] = event.data;

557

console.log(`Bonded ${amount.toHuman()} from ${stash}`);

558

}

559

});

560

}

561

});

562

563

// Nominate validators

564

const validators = [

565

'1KvKReVmUiTc2LW2a4qyHy3PeGk1RbFw67rZE4TaFvZw6Z3',

566

'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'

567

];

568

569

const nominate = api.tx.staking.nominate(validators);

570

571

await nominate.signAndSend(controller, ({ status, events }) => {

572

if (status.isInBlock) {

573

events.forEach(({ event }) => {

574

if (api.events.staking.Nominated.is(event)) {

575

const [nominator, targets] = event.data;

576

console.log(`${nominator} nominated ${targets.length} validators`);

577

}

578

});

579

}

580

});

581

```

582

583

### RxJS Transaction Handling

584

585

```typescript

586

import { ApiRx } from "@polkadot/api";

587

import { Keyring } from "@polkadot/keyring";

588

import { switchMap, tap, filter } from "rxjs";

589

590

const api$ = ApiRx.create();

591

const keyring = new Keyring({ type: 'sr25519' });

592

const alice = keyring.addFromUri('//Alice');

593

594

// RxJS transaction pipeline

595

api$.pipe(

596

switchMap(api => {

597

const transfer = api.tx.balances.transferAllowDeath(

598

'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',

599

1000000000000

600

);

601

602

return transfer.signAndSend(alice);

603

}),

604

tap(result => console.log(`Status: ${result.status.type}`)),

605

filter(result => result.status.isFinalized)

606

).subscribe(result => {

607

console.log(`Transaction finalized: ${result.txHash}`);

608

609

// Process events

610

result.events.forEach(({ event }) => {

611

if (event.section === 'balances' && event.method === 'Transfer') {

612

const [from, to, amount] = event.data;

613

console.log(`Transfer: ${from} -> ${to}: ${amount.toHuman()}`);

614

}

615

});

616

});

617

```