Add Oracle Aggregator and CCIP Integration

- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
This commit is contained in:
defiQUG
2025-12-12 14:57:48 -08:00
parent a1466e4005
commit 1fb7266469
1720 changed files with 241279 additions and 16 deletions

64
sdk/src/config.ts Normal file
View File

@@ -0,0 +1,64 @@
/**
* Configuration for Tatum SDK integration with ChainID 138
*/
// Load environment variables
import * as dotenv from 'dotenv';
dotenv.config();
export const CHAIN_ID = 138;
export const CHAIN_ID_HEX = '0x8a'; // 138 in hex
export const CHAIN_NAME = 'DeFi Oracle Meta Mainnet';
/**
* Default RPC endpoints
* Update these to point to your deployed RPC nodes
*/
export const DEFAULT_RPC_URL = process.env.RPC_URL || 'http://localhost:8545';
export const DEFAULT_WS_URL = process.env.WS_URL || 'ws://localhost:8546';
/**
* Network configuration for ChainID 138
*/
export const NETWORK_CONFIG = {
chainId: CHAIN_ID,
chainIdHex: CHAIN_ID_HEX,
name: CHAIN_NAME,
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: {
default: {
http: [DEFAULT_RPC_URL],
webSocket: [DEFAULT_WS_URL],
},
},
blockExplorers: {
default: {
name: 'Blockscout',
url: process.env.EXPLORER_URL || 'https://explorer.d-bis.org',
},
},
};
/**
* Tatum SDK configuration
*/
export interface TatumConfig {
rpcUrl?: string;
wsUrl?: string;
verbose?: boolean;
apiKey?: string; // Optional: for Tatum cloud features (not used for custom RPC)
}
export const getTatumConfig = (config?: TatumConfig): TatumConfig => {
return {
rpcUrl: config?.rpcUrl || DEFAULT_RPC_URL,
wsUrl: config?.wsUrl || DEFAULT_WS_URL,
verbose: config?.verbose ?? true,
apiKey: config?.apiKey,
};
};

View File

@@ -0,0 +1,53 @@
/**
* Basic usage example: Connect to ChainID 138 and query chain data
*/
import { initTatumSDK, verifyConnection } from '../tatum-client';
import { CHAIN_ID, CHAIN_NAME } from '../config';
async function main() {
console.log(`Connecting to ${CHAIN_NAME} (ChainID ${CHAIN_ID})...`);
// Initialize Tatum SDK with custom RPC URL
const tatum = await initTatumSDK({
rpcUrl: process.env.RPC_URL || 'http://localhost:8545',
verbose: true,
});
// Verify connection and chain ID
console.log('\n=== Verifying Connection ===');
const connectionInfo = await verifyConnection(tatum);
console.log('Connection verified!');
console.log(`Chain ID: ${connectionInfo.chainId} (${connectionInfo.chainIdHex})`);
console.log(`Current Block: ${connectionInfo.blockNumber}`);
console.log(`Network Version: ${connectionInfo.netVersion}`);
console.log(`Syncing: ${connectionInfo.syncing}`);
// Query additional chain data
console.log('\n=== Querying Chain Data ===');
// Get latest block
const latestBlock = await tatum.rpc.request('eth_getBlockByNumber', ['latest', false]);
console.log('Latest Block:', JSON.stringify(latestBlock, null, 2));
// Get gas price
const gasPrice = await tatum.rpc.request('eth_gasPrice', []);
console.log('Gas Price:', gasPrice);
// Get peer count
const peerCount = await tatum.rpc.request('net_peerCount', []);
console.log('Peer Count:', peerCount);
// Get balance of an address (example)
const exampleAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
const balance = await tatum.rpc.request('eth_getBalance', [exampleAddress, 'latest']);
console.log(`Balance of ${exampleAddress}:`, balance);
console.log('\n=== Basic Usage Example Complete ===');
}
main().catch((error) => {
console.error('Error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,114 @@
/**
* Contract deployment example: Deploy a simple contract on ChainID 138
*/
import { ethers } from 'ethers';
import { initTatumSDK } from '../tatum-client';
import { CHAIN_ID, CHAIN_NAME, DEFAULT_RPC_URL } from '../config';
// Simple storage contract bytecode and ABI
const STORAGE_CONTRACT_BYTECODE = '0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d1565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009a81610087565b81146100a557600080fd5b50565b6000813590506100b781610091565b92915050565b6000602082840312156100d3576100d26000fd5b60006100e1848285016100a8565b91505092915050565b6100f381610087565b82525050565b600060208201905061010e60008301846100ea565b9291505056fea2646970667358221220c5f5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c64736f6c63430008070033';
const STORAGE_CONTRACT_ABI = [
{
inputs: [],
name: 'retrieve',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'uint256', name: 'num', type: 'uint256' }],
name: 'store',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
async function main() {
console.log(`Deploying contract on ${CHAIN_NAME} (ChainID ${CHAIN_ID})...`);
// Load private key from environment
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable not set');
}
// Initialize ethers provider with ChainID 138
const provider = new ethers.JsonRpcProvider(DEFAULT_RPC_URL, {
chainId: CHAIN_ID,
name: 'defi-oracle-mainnet',
});
// Create wallet
const wallet = new ethers.Wallet(privateKey, provider);
console.log('Wallet Address:', wallet.address);
// Get balance
const balance = await provider.getBalance(wallet.address);
console.log('Balance:', ethers.formatEther(balance), 'ETH');
// Deploy contract
console.log('\nDeploying Storage contract...');
const factory = new ethers.ContractFactory(
STORAGE_CONTRACT_ABI,
STORAGE_CONTRACT_BYTECODE,
wallet
);
const contract = await factory.deploy({
chainId: CHAIN_ID, // Important: Must match ChainID 138
});
console.log('Deployment Transaction Hash:', contract.deploymentTransaction()?.hash);
console.log('Waiting for deployment...');
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log('Contract deployed at:', contractAddress);
// Interact with contract
console.log('\n=== Interacting with Contract ===');
// Store a value
const valueToStore = 42;
console.log(`Storing value: ${valueToStore}`);
const storeTx = await contract.store(valueToStore, {
chainId: CHAIN_ID,
});
await storeTx.wait();
console.log('Store transaction confirmed:', storeTx.hash);
// Retrieve the value
const retrievedValue = await contract.retrieve();
console.log('Retrieved value:', retrievedValue.toString());
// Verify using Tatum SDK
console.log('\n=== Verifying Contract with Tatum SDK ===');
const tatum = await initTatumSDK({
rpcUrl: DEFAULT_RPC_URL,
verbose: false,
});
// Get contract code
const code = await tatum.rpc.request('eth_getCode', [contractAddress, 'latest']);
console.log('Contract Code:', code ? 'Present' : 'Not found');
// Get contract storage
const storageValue = await tatum.rpc.request('eth_getStorageAt', [
contractAddress,
'0x0', // Storage slot 0
'latest',
]);
console.log('Storage Value (slot 0):', storageValue);
console.log('\n=== Contract Deployment Example Complete ===');
console.log(`Contract Address: ${contractAddress}`);
console.log(`Explorer: ${process.env.EXPLORER_URL || 'https://explorer.defi-oracle-meta-mainnet.org'}/address/${contractAddress}`);
}
main().catch((error) => {
console.error('Error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,88 @@
/**
* Transaction example: Send a transaction on ChainID 138
*
* Important: Transactions must be signed with chainId: 138 (EIP-155)
*/
import { ethers } from 'ethers';
import { initTatumSDK } from '../tatum-client';
import { CHAIN_ID, CHAIN_NAME, DEFAULT_RPC_URL } from '../config';
async function main() {
console.log(`Sending transaction on ${CHAIN_NAME} (ChainID ${CHAIN_ID})...`);
// Load private key from environment
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error('PRIVATE_KEY environment variable not set');
}
// Initialize ethers provider with ChainID 138
const provider = new ethers.JsonRpcProvider(DEFAULT_RPC_URL, {
chainId: CHAIN_ID,
name: 'defi-oracle-mainnet',
});
// Create wallet
const wallet = new ethers.Wallet(privateKey, provider);
console.log('Wallet Address:', wallet.address);
// Get balance
const balance = await provider.getBalance(wallet.address);
console.log('Balance:', ethers.formatEther(balance), 'ETH');
// Check if we have enough balance
if (balance === 0n) {
throw new Error('Insufficient balance. Please fund the wallet first.');
}
// Get recipient address from environment or use a test address
const recipient = process.env.RECIPIENT_ADDRESS || '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
const amount = process.env.AMOUNT || '0.01'; // ETH
console.log(`\nSending ${amount} ETH to ${recipient}...`);
// Send transaction with correct chainId
const tx = await wallet.sendTransaction({
to: recipient,
value: ethers.parseEther(amount),
chainId: CHAIN_ID, // Important: Must match ChainID 138
});
console.log('Transaction Hash:', tx.hash);
console.log('Waiting for confirmation...');
// Wait for transaction to be mined
const receipt = await tx.wait();
console.log('Transaction confirmed!');
console.log('Block Number:', receipt?.blockNumber);
console.log('Gas Used:', receipt?.gasUsed.toString());
// Verify transaction using Tatum SDK
console.log('\n=== Verifying Transaction with Tatum SDK ===');
const tatum = await initTatumSDK({
rpcUrl: DEFAULT_RPC_URL,
verbose: false,
});
const txFromChain = await tatum.rpc.request('eth_getTransactionByHash', [tx.hash]);
console.log('Transaction from chain:', JSON.stringify(txFromChain, null, 2));
// Verify chainId in transaction
if (txFromChain && typeof txFromChain === 'object' && 'chainId' in txFromChain) {
const txChainId = parseInt(txFromChain.chainId as string, 16);
if (txChainId === CHAIN_ID) {
console.log('✓ ChainID verified in transaction');
} else {
console.error(`✗ ChainID mismatch! Expected ${CHAIN_ID}, got ${txChainId}`);
}
}
console.log('\n=== Transaction Example Complete ===');
}
main().catch((error) => {
console.error('Error:', error);
process.exit(1);
});

20
sdk/src/index.ts Normal file
View File

@@ -0,0 +1,20 @@
/**
* Tatum SDK Integration for ChainID 138
* Main export file
*/
export { initTatumSDK, initTatumSDKWithNodes, verifyConnection } from './tatum-client';
export {
CHAIN_ID,
CHAIN_ID_HEX,
CHAIN_NAME,
DEFAULT_RPC_URL,
DEFAULT_WS_URL,
NETWORK_CONFIG,
getTatumConfig,
type TatumConfig,
} from './config';
// MetaMask integration helpers
export * from './metamask';

41
sdk/src/metamask.ts Normal file
View File

@@ -0,0 +1,41 @@
/**
* MetaMask integration helpers for ChainID 138
* Integrated into the main SDK
*
* Note: MetaMask SDK is browser-only. In Node.js environments, only configuration exports are available.
*/
// Always export configuration (available in both browser and Node.js)
export {
CHAIN_ID,
CHAIN_ID_HEX,
CHAIN_NAME,
RPC_URLS,
BLOCK_EXPLORER_URL,
NETWORK_METADATA,
CAIP2_IDENTIFIER,
} from '../../metamask-sdk/src/config';
export type {
EthereumProvider,
AddEthereumChainParameter,
WatchAssetParameters,
MetaMaskError,
} from '../../metamask-sdk/src/types';
// Browser-only exports (conditional)
if (typeof window !== 'undefined') {
// Browser environment - export MetaMask SDK functions
export {
addNetwork,
addOrSwitchNetwork,
isNetworkAdded,
getEthereumProvider,
switchNetwork,
getCurrentChainId,
isOnChain138,
addToken,
addTokenFromList,
} from '../../metamask-sdk/src';
}

182
sdk/src/smoke-test.ts Normal file
View File

@@ -0,0 +1,182 @@
/**
* Smoke test: Comprehensive test of Tatum SDK integration with ChainID 138
*
* This test verifies:
* 1. RPC connectivity
* 2. Chain ID verification
* 3. Basic RPC calls
* 4. Transaction signing (dry run)
* 5. Contract interaction (if deployed)
*/
import { initTatumSDK, verifyConnection } from './tatum-client';
import { CHAIN_ID, CHAIN_NAME, DEFAULT_RPC_URL } from './config';
import { ethers } from 'ethers';
interface TestResult {
name: string;
passed: boolean;
error?: string;
details?: any;
}
async function runTest(name: string, testFn: () => Promise<any>): Promise<TestResult> {
try {
const result = await testFn();
return {
name,
passed: true,
details: result,
};
} catch (error) {
return {
name,
passed: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
async function main() {
console.log('='.repeat(70));
console.log(`Smoke Test: ${CHAIN_NAME} (ChainID ${CHAIN_ID})`);
console.log('='.repeat(70));
console.log();
const results: TestResult[] = [];
// Test 1: Initialize Tatum SDK
console.log('Test 1: Initializing Tatum SDK...');
const tatum = await initTatumSDK({
rpcUrl: DEFAULT_RPC_URL,
verbose: false,
});
results.push(await runTest('Initialize Tatum SDK', async () => {
if (!tatum) throw new Error('Tatum SDK initialization failed');
return { initialized: true };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
// Test 2: Verify connection and chain ID
console.log('Test 2: Verifying connection and chain ID...');
results.push(await runTest('Verify Connection', async () => {
return await verifyConnection(tatum);
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
if (results[results.length - 1].passed && results[results.length - 1].details) {
const info = results[results.length - 1].details;
console.log(` Chain ID: ${info.chainId}, Block: ${info.blockNumber}`);
}
// Test 3: Test basic RPC calls
console.log('Test 3: Testing basic RPC calls...');
results.push(await runTest('RPC: eth_blockNumber', async () => {
const blockNumber = await tatum.rpc.request('eth_blockNumber', []);
return { blockNumber };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
results.push(await runTest('RPC: eth_chainId', async () => {
const chainId = await tatum.rpc.request('eth_chainId', []);
const chainIdDecimal = parseInt(chainId as string, 16);
if (chainIdDecimal !== CHAIN_ID) {
throw new Error(`Chain ID mismatch: expected ${CHAIN_ID}, got ${chainIdDecimal}`);
}
return { chainId: chainIdDecimal };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
results.push(await runTest('RPC: eth_gasPrice', async () => {
const gasPrice = await tatum.rpc.request('eth_gasPrice', []);
return { gasPrice };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
results.push(await runTest('RPC: net_peerCount', async () => {
const peerCount = await tatum.rpc.request('net_peerCount', []);
return { peerCount: parseInt(peerCount as string, 16) };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
// Test 4: Test ethers.js provider
console.log('Test 4: Testing ethers.js provider...');
results.push(await runTest('Ethers: Provider Initialization', async () => {
const provider = new ethers.JsonRpcProvider(DEFAULT_RPC_URL, {
chainId: CHAIN_ID,
name: 'defi-oracle-mainnet',
});
const network = await provider.getNetwork();
if (Number(network.chainId) !== CHAIN_ID) {
throw new Error(`Chain ID mismatch: expected ${CHAIN_ID}, got ${Number(network.chainId)}`);
}
return { chainId: Number(network.chainId), name: network.name };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
// Test 5: Test transaction signing (dry run)
console.log('Test 5: Testing transaction signing (dry run)...');
if (process.env.PRIVATE_KEY) {
results.push(await runTest('Ethers: Transaction Signing', async () => {
const provider = new ethers.JsonRpcProvider(DEFAULT_RPC_URL, {
chainId: CHAIN_ID,
name: 'defi-oracle-mainnet',
});
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
// Create a transaction (but don't send it)
const tx = await wallet.populateTransaction({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: ethers.parseEther('0.001'),
chainId: CHAIN_ID,
});
// Verify chainId is set correctly
if (tx.chainId !== BigInt(CHAIN_ID)) {
throw new Error(`Transaction chainId mismatch: expected ${CHAIN_ID}, got ${tx.chainId}`);
}
return { chainId: Number(tx.chainId), to: tx.to, value: tx.value?.toString() };
}));
console.log(results[results.length - 1].passed ? '✓' : '✗', results[results.length - 1].name);
} else {
console.log('⚠ Skipping transaction signing test (PRIVATE_KEY not set)');
}
// Summary
console.log();
console.log('='.repeat(70));
console.log('Test Summary');
console.log('='.repeat(70));
const passed = results.filter(r => r.passed).length;
const failed = results.filter(r => !r.passed).length;
console.log(`Total Tests: ${results.length}`);
console.log(`Passed: ${passed}`);
console.log(`Failed: ${failed}`);
console.log();
if (failed > 0) {
console.log('Failed Tests:');
results.filter(r => !r.passed).forEach(r => {
console.log(`${r.name}: ${r.error}`);
});
console.log();
}
console.log('='.repeat(70));
if (failed === 0) {
console.log('✓ All tests passed!');
process.exit(0);
} else {
console.log('✗ Some tests failed');
process.exit(1);
}
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

103
sdk/src/tatum-client.ts Normal file
View File

@@ -0,0 +1,103 @@
/**
* Tatum SDK client initialization for ChainID 138
*/
import { TatumSDK, Network, Ethereum, RpcNodeType } from '@tatumio/tatum';
import { getTatumConfig, CHAIN_ID, CHAIN_ID_HEX, NETWORK_CONFIG, TatumConfig } from './config';
/**
* Initialize Tatum SDK for ChainID 138
*
* Note: With custom RPC, only RPC calls are redirected to your node.
* Tatum's cloud services (Notifications, Blockchain Data, etc.) won't work
* on unsupported/private chains - only raw JSON-RPC will work.
*
* @param config Optional configuration
* @returns Initialized Tatum SDK instance
*/
export async function initTatumSDK(config?: TatumConfig) {
const tatumConfig = getTatumConfig(config);
// Option A: Simple initialization with custom RPC URL
const tatum = await TatumSDK.init<Ethereum>({
network: Network.ETHEREUM, // Use Ethereum network type for EVM compatibility
rpcUrl: tatumConfig.rpcUrl,
verbose: tatumConfig.verbose,
// apiKey is optional and only needed for Tatum cloud features
// With custom RPC, cloud features won't work anyway
...(tatumConfig.apiKey && { apiKey: tatumConfig.apiKey }),
});
return tatum;
}
/**
* Initialize Tatum SDK with explicit node configuration
*
* @param config Optional configuration
* @returns Initialized Tatum SDK instance
*/
export async function initTatumSDKWithNodes(config?: TatumConfig) {
const tatumConfig = getTatumConfig(config);
// Option B: Explicit node configuration
const tatum = await TatumSDK.init<Ethereum>({
network: Network.ETHEREUM,
rpc: {
nodes: [
{
url: tatumConfig.rpcUrl!,
type: RpcNodeType.NORMAL,
},
],
},
verbose: tatumConfig.verbose,
...(tatumConfig.apiKey && { apiKey: tatumConfig.apiKey }),
});
return tatum;
}
/**
* Verify connectivity and chain ID
*
* @param tatum Tatum SDK instance
* @returns Object with block number and chain ID
*/
export async function verifyConnection(tatum: Ethereum) {
try {
// Get current block number
const blockNumberHex = await tatum.rpc.request('eth_blockNumber', []);
const blockNumber = parseInt(blockNumberHex as string, 16);
// Get chain ID
const chainIdHex = await tatum.rpc.request('eth_chainId', []);
const chainId = parseInt(chainIdHex as string, 16);
// Verify chain ID
if (chainId !== CHAIN_ID) {
throw new Error(
`Chain ID mismatch! Expected ${CHAIN_ID} (${CHAIN_ID_HEX}), got ${chainId} (${chainIdHex})`
);
}
// Get network version
const netVersion = await tatum.rpc.request('net_version', []);
// Get sync status
const syncing = await tatum.rpc.request('eth_syncing', []);
return {
blockNumber,
blockNumberHex,
chainId,
chainIdHex,
netVersion,
syncing,
networkConfig: NETWORK_CONFIG,
};
} catch (error) {
throw new Error(`Failed to verify connection: ${error instanceof Error ? error.message : String(error)}`);
}
}

View File

@@ -0,0 +1,82 @@
/**
* Test connection to ChainID 138 RPC endpoint
*/
import { initTatumSDK, verifyConnection } from './tatum-client';
import { CHAIN_ID, CHAIN_NAME, DEFAULT_RPC_URL } from './config';
async function main() {
console.log('='.repeat(60));
console.log(`Testing Connection to ${CHAIN_NAME}`);
console.log(`ChainID: ${CHAIN_ID} (0x${CHAIN_ID.toString(16)})`);
console.log(`RPC URL: ${DEFAULT_RPC_URL}`);
console.log('='.repeat(60));
console.log();
try {
// Initialize Tatum SDK
console.log('Initializing Tatum SDK...');
const tatum = await initTatumSDK({
rpcUrl: DEFAULT_RPC_URL,
verbose: true,
});
console.log('✓ Tatum SDK initialized\n');
// Verify connection
console.log('Verifying connection...');
const connectionInfo = await verifyConnection(tatum);
console.log('✓ Connection verified\n');
// Display connection info
console.log('Connection Information:');
console.log('-'.repeat(60));
console.log(`Chain ID: ${connectionInfo.chainId} (${connectionInfo.chainIdHex})`);
console.log(`Current Block: ${connectionInfo.blockNumber}`);
console.log(`Network Version: ${connectionInfo.netVersion}`);
console.log(`Syncing: ${connectionInfo.syncing}`);
console.log('-'.repeat(60));
console.log();
// Test additional RPC calls
console.log('Testing additional RPC calls...');
// Get gas price
const gasPrice = await tatum.rpc.request('eth_gasPrice', []);
console.log(`✓ Gas Price: ${gasPrice}`);
// Get peer count
const peerCount = await tatum.rpc.request('net_peerCount', []);
const peerCountDecimal = parseInt(peerCount as string, 16);
console.log(`✓ Peer Count: ${peerCountDecimal}`);
// Get latest block
const latestBlock = await tatum.rpc.request('eth_getBlockByNumber', ['latest', false]);
if (latestBlock && typeof latestBlock === 'object') {
console.log(`✓ Latest Block: ${JSON.stringify(latestBlock, null, 2).substring(0, 200)}...`);
}
console.log();
console.log('='.repeat(60));
console.log('✓ All tests passed!');
console.log('='.repeat(60));
} catch (error) {
console.error();
console.error('='.repeat(60));
console.error('✗ Connection test failed!');
console.error('='.repeat(60));
console.error();
console.error('Error:', error instanceof Error ? error.message : String(error));
console.error();
console.error('Troubleshooting:');
console.error('1. Ensure RPC node is running and accessible');
console.error('2. Check RPC_URL environment variable');
console.error('3. Verify firewall/network settings');
console.error('4. Check node logs for errors');
console.error();
process.exit(1);
}
}
main();