Skip to main content

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​