Skip to main content

Architecture Overview

TokenOps SDK is designed with modularity, type safety, and extensibility as core principles. This guide explains the overall architecture and design decisions that make the SDK powerful and developer-friendly.

High-Level Architecture

Core Principles

1. Factory Pattern

All contract deployments use the factory pattern for consistent, upgradeable deployments:

// Factory creates contracts with consistent patterns
const factory = new TokenVestingManagerFactory(factoryAddress, provider);
const result = await factory.newTokenVestingManager(/* params */);

// Manager handles contract interactions
const manager = new TokenVestingManager(result.contractAddress, tokenAddress, provider);

2. Type Safety

Full TypeScript support with comprehensive type definitions:

// All parameters are typed
interface VestingParams {
recipient: string;
startTimestamp: bigint;
endTimestamp: bigint;
cliffAmount: bigint;
linearVestAmount: bigint;
isRevocable: boolean;
}

3. Event-Driven Architecture

All contracts emit typed events that are automatically parsed:

interface VestingCreatedEvent {
vestingId: bigint;
recipient: string;
amount: bigint;
startTime: bigint;
endTime: bigint;
}

Design Patterns

1. Factory + Manager Pattern

Each contract type follows a consistent pattern:

// 1. Factory for deployment
class ContractFactory {
constructor(factoryAddress: string, provider: IProviderAdapter) {}
async newContract(...params): Promise<DeploymentResult> {}
}

// 2. Manager for interactions
class ContractManager {
constructor(contractAddress: string, provider: IProviderAdapter) {}
async performAction(...params): Promise<ActionResult> {}
async queryState(...params): Promise<StateResult> {}
}

2. Provider Strategy Pattern

Different blockchain libraries are supported through adapters:

abstract class BaseProviderAdapter implements IProviderAdapter {
abstract call(params: CallParams): Promise<any>;
abstract sendTransaction(params: TransactionParams): Promise<string>;
// ... other abstract methods
}

class ViemProviderAdapter extends BaseProviderAdapter {
// Viem-specific implementation
}

class EthersProviderAdapter extends BaseProviderAdapter {
// Ethers-specific implementation
}

3. Configuration Object Pattern

Complex operations use configuration objects:

interface VestingConfig {
recipient: string;
schedule: VestingSchedule;
cliff?: CliffConfig;
revocable: boolean;
funding: FundingConfig;
}

await manager.createVesting(config);

Data Flow

1. Contract Deployment Flow

2. Contract Interaction Flow

Error Handling

1. Error Hierarchy

abstract class TokenOpsError extends Error {
abstract code: string;
abstract userMessage: string;
}

class TransactionError extends TokenOpsError {
code = 'TRANSACTION_FAILED';
constructor(public txHash: string, public reason: string) {
super(`Transaction failed: ${reason}`);
}
}

class ContractError extends TokenOpsError {
code = 'CONTRACT_ERROR';
constructor(public contractAddress: string, public method: string) {
super(`Contract call failed: ${method} on ${contractAddress}`);
}
}

2. Error Recovery

try {
await manager.createVesting(params);
} catch (error) {
if (error instanceof TransactionError) {
// Handle transaction failures
console.log(`Transaction ${error.txHash} failed: ${error.reason}`);
} else if (error instanceof ContractError) {
// Handle contract interaction errors
console.log(`Contract error on ${error.contractAddress}`);
}
}

Performance Considerations

1. Batch Operations

Multiple operations can be batched for efficiency:

const batch = [
manager.createVesting(params1),
manager.createVesting(params2),
manager.createVesting(params3)
];

const results = await Promise.all(batch);

2. Caching Strategy

Frequently accessed data is cached:

class APIClient {
private cache = new Map<string, { data: any, timestamp: number }>();

async getData(key: string): Promise<any> {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < 60000) {
return cached.data;
}

const data = await this.fetchFromAPI(key);
this.cache.set(key, { data, timestamp: Date.now() });
return data;
}
}

Extensibility

1. Plugin System

The SDK supports plugins for extended functionality:

interface Plugin {
name: string;
init(sdk: TokenOpsSDK): void;
beforeTransaction?(params: TransactionParams): Promise<TransactionParams>;
afterTransaction?(result: TransactionResult): Promise<void>;
}

class LoggingPlugin implements Plugin {
name = 'logging';

init(sdk: TokenOpsSDK) {
// Initialize plugin
}

async beforeTransaction(params: TransactionParams) {
console.log('Transaction starting:', params);
return params;
}
}

Security Model

1. Input Validation

All inputs are validated at the SDK level:

function validateAddress(address: string): void {
if (!isValidAddress(address)) {
throw new ValidationError('Invalid Ethereum address');
}
}

function validateAmount(amount: bigint): void {
if (amount <= 0n) {
throw new ValidationError('Amount must be positive');
}
}

Testing Architecture

The SDK includes comprehensive testing at multiple levels:

1. Unit Tests

  • Individual function testing
  • Mocked dependencies
  • Edge case coverage

2. Integration Tests

  • Contract interaction testing
  • Provider compatibility testing
  • End-to-end workflows

3. Network Tests

  • Testnet deployment testing
  • Real transaction testing
  • Performance benchmarking