Skip to main content

Connect Integration Best Practices

Initialization and Configuration

The manifest identifies your application to users:
await TrezorConnect.init({
  manifest: {
    email: '[email protected]',  // Your contact email
    appUrl: 'https://example.com'     // Your app URL
  }
});
Why it matters:
  • Users see your app name and URL on their device
  • Helps with trust and security
  • Required for Connect to function properly
Call TrezorConnect.init() only once when your app starts:
// Good: Initialize on app startup
async function initializeApp() {
  await TrezorConnect.init({ manifest: {...} });
  // Rest of app initialization
}

// Bad: Initializing before every method call
async function getAddress() {
  await TrezorConnect.init({ manifest: {...} }); // Don't do this
  return await TrezorConnect.getAddress({...});
}
Always handle potential initialization failures:
try {
  await TrezorConnect.init({
    manifest: {
      email: '[email protected]',
      appUrl: 'https://example.com'
    }
  });
} catch (error) {
  console.error('Failed to initialize TrezorConnect:', error);
  // Show user-friendly error message
  // Provide fallback or retry mechanism
}

Error Handling

Never assume a method succeeded:
const result = await TrezorConnect.getAddress({
  path: "m/49'/0'/0'/0/0",
  coin: 'btc'
});

if (result.success) {
  // Success path
  const address = result.payload.address;
  console.log('Address:', address);
} else {
  // Error path
  console.error('Error:', result.payload.error);
  handleError(result.payload.code, result.payload.error);
}
Translate technical errors into user-friendly messages:
function handleTrezorError(code, error) {
  const userMessages = {
    'Device_CallInProgress': 'Please complete the current action on your Trezor.',
    'Device_NotFound': 'Please connect your Trezor device.',
    'Failure_PinInvalid': 'Incorrect PIN. Please try again.',
    'Failure_ActionCancelled': 'Action cancelled.',
  };
  
  const message = userMessages[code] || 
    `An error occurred: ${error}. Please try again.`;
  
  showUserMessage(message);
}
Account for different device states:
async function safelyCallTrezor(method, params) {
  const result = await TrezorConnect[method](params);
  
  if (!result.success) {
    switch (result.payload.code) {
      case 'Device_NotFound':
        await promptUserToConnectDevice();
        return safelyCallTrezor(method, params);
      
      case 'Device_InitializeFailed':
        await promptUserToUnlockDevice();
        return safelyCallTrezor(method, params);
      
      case 'Failure_ActionCancelled':
        // User cancelled, don't retry
        return result;
      
      default:
        // Other errors
        return result;
    }
  }
  
  return result;
}

Security Best Practices

NEVER try to export or access private keys:
// WRONG: Never try to get private keys
// Private keys should NEVER leave the device

// RIGHT: Use signing methods
const result = await TrezorConnect.signTransaction({
  // Transaction params
});
Trezor’s security model ensures private keys never leave the device.
Always show addresses on the device for verification:
const result = await TrezorConnect.getAddress({
  path: "m/49'/0'/0'/0/0",
  coin: 'btc',
  showOnTrezor: true  // Always true for receiving addresses
});
Why it matters:
  • Protects against malware showing fake addresses
  • Users can verify the address on trusted device screen
  • Critical for security
Always validate transaction data before signing:
function validateTransaction(tx) {
  // Check recipient address format
  if (!isValidAddress(tx.recipient)) {
    throw new Error('Invalid recipient address');
  }
  
  // Check amount is positive
  if (tx.amount <= 0) {
    throw new Error('Amount must be positive');
  }
  
  // Check for suspicious amounts
  if (tx.amount > MAX_REASONABLE_AMOUNT) {
    const confirmed = confirm(
      `Large amount detected: ${tx.amount}. Continue?`
    );
    if (!confirmed) return false;
  }
  
  return true;
}
Always use HTTPS for production deployments:
// Development
const appUrl = process.env.NODE_ENV === 'production'
  ? 'https://example.com'  // HTTPS in production
  : 'http://localhost:3000'; // HTTP OK for local dev

await TrezorConnect.init({
  manifest: {
    email: '[email protected]',
    appUrl: appUrl
  }
});

Performance Optimization

Use bundle methods to request multiple addresses:
// Good: Batch request
const result = await TrezorConnect.getAddress({
  bundle: [
    { path: "m/49'/0'/0'/0/0", coin: 'btc' },
    { path: "m/49'/0'/0'/0/1", coin: 'btc' },
    { path: "m/49'/0'/0'/0/2", coin: 'btc' },
  ]
});

// Bad: Multiple individual requests
const addr1 = await TrezorConnect.getAddress({...});
const addr2 = await TrezorConnect.getAddress({...});
const addr3 = await TrezorConnect.getAddress({...});
Cache public data like addresses and public keys:
class AddressCache {
  constructor() {
    this.cache = new Map();
  }
  
  async getAddress(path, coin) {
    const key = `${path}-${coin}`;
    
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    
    const result = await TrezorConnect.getAddress({
      path,
      coin,
      showOnTrezor: false  // Don't show for cached lookups
    });
    
    if (result.success) {
      this.cache.set(key, result.payload.address);
      return result.payload.address;
    }
    
    throw new Error(result.payload.error);
  }
}
Set reasonable timeouts for user actions:
async function getAddressWithTimeout(params, timeout = 60000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Operation timeout')), timeout);
  });
  
  try {
    const result = await Promise.race([
      TrezorConnect.getAddress(params),
      timeoutPromise
    ]);
    return result;
  } catch (error) {
    if (error.message === 'Operation timeout') {
      showUserMessage('Operation timed out. Please try again.');
    }
    throw error;
  }
}

Suite Development Best Practices

Code Organization

Use conventional commit format for all commits:
# Format: <type>(<scope>): <description>

feat(connect): add Tron support
fix(suite): resolve balance calculation bug
docs(readme): update installation instructions
test(connect): add unit tests for tronGetAddress
chore(deps): update dependencies
See COMMITS.md for details.
Enable strict TypeScript checking:
// Good: Properly typed
interface TransactionParams {
  recipient: string;
  amount: number;
  coin: string;
}

function createTransaction(params: TransactionParams): Transaction {
  // Implementation
}

// Bad: Using 'any'
function createTransaction(params: any): any {
  // Don't do this
}
Always include tests with new features:
// Feature implementation
export function calculateFee(amount: number, feeRate: number): number {
  return amount * feeRate;
}

// Test
describe('calculateFee', () => {
  it('calculates fee correctly', () => {
    expect(calculateFee(100, 0.01)).toBe(1);
  });
  
  it('handles zero amount', () => {
    expect(calculateFee(0, 0.01)).toBe(0);
  });
  
  it('handles zero fee rate', () => {
    expect(calculateFee(100, 0)).toBe(0);
  });
});

Performance

Keep bundle sizes minimal:
// Good: Import only what you need
import { getAddress } from '@trezor/connect';

// Bad: Import entire library
import * as TrezorConnect from '@trezor/connect';
Follow React optimization patterns:
// Use memo for expensive computations
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(data);
}, [data]);

// Use callback for event handlers
const handleClick = useCallback(() => {
  // Handler logic
}, [dependencies]);

// Prevent unnecessary re-renders
const MemoizedComponent = React.memo(MyComponent);

Testing

Use the emulator for integration testing:
import TrezorConnect from '@trezor/connect';
import { startEmulator, stopEmulator } from '@trezor/user-env';

beforeAll(async () => {
  await startEmulator();
});

afterAll(async () => {
  await stopEmulator();
});

test('getAddress returns valid address', async () => {
  const result = await TrezorConnect.getAddress({
    path: "m/49'/0'/0'/0/0",
    coin: 'btc'
  });
  
  expect(result.success).toBe(true);
  expect(result.payload.address).toMatch(/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/);
});
Mock external services in unit tests:
// Mock blockchain API
jest.mock('@trezor/blockchain-link', () => ({
  getBalance: jest.fn().mockResolvedValue({ balance: '1000000' }),
  getTransactions: jest.fn().mockResolvedValue([]),
}));

test('fetches balance correctly', async () => {
  const balance = await getAccountBalance('btc', 'address');
  expect(balance).toBe('1000000');
});

Documentation Best Practices

Always document public methods and interfaces:
/**
 * Gets a cryptocurrency address from the Trezor device.
 * 
 * @param path - BIP44 derivation path
 * @param coin - Cryptocurrency symbol (e.g., 'btc', 'eth')
 * @param showOnTrezor - Whether to display address on device
 * @returns Promise resolving to address result
 * 
 * @example
 * ```typescript
 * const result = await getAddress(
 *   "m/49'/0'/0'/0/0",
 *   'btc',
 *   true
 * );
 * ```
 */
export async function getAddress(
  path: string,
  coin: string,
  showOnTrezor: boolean = false
): Promise<AddressResult> {
  // Implementation
}
Update README.md when adding features:
  • Add to feature list
  • Update setup instructions if needed
  • Include usage examples
  • Update dependencies section
Provide context in error messages:
// Good: Helpful error message
throw new Error(
  `Invalid derivation path: ${path}. ` +
  `Expected format: m/44'/0'/0'/0/0`
);

// Bad: Vague error message
throw new Error('Invalid path');

Additional Resources

Migration Guide

Learn how to upgrade versions

Troubleshooting

Solve common issues

FAQ

Find answers to common questions

Security

Review security guidelines

Build docs developers (and LLMs) love