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
```