Overview
The TransactionService is the central service for managing all transaction-related operations in SubWallet Extension. It handles transaction validation, signing, submission, and monitoring across Substrate, EVM, Ton, Cardano, and Bitcoin networks.
Key Features
- Multi-chain transaction support (Substrate, EVM, Ton, Cardano, Bitcoin)
- Transaction validation and error checking
- Fee estimation
- Transaction signing and submission
- Transaction status monitoring
- Multisig and proxy account support
- Process-based transaction flows
Class: TransactionService
Constructor
constructor(state: KoniState)
The global state object of the extension
Core Transaction Methods
handleTransaction()
Validates and executes a transaction.
async handleTransaction(
transaction: SWTransactionInput
): Promise<SWTransactionResponse>
transaction
SWTransactionInput
required
Transaction input dataType of chain (substrate, evm, ton, cardano, bitcoin)
Type of transaction (TRANSFER_BALANCE, STAKING_BOND, etc.)
Transaction-specific data
Pre-built transaction object (optional)
Returns:
Transaction response with ID, hash, and validation resultsTransaction hash after submission
Array of validation errors
Estimated transaction fees
Usage:
const response = await transactionService.handleTransaction({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
chain: 'polkadot',
chainType: ChainType.SUBSTRATE,
extrinsicType: ExtrinsicType.TRANSFER_BALANCE,
data: {
to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
value: '1000000000000', // 1 DOT
tokenSlug: 'polkadot-NATIVE-DOT'
}
});
if (response.errors.length === 0) {
console.log('Transaction submitted:', response.extrinsicHash);
} else {
console.error('Transaction failed:', response.errors);
}
validateTransaction()
Validates a transaction without submitting it.
async validateTransaction(
transaction: SWTransactionInput
): Promise<SWTransactionResponse>
Returns:
Validation response with errors, warnings, and fee estimates
Usage:
const validation = await transactionService.validateTransaction({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
chain: 'polkadot',
chainType: ChainType.SUBSTRATE,
extrinsicType: ExtrinsicType.TRANSFER_BALANCE,
data: { /* ... */ }
});
if (validation.errors.length > 0) {
console.error('Validation failed:', validation.errors);
} else {
console.log('Estimated fee:', validation.estimateFee);
}
Transaction Monitoring
getTransaction()
Retrieves a transaction by ID.
getTransaction(id: string): SWTransactionBase | undefined
getTransactionSubject()
Subscribes to all transaction updates.
getTransactionSubject(): BehaviorSubject<Record<string, SWTransactionBase>>
Returns:
Observable emitting transaction map updates
Usage:
transactionService.getTransactionSubject().subscribe((transactions) => {
Object.values(transactions).forEach(tx => {
console.log(`${tx.id}: ${tx.status}`);
});
});
Advanced Features
handleWrappedTransaction()
Handles multisig or proxy-wrapped transactions.
async handleWrappedTransaction(
transaction: SWTransactionInput
): Promise<SWTransactionResponse>
Description:
This method is used when a transaction needs to be wrapped by a multisig or proxy account. The flow:
- Creates base transaction (marked as WRAPPABLE)
- User selects signer (multisig signatory or proxy)
- Builds wrapped transaction
- Submits wrapped transaction
- Forwards events to base transaction
Usage:
// Base transaction (will be wrapped)
const baseTransaction = {
address: 'MULTISIG_ADDRESS',
chain: 'polkadot',
chainType: ChainType.SUBSTRATE,
extrinsicType: ExtrinsicType.TRANSFER_BALANCE,
data: { /* ... */ },
wrappingStatus: SubstrateTransactionWrappingStatus.WRAPPABLE
};
// This will create the wrapped transaction
const response = await transactionService.handleWrappedTransaction({
...baseTransaction,
data: {
transactionId: 'BASE_TX_ID',
multisigMetadata: {
threshold: 2,
signatories: ['ADDRESS_1', 'ADDRESS_2', 'ADDRESS_3']
}
},
wrappingStatus: SubstrateTransactionWrappingStatus.WRAP_RESULT
});
Transaction Events
Transactions emit events through EventEmitter:
Event Types
interface TransactionEventMap {
send: (response: TransactionEventResponse) => void;
signed: (response: TransactionEventResponse) => void;
extrinsicHash: (response: TransactionEventResponse) => void;
error: (response: TransactionEventResponse) => void;
success: (response: TransactionEventResponse) => void;
timeout: (response: TransactionEventResponse) => void;
}
Listening to Events
const transaction = await transactionService.addTransaction(txInput);
transaction.on('send', (data) => {
console.log('Transaction sent to network');
});
transaction.on('signed', (data) => {
console.log('Transaction signed:', data.id);
});
transaction.on('extrinsicHash', (data) => {
console.log('Transaction hash received:', data.extrinsicHash);
});
transaction.on('success', (data) => {
console.log('Transaction successful!', data);
});
transaction.on('error', (data) => {
console.error('Transaction failed:', data.errors);
});
transaction.on('timeout', (data) => {
console.warn('Transaction timed out');
});
Process-Based Transactions
For multi-step transactions (e.g., swap with approval):
const step: BriefProcessStep = {
processId: 'swap-process-123',
stepId: 1,
name: 'Approve Token'
};
const txResponse = await transactionService.handleTransaction({
address: '0x123...',
chain: 'ethereum',
chainType: ChainType.EVM,
extrinsicType: ExtrinsicType.TOKEN_APPROVE,
data: { /* approval data */ },
step
});
// Service automatically updates process status
// and tracks multi-step flows
Transaction Types
ExtrinsicType
enum ExtrinsicType {
TRANSFER_BALANCE = 'TRANSFER_BALANCE',
TRANSFER_TOKEN = 'TRANSFER_TOKEN',
TRANSFER_XCM = 'TRANSFER_XCM',
SEND_NFT = 'SEND_NFT',
STAKING_BOND = 'STAKING_BOND',
STAKING_UNBOND = 'STAKING_UNBOND',
STAKING_CLAIM_REWARD = 'STAKING_CLAIM_REWARD',
STAKING_WITHDRAW = 'STAKING_WITHDRAW',
JOIN_YIELD_POOL = 'JOIN_YIELD_POOL',
MINT_VDOT = 'MINT_VDOT',
MINT_LDOT = 'MINT_LDOT',
UNSTAKE_VDOT = 'UNSTAKE_VDOT',
SWAP = 'SWAP',
// ... many more
}
TransactionStatus
enum ExtrinsicStatus {
QUEUED = 'queued',
SUBMITTING = 'submitting',
PROCESSING = 'processing',
SUCCESS = 'success',
FAIL = 'fail',
CANCELLED = 'cancelled',
TIMEOUT = 'timeout'
}
Error Handling
TransactionError
class TransactionError {
errorType: TransactionErrorType;
message: string;
constructor(errorType: TransactionErrorType, message?: string);
}
Common Error Types
enum BasicTxErrorType {
INTERNAL_ERROR = 'INTERNAL_ERROR',
INVALID_PARAMS = 'INVALID_PARAMS',
UNSUPPORTED = 'UNSUPPORTED',
USER_REJECT_REQUEST = 'USER_REJECT_REQUEST',
UNABLE_TO_SIGN = 'UNABLE_TO_SIGN',
DUPLICATE_TRANSACTION = 'DUPLICATE_TRANSACTION',
TIMEOUT = 'TIMEOUT',
CHAIN_DISCONNECTED = 'CHAIN_DISCONNECTED',
NOT_ENOUGH_BALANCE = 'NOT_ENOUGH_BALANCE',
INVALID_ADDRESS = 'INVALID_ADDRESS'
}
Example: Complete Transfer Flow
import TransactionService from '@subwallet/extension-base/services/transaction-service';
const transactionService = new TransactionService(state);
// 1. Validate transaction first
const validation = await transactionService.validateTransaction({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
chain: 'polkadot',
chainType: ChainType.SUBSTRATE,
extrinsicType: ExtrinsicType.TRANSFER_BALANCE,
data: {
to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
value: '1000000000000',
tokenSlug: 'polkadot-NATIVE-DOT'
}
});
if (validation.errors.length > 0) {
console.error('Validation errors:', validation.errors);
return;
}
console.log('Estimated fee:', validation.estimateFee);
// 2. If validation passes, submit transaction
const response = await transactionService.handleTransaction({
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
chain: 'polkadot',
chainType: ChainType.SUBSTRATE,
extrinsicType: ExtrinsicType.TRANSFER_BALANCE,
data: {
to: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
value: '1000000000000',
tokenSlug: 'polkadot-NATIVE-DOT'
},
eventsHandler: (emitter) => {
emitter.on('signed', (data) => {
console.log('Transaction signed!');
});
emitter.on('success', (data) => {
console.log('Transfer successful!', data.extrinsicHash);
});
emitter.on('error', (data) => {
console.error('Transfer failed:', data.errors);
});
}
});
console.log('Transaction ID:', response.id);
console.log('Transaction Hash:', response.extrinsicHash);
Fee Estimation
The service automatically estimates fees for all transaction types:
const validation = await transactionService.validateTransaction(txInput);
if (validation.estimateFee) {
console.log('Fee:', validation.estimateFee.value);
console.log('Fee symbol:', validation.estimateFee.symbol);
console.log('Fee decimals:', validation.estimateFee.decimals);
}
Best Practices
- Always validate before submitting: Use
validateTransaction() first
- Handle errors gracefully: Check
errors array in response
- Monitor transaction status: Subscribe to transaction events
- Use event handlers: Attach event listeners for real-time updates
- Check balance before transfer: Service validates but explicit checks are good
- Handle timeouts: Transactions can timeout, implement retry logic if needed