Best Practices
Function Arguments​
- Type Safety: Ensure arguments match the contract function signature
- Value Formatting: Use proper units (wei for ETH amounts, etc.)
- Address Validation: Verify Ethereum addresses are valid and checksummed
Idempotency​
- Required Field: Idempotency key is mandatory for all transaction executions
- Unique Keys: Use unique idempotency keys to prevent duplicate transactions
- Key Format: Use descriptive, unique identifiers (e.g.,
user-123-mint-456
) - Retry Safety: Same idempotency key returns existing transaction if already created
Status Monitoring​
- Polling Strategy: Check transaction status periodically, not continuously
- Timeout Handling: Set reasonable timeouts for transaction confirmation
- Error Recovery: Handle failed transactions gracefully in your application
Gas Management​
- Gas Estimation: MOUNTAIN automatically estimates gas limits with a 30% buffer
- Minimum Gas Limit: 200,000 gas enforced for all transactions
- Gas Pricing: Uses current network gas prices for optimal transaction confirmation
- Cost Responsibility: Projects are responsible for gas fees consumed by their transactions
Error Handling​
Common Error Scenarios​
Insufficient Gas
- Transaction fails during broadcast with
broadcast-failed
status - Automatic gas estimation with 30% buffer applied
- Minimum gas limit of 200,000 enforced for all transactions
Contract Revert
- Transaction broadcasts successfully but reverts on blockchain
- Status becomes
reverted
with revert reason in error message - Gas is still consumed and recorded
Network Issues
- Broadcast failures result in
broadcast-failed
status - Monitor failures result in
monitor-failed
status - Automatic retry logic with exponential backoff (for applicable errors)
Nonce Conflicts
- Handled during broadcast phase with automatic nonce management
- Race condition prevention in
processing
status
Error Handling Example​
import { TransactionApi } from '@kyuzan/mountain-public-api-client';
async function executeTransactionWithErrorHandling() {
const transactionApi = new TransactionApi();
try {
const transaction = await transactionApi.executeTransaction({
contractActivatedFunctionId: 1,
functionArgs: [
'0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
'1000000000000000000',
],
idempotencyKey: `transfer-${Date.now()}`,
});
// Monitor transaction with timeout
const maxWaitTime = 5 * 60 * 1000; // 5 minutes
const startTime = Date.now();
let status = transaction.transaction.status;
while (
(status === 'created' || status === 'processing' || status === 'broadcasted') &&
(Date.now() - startTime < maxWaitTime)
) {
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds
const updated = await transactionApi.getTransaction({
transactionId: transaction.transaction.id,
});
status = updated.transaction.status;
}
// Handle final status
switch (status) {
case 'succeeded':
console.log('Transaction succeeded:', updated.transaction.transactionHash);
return updated.transaction;
case 'reverted':
console.error('Transaction reverted:', updated.transaction.errorMessage);
throw new Error(`Transaction reverted: ${updated.transaction.errorMessage}`);
case 'broadcast-failed':
console.error('Broadcast failed:', updated.transaction.errorMessage);
throw new Error(`Broadcast failed: ${updated.transaction.errorMessage}`);
case 'monitor-failed':
console.warn('Monitor failed, transaction may still be pending');
// Consider checking blockchain directly or retrying monitoring
return updated.transaction;
default:
if (Date.now() - startTime >= maxWaitTime) {
console.warn('Transaction timeout, still pending');
return updated.transaction;
}
throw new Error(`Unexpected status: ${status}`);
}
} catch (error) {
console.error('Transaction execution failed:', error.message);
throw error;
}
}
Complete Implementation Example​
Here's a robust implementation following all best practices:
import { TransactionApi } from '@kyuzan/mountain-public-api-client';
class TransactionManager {
constructor(contractActivatedFunctionId) {
this.transactionApi = new TransactionApi();
this.contractActivatedFunctionId = contractActivatedFunctionId;
}
async executeTransaction(functionArgs, idempotencyKey) {
try {
// Validate inputs
this.validateInputs(functionArgs, idempotencyKey);
// Execute transaction
const transaction = await this.transactionApi.executeTransaction({
contractActivatedFunctionId: this.contractActivatedFunctionId,
functionArgs,
idempotencyKey,
});
console.log('Transaction created:', transaction.transaction.id);
// Monitor with robust error handling
return await this.monitorTransaction(transaction.transaction.id);
} catch (error) {
console.error('Transaction failed:', error.message);
throw error;
}
}
validateInputs(functionArgs, idempotencyKey) {
if (!idempotencyKey || idempotencyKey.trim() === '') {
throw new Error('Idempotency key is required');
}
if (!Array.isArray(functionArgs)) {
throw new Error('Function arguments must be an array');
}
}
async monitorTransaction(transactionId, maxWaitTime = 5 * 60 * 1000) {
const startTime = Date.now();
const pollInterval = 3000; // 3 seconds
while (Date.now() - startTime < maxWaitTime) {
const result = await this.transactionApi.getTransaction({ transactionId });
const status = result.transaction.status;
console.log(`Transaction ${transactionId} status: ${status}`);
if (status === 'succeeded') {
return result.transaction;
}
if (status === 'reverted' || status === 'broadcast-failed') {
throw new Error(`Transaction failed with status ${status}: ${result.transaction.errorMessage}`);
}
if (status === 'monitor-failed') {
console.warn('Monitor failed, transaction may still be pending');
return result.transaction;
}
// Continue monitoring if still processing
if (status === 'created' || status === 'processing' || status === 'broadcasted') {
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
throw new Error(`Unexpected transaction status: ${status}`);
}
throw new Error('Transaction monitoring timeout');
}
}
// Usage
const manager = new TransactionManager(1); // contract activated function ID
manager.executeTransaction(
['0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', '1000000000000000000'],
`mint-${Date.now()}`
)
.then(transaction => console.log('Success:', transaction.transactionHash))
.catch(error => console.error('Failed:', error.message));
Next Steps​
- Learn about transaction lifecycle for advanced monitoring
- Understand system wallet integration for security considerations
- Get started with basic transactions