JavaScript/TypeScript SDK
The foc-engine JavaScript/TypeScript SDK provides a comprehensive client library for interacting with foc.fun applications from JavaScript and TypeScript environments.
Installation
Install the SDK using your preferred package manager:
# npm
npm install foc-engine
# yarn
yarn add foc-engine
# pnpm
pnpm add foc-engine
Quick Start
import { FocEngine } from 'foc-engine';
// Initialize the engine
const engine = new FocEngine({
url: 'http://localhost:8080',
modules: ['registry', 'accounts', 'events', 'paymaster']
});
// Connect to the engine
await engine.connect();
// Start using modules
const registry = engine.getModule('registry');
const accounts = engine.getModule('accounts');
Configuration
Basic Configuration
const engine = new FocEngine({
url: 'http://localhost:8080', // Engine URL
wsUrl: 'ws://localhost:8081', // WebSocket URL for events
timeout: 30000, // Request timeout (ms)
retries: 3, // Retry failed requests
modules: ['registry', 'accounts'], // Modules to load
// Paymaster configuration with AVNU fallback
paymaster: {
primary: {
type: 'foc-engine',
endpoint: 'https://api.foc.fun/paymaster/sepolia'
},
fallback: {
type: 'avnu',
apiKey: process.env.AVNU_API_KEY // AVNU API key for fallback
}
}
});
Advanced Configuration
const engine = new FocEngine({
url: 'http://localhost:8080',
wsUrl: 'ws://localhost:8081',
// Request configuration
timeout: 30000,
retries: 3,
retryDelay: 1000,
// Module configuration
modules: {
registry: {
cache: true,
cacheTimeout: 300000 // 5 minutes
},
accounts: {
autoConnect: true,
defaultImplementation: 'openzeppelin'
},
events: {
reconnect: true,
reconnectDelay: 5000,
maxReconnectAttempts: 10
},
paymaster: {
enabled: true,
// AVNU fallback configuration
fallback: {
provider: 'avnu',
apiKey: process.env.AVNU_API_KEY,
endpoint: 'https://paymaster.avnu.fi',
timeout: 10000,
retries: 3
}
}
},
// Network configuration
network: 'devnet', // 'devnet', 'testnet', 'mainnet'
// Logging
debug: true,
logger: console
});
Core API
Engine Methods
connect()
Connect to the foc-engine:
await engine.connect();
// With connection options
await engine.connect({
timeout: 10000,
retries: 5
});
disconnect()
Disconnect from the engine:
await engine.disconnect();
getModule(name)
Get a module instance:
const registry = engine.getModule('registry');
const accounts = engine.getModule('accounts');
const events = engine.getModule('events');
const paymaster = engine.getModule('paymaster');
isConnected()
Check connection status:
if (engine.isConnected()) {
console.log('Engine is connected');
}
getStatus()
Get engine status:
const status = await engine.getStatus();
console.log(status);
// {
// connected: true,
// version: '1.0.0',
// modules: ['registry', 'accounts', 'events', 'paymaster'],
// network: 'devnet',
// blockNumber: 12345
// }
Contract Interaction
Deploying Contracts
// Deploy a contract
const contract = await engine.deploy({
contract: 'MyToken', // Contract name or path
constructorArgs: {
name: 'My Token',
symbol: 'MTK',
decimals: 18,
totalSupply: '1000000000000000000000'
}
});
console.log('Deployed at:', contract.address);
Contract Class
Work with deployed contracts:
// Get contract instance
const token = await engine.getContract('0x123...');
// Or from registry
const registry = engine.getModule('registry');
const token = await registry.getContract('MyToken');
// Call read function
const balance = await token.call('balanceOf', {
account: '0x456...'
});
// Send transaction
const tx = await token.invoke('transfer', {
to: '0x789...',
amount: '1000000000000000000'
});
// Wait for confirmation
await tx.wait();
console.log('Transaction confirmed:', tx.hash);
Transaction Options
// With custom options
const tx = await token.invoke('transfer', {
to: '0x789...',
amount: '1000'
}, {
account: '0xmyaccount...', // Specific account to use
maxFee: '1000000000000000', // Max fee in wei
nonce: 5, // Custom nonce
paymaster: true, // Use paymaster (with AVNU fallback)
waitForAccept: true // Wait for L2 acceptance
});
// Force specific paymaster provider
const txWithAvnu = await token.invoke('transfer', {
to: '0x789...',
amount: '1000'
}, {
account: '0xmyaccount...',
paymaster: {
provider: 'avnu', // Force AVNU paymaster
apiKey: process.env.AVNU_API_KEY
}
});
Multicall
Execute multiple calls in one transaction:
// Prepare calls
const calls = [
token.populateTransaction('approve', {
spender: '0xdex...',
amount: '1000000'
}),
dex.populateTransaction('swap', {
tokenIn: token.address,
tokenOut: '0xother...',
amountIn: '1000000'
})
];
// Execute multicall
const tx = await engine.multicall(calls);
await tx.wait();
Type Safety with TypeScript
Basic Types
import { FocEngine, Contract, Transaction } from 'foc-engine';
const engine: FocEngine = new FocEngine({
url: 'http://localhost:8080'
});
const contract: Contract = await engine.getContract('0x123...');
const tx: Transaction = await contract.invoke('transfer', {
to: '0x456...',
amount: '1000'
});
Generated Types
Generate TypeScript types from your contracts:
# Generate types
foc-engine generate-types --output ./types
Use generated types:
import { MyToken } from './types/MyToken';
const token = await engine.getContract<MyToken>('0x123...');
// Type-safe calls
const balance = await token.balanceOf({ account: '0x456...' });
const tx = await token.transfer({
to: '0x789...',
amount: BigInt('1000000000000000000')
});
Module Types
import {
Registry,
Accounts,
Events,
Paymaster
} from 'foc-engine';
const registry: Registry = engine.getModule('registry');
const accounts: Accounts = engine.getModule('accounts');
const events: Events = engine.getModule('events');
const paymaster: Paymaster = engine.getModule('paymaster');
Event Handling
Basic Event Subscription
const events = engine.getModule('events');
// Subscribe to events
const subscription = await events.subscribe({
contract: '0xtoken...',
event: 'Transfer',
callback: (event) => {
console.log(`Transfer: ${event.from} -> ${event.to}`);
}
});
// Unsubscribe later
subscription.unsubscribe();
Event Emitter Pattern
// Get event emitter
const emitter = await token.events();
// Listen to specific event
emitter.on('Transfer', (from, to, amount) => {
console.log(`Transfer: ${from} -> ${to}: ${amount}`);
});
// Listen to all events
emitter.on('*', (eventName, ...args) => {
console.log(`Event ${eventName}:`, args);
});
// Remove listeners
emitter.off('Transfer', handler);
emitter.removeAllListeners();
Paymaster Integration
AVNU Fallback Configuration
// Configure automatic AVNU fallback
const engine = new FocEngine({
network: 'sepolia',
paymaster: {
// Primary: FOC Engine paymaster
primary: {
type: 'foc-engine',
endpoint: 'https://api.foc.fun/paymaster/sepolia'
},
// Fallback: AVNU direct integration
fallback: {
type: 'avnu',
apiKey: process.env.AVNU_API_KEY,
endpoint: 'https://paymaster.avnu.fi',
// Fallback triggers
triggers: {
timeout: 10000,
retries: 3,
errorCodes: ['503', '504', 'TIMEOUT']
}
},
// Fallback behavior
fallbackOnError: true,
logFallbacks: true
}
});
Direct AVNU Integration
// Use AVNU paymaster only (no FOC Engine fallback needed)
const engine = new FocEngine({
network: 'sepolia',
paymaster: {
type: 'avnu-only',
apiKey: process.env.AVNU_API_KEY,
endpoint: 'https://paymaster.avnu.fi',
// AVNU-specific options
options: {
maxFeePercentage: 10, // Max 10% of transaction value as fee
validityPeriod: 3600, // 1 hour validity
gaslessTokens: ['ETH', 'USDC', 'USDT']
}
}
});
// All transactions will use AVNU paymaster
const paymaster = engine.getModule('paymaster');
const sponsoredTx = await paymaster.sponsor({
account: '0xuser...',
to: '0xcontract...',
selector: 'transfer',
calldata: ['0xrecipient...', '1000']
});
Paymaster Event Handling
// Monitor paymaster fallback events
engine.paymaster.on('fallbackTriggered', (event) => {
console.log('Paymaster fallback:', {
reason: event.reason,
primaryProvider: event.primary,
fallbackProvider: event.fallback,
transactionHash: event.txHash
});
});
// Track paymaster provider health
engine.paymaster.on('providerHealthChange', (event) => {
console.log('Provider health:', {
provider: event.provider,
status: event.status, // 'healthy', 'degraded', 'down'
latency: event.latency,
successRate: event.successRate
});
});
Error Handling
Error Types
import {
FocEngineError,
ConnectionError,
ContractError,
TransactionError,
ModuleError,
PaymasterError
} from 'foc-engine';
try {
await engine.connect();
} catch (error) {
if (error instanceof ConnectionError) {
console.error('Failed to connect:', error.message);
} else if (error instanceof ModuleError) {
console.error('Module error:', error.module, error.message);
} else if (error instanceof PaymasterError) {
console.error('Paymaster error:', error.provider, error.message);
}
}
Transaction Errors with Paymaster Fallback
try {
const tx = await token.invoke('transfer', {
to: '0x789...',
amount: '1000000'
}, {
paymaster: true // Enables automatic AVNU fallback
});
await tx.wait();
} catch (error) {
if (error instanceof TransactionError) {
switch (error.code) {
case 'INSUFFICIENT_BALANCE':
console.error('Not enough tokens');
break;
case 'REVERTED':
console.error('Transaction reverted:', error.reason);
break;
case 'TIMEOUT':
console.error('Transaction timed out');
break;
case 'PAYMASTER_UNAVAILABLE':
console.error('All paymasters failed:', error.providers);
break;
}
} else if (error instanceof PaymasterError) {
if (error.fallbackUsed) {
console.log('Transaction succeeded using AVNU fallback');
} else {
console.error('Paymaster failed:', error.message);
}
}
}
Utilities
Address Utilities
import { utils } from 'foc-engine';
// Validate address
if (utils.isValidAddress('0x123...')) {
console.log('Valid address');
}
// Format address
const formatted = utils.formatAddress('0x123...');
// Get address from public key
const address = utils.getAddressFromPublicKey('0xpubkey...');
Number Utilities
// Convert between units
const wei = utils.parseEther('1.5'); // 1.5 ETH to wei
const eth = utils.formatEther('1500000000000000000'); // wei to ETH
// BigInt utilities
const sum = utils.addBigInt('1000', '2000');
const product = utils.multiplyBigInt('1000', '2');
Encoding Utilities
// Encode function call
const calldata = utils.encodeCall('transfer', {
to: '0x789...',
amount: '1000'
});
// Decode event
const decoded = utils.decodeEvent('Transfer', eventData);
React Integration
Provider Component with AVNU Fallback
import { FocEngineProvider } from 'foc-engine/react';
function App() {
return (
<FocEngineProvider
config={{
url: 'http://localhost:8080',
modules: ['registry', 'accounts', 'events', 'paymaster'],
// AVNU fallback configuration
paymaster: {
primary: {
type: 'foc-engine',
endpoint: process.env.REACT_APP_FOC_PAYMASTER_URL
},
fallback: {
type: 'avnu',
apiKey: process.env.REACT_APP_AVNU_API_KEY
},
fallbackOnError: true
}
}}
>
<YourApp />
</FocEngineProvider>
);
}
Hooks with Paymaster
import {
useFocEngine,
useContract,
useAccount,
useEvents,
usePaymaster
} from 'foc-engine/react';
function TokenTransfer({ tokenAddress }) {
const engine = useFocEngine();
const token = useContract(tokenAddress);
const paymaster = usePaymaster();
const [isTransferring, setIsTransferring] = useState(false);
const transfer = async (to, amount) => {
setIsTransferring(true);
try {
// Automatically uses AVNU fallback if FOC paymaster fails
const tx = await token.invoke('transfer', { to, amount }, {
paymaster: true
});
await tx.wait();
console.log('Transfer successful');
} catch (error) {
console.error('Transfer failed:', error);
} finally {
setIsTransferring(false);
}
};
return (
<div>
<button
onClick={() => transfer('0x123...', '1000')}
disabled={isTransferring}
>
{isTransferring ? 'Transferring...' : 'Send Gasless Transfer'}
</button>
{/* Show paymaster status */}
<div>Paymaster Status: {paymaster.status}</div>
{paymaster.fallbackActive && (
<div>Using AVNU fallback</div>
)}
</div>
);
}
Testing
Mock Engine
import { MockEngine } from 'foc-engine/testing';
describe('MyComponent', () => {
let mockEngine;
beforeEach(() => {
mockEngine = new MockEngine();
mockEngine.mockContract('0xtoken...', {
balanceOf: async ({ account }) => '1000000',
transfer: async ({ to, amount }) => ({
hash: '0xmocktx...',
wait: async () => ({ status: 'success' })
})
});
});
test('should transfer tokens', async () => {
const token = await mockEngine.getContract('0xtoken...');
const tx = await token.invoke('transfer', {
to: '0x789...',
amount: '1000'
});
expect(tx.hash).toBe('0xmocktx...');
});
});
Test Utilities
import { testUtils } from 'foc-engine/testing';
// Create test accounts
const accounts = await testUtils.createTestAccounts(5);
// Fund accounts
await testUtils.fundAccounts(accounts, '1000000000000000000');
// Deploy test token
const token = await testUtils.deployTestToken({
name: 'Test Token',
symbol: 'TEST',
supply: '1000000'
});
Performance
Connection Pooling
const engine = new FocEngine({
url: 'http://localhost:8080',
pool: {
min: 2,
max: 10,
idleTimeout: 30000
}
});
Caching
// Enable caching
const engine = new FocEngine({
cache: {
enabled: true,
ttl: 300000, // 5 minutes
max: 1000 // Max cached items
}
});
// Cache specific calls
const balance = await token.call('balanceOf', {
account: '0x456...'
}, {
cache: true,
cacheKey: `balance-${account}`,
cacheTTL: 60000 // 1 minute
});
Batch Requests
// Batch multiple calls
const results = await engine.batch([
token.populateCall('balanceOf', { account: '0x123...' }),
token.populateCall('balanceOf', { account: '0x456...' }),
token.populateCall('totalSupply')
]);
console.log(results); // [balance1, balance2, totalSupply]
Migration Guide
From Web3.js
// Web3.js
const balance = await token.methods.balanceOf(account).call();
await token.methods.transfer(to, amount).send({ from: account });
// foc-engine
const balance = await token.call('balanceOf', { account });
await token.invoke('transfer', { to, amount }, { account });
From Ethers.js
// Ethers.js
const balance = await token.balanceOf(account);
const tx = await token.transfer(to, amount);
await tx.wait();
// foc-engine
const balance = await token.call('balanceOf', { account });
const tx = await token.invoke('transfer', { to, amount });
await tx.wait();
Best Practices
1. Resource Management
Always clean up resources:
const engine = new FocEngine({ /* config */ });
try {
await engine.connect();
// Use engine
} finally {
await engine.disconnect();
}
2. Error Handling
Implement comprehensive error handling:
async function safeTransfer(token, to, amount) {
try {
const tx = await token.invoke('transfer', { to, amount });
return await tx.wait();
} catch (error) {
if (error.code === 'INSUFFICIENT_BALANCE') {
throw new Error('Not enough tokens');
}
throw error;
}
}
3. Type Safety
Use TypeScript for better development experience:
interface TokenTransferParams {
to: string;
amount: bigint;
}
async function transfer(
token: Contract,
params: TokenTransferParams
): Promise<Transaction> {
return token.invoke('transfer', params);
}
Troubleshooting
Connection Issues
// Enable debug logging
const engine = new FocEngine({
url: 'http://localhost:8080',
debug: true
});
// Listen to connection events
engine.on('connected', () => console.log('Connected'));
engine.on('disconnected', () => console.log('Disconnected'));
engine.on('error', (error) => console.error('Error:', error));
Performance Issues
// Monitor performance
const metrics = await engine.getMetrics();
console.log({
requestsPerSecond: metrics.rps,
averageLatency: metrics.avgLatency,
activeConnections: metrics.connections
});
Related Code & Resources
Source Code
- Main SDK Repository: foc-engine.js
- Core SDK Implementation: foc-engine.js/src/core
- Module Implementations: foc-engine.js/src/modules
TypeScript Types
- Type Definitions: foc-engine.js/src/types
- Generated Contract Types: foc-engine.js/types
React Integration
- React Hooks: foc-engine.js/src/react
- Provider Components: foc-engine.js/src/react/providers
Examples & Templates
- SDK Examples: foc-engine.js/examples
- Integration Examples: foc-fun/examples
- Sample Applications: foc-examples
Testing & Development
- Test Suite: foc-engine.js/tests
- Mock Utilities: foc-engine.js/src/testing
- Development Tools: foc-engine.js/tools
Build & Configuration
- Build Scripts: foc-engine.js/scripts
- Configuration Files: foc-engine.js/config
Next Steps
- Explore the Getting Started guide
- Learn about Modules
- Check out example applications