# IoTeX Node.js SDK - Query-Only Version

A lightweight, query-focused npm package for reading data from the IoTeX blockchain using native gRPC protocol.

## Features

✅ **Account Queries**
- Get account balance
- Get account metadata (nonce, pending nonce, etc.)
- Address conversion (IoTeX ↔ Ethereum)

✅ **Blockchain Queries**
- Get chain metadata (height, epoch, TPS)
- Get blocks by height or hash
- Get epoch metadata
- Get blockchain version
- Get transaction receipts

✅ **Node Delegate Queries**
- Get current delegates
- Get delegates by epoch
- Get epoch information

✅ **No Write Operations**
- Read-only SDK
- No account creation/management needed
- No transaction signing
- Perfect for data analysis and monitoring

## Installation

```bash
npm install iotex-node-sdk
```

## Quick Start

```typescript
import { IoTeXSDK } from 'iotex-node-sdk';

async function main() {
  // Connect to mainnet
  const sdk = IoTeXSDK.mainnet();
  await sdk.connect();

  // Query account balance
  const balance = await sdk.account.getBalance('io1gh7xfrsnj6p5uqgjpk9xq6jg9na28aewgp7a9v');
  console.log('Balance:', balance, 'IOTX');

  // Query blockchain info
  const chainMeta = await sdk.blockchain.getChainMeta();
  console.log('Height:', chainMeta.height.toString());
  console.log('Epoch:', chainMeta.epoch.num.toString());

  // Query delegates
  const delegates = await sdk.node.getDelegates();
  console.log('Top 5 delegates:');
  delegates.slice(0, 5).forEach(d => {
    console.log(`${d.rank}. ${d.name} - ${d.votes} IOTX`);
  });

  sdk.disconnect();
}

main();
```

## Query Examples

### Account Queries

#### Get Account Balance

```typescript
const sdk = IoTeXSDK.mainnet();
await sdk.connect();

const balance = await sdk.account.getBalance('io1gh7xfrsnj6p5uqgjpk9xq6jg9na28aewgp7a9v');
console.log(`Balance: ${balance} IOTX`);
```

#### Get Account Metadata

```typescript
const meta = await sdk.account.getMeta('io1gh7xfrsnj6p5uqgjpk9xq6jg9na28aewgp7a9v');
console.log('Balance:', meta.balance);
console.log('Nonce:', meta.nonce.toString());
console.log('Pending Nonce:', meta.pendingNonce.toString());
console.log('Num Actions:', meta.numActions.toString());
console.log('Is Contract:', meta.isContract);
```

#### Convert Addresses

```typescript
import { toEthAddress, toIoAddress } from 'iotex-node-sdk';

// IoTeX to Ethereum
const ethAddr = toEthAddress('io1hp6y4eqr90j7tmul4w2wa8pm7wx462hq0mg4tw');
console.log(ethAddr); // 0xb8744ae4032be5e5ef9fab94ee9c3bf38d5d2ae0

// Ethereum to IoTeX
const ioAddr = toIoAddress('0xb8744ae4032be5e5ef9fab94ee9c3bf38d5d2ae0');
console.log(ioAddr); // io1hp6y4eqr90j7tmul4w2wa8pm7wx462hq0mg4tw
```

### Blockchain Queries

#### Get Chain Metadata

```typescript
const chainMeta = await sdk.blockchain.getChainMeta();
console.log({
  height: chainMeta.height.toString(),
  numActions: chainMeta.numActions.toString(),
  epoch: chainMeta.epoch.num.toString(),
  tps: chainMeta.tps,
  tpsFloat: chainMeta.tpsFloat
});
```

#### Get Block by Height

```typescript
const block = await sdk.blockchain.getBlock(1000000);
console.log({
  hash: block.blockHash,
  height: block.height.toString(),
  timestamp: block.timestamp,
  numActions: block.numActions,
  producer: block.producerAddress
});
```

#### Get Block by Hash

```typescript
const block = await sdk.blockchain.getBlock('block-hash-here');
```

#### Get Latest Block

```typescript
const chainMeta = await sdk.blockchain.getChainMeta();
const latestBlock = await sdk.blockchain.getBlock(Number(chainMeta.height));
```

#### Get Epoch Metadata

```typescript
// Current epoch
const currentEpoch = await sdk.blockchain.getEpochMeta();

// Specific epoch
const epoch1000 = await sdk.blockchain.getEpochMeta(1000);

console.log({
  num: currentEpoch.num.toString(),
  height: currentEpoch.height.toString(),
  gravityChainStartHeight: currentEpoch.gravityChainStartHeight.toString()
});
```

#### Get Blockchain Version

```typescript
const version = await sdk.blockchain.getVersion();
console.log({
  version: version.packageVersion,
  commit: version.packageCommitID,
  goVersion: version.goVersion,
  buildTime: version.buildTime
});
```

#### Get Transaction Receipt

```typescript
const receipt = await sdk.blockchain.getReceipt('action-hash');
console.log(receipt);
```

#### Query Actions

```typescript
// Get action by hash
const actions = await sdk.blockchain.getActions({
  byHash: 'action-hash-here'
});

// Get actions by address
const actions = await sdk.blockchain.getActions({
  byAddr: {
    address: 'io1gh7xfrsnj6p5uqgjpk9xq6jg9na28aewgp7a9v',
    start: 0,
    count: 10
  }
});

// Get actions by block
const actions = await sdk.blockchain.getActions({
  byBlk: {
    blkHash: 'block-hash',
    start: 0,
    count: 10
  }
});
```

### Node Delegate Queries

#### Get Current Delegates

```typescript
// Get top 36 delegates with full candidate information
const delegates = await sdk.node.getDelegates();

delegates.forEach(d => {
  console.log(`${d.rank}. ${d.name}`);
  console.log(`   Operator Address: ${d.operatorAddress || d.address}`);
  console.log(`   Owner Address: ${d.ownerAddress}`);
  console.log(`   Reward Address: ${d.rewardAddress}`);
  console.log(`   Weighted Votes: ${d.votes} IOTX`);  // NOTE: These are WEIGHTED votes
  console.log(`   Total Weighted Votes (Rau): ${d.totalWeightedVotes}`);
  console.log(`   Self-staking Tokens (Rau): ${d.selfStakingTokens}`);
  console.log(`   Production: ${d.production}`);
  console.log(`   Active: ${d.active ? 'Yes' : 'No'}`);
});
```

**Note**: The `votes` field contains **weighted votes** (staked amount × lock duration multiplier), not raw staked IOTX.

#### Get All Delegates

```typescript
// Get all registered candidates (not just top 36 block producers)
const allDelegates = await sdk.node.getDelegates({ all: true });
console.log(`Total delegates: ${allDelegates.length}`);

// Access extended fields
allDelegates.forEach(d => {
  console.log(`${d.name}:`);
  console.log(`  Owner: ${d.ownerAddress}`);
  console.log(`  Operator: ${d.operatorAddress}`);
  console.log(`  Self-stake bucket: ${d.selfStakeBucketIdx}`);
  console.log(`  ID: ${d.id}`);
});
```

#### Get Delegates for Specific Epoch

```typescript
const epoch1000Delegates = await sdk.node.getDelegates({
  epochNumber: 1000,
  all: true
});
```

#### Get Current Epoch

```typescript
const currentEpoch = await sdk.node.getCurrentEpoch();
console.log({
  num: currentEpoch.num.toString(),
  height: currentEpoch.height.toString()
});
```

### Staking Bucket Queries

#### Get Buckets by Voter Address

```typescript
const buckets = await sdk.blockchain.getBucketList({
  voterAddress: 'io1...',
  offset: 0,
  limit: 100
});

buckets.forEach(b => {
  console.log(`Bucket #${b.index}:`);
  console.log(`  Candidate: ${b.candidateAddress}`);
  console.log(`  Staked Amount: ${b.stakedAmount} IOTX`);  // Raw staked, NOT weighted
  console.log(`  Duration: ${b.stakedDuration} days`);
  console.log(`  Auto-stake: ${b.autoStake}`);
  console.log(`  Owner: ${b.owner}`);
});
```

#### Get Buckets by Candidate Name

```typescript
const buckets = await sdk.blockchain.getBucketList({
  candidateName: 'delegateName',
  offset: 0,
  limit: 10000
});

// Calculate total raw staked IOTX for a delegate
const totalStakedIotx = buckets.reduce(
  (sum, bucket) => sum + parseFloat(bucket.stakedAmount),
  0
);
console.log(`Total staked: ${totalStakedIotx.toFixed(2)} IOTX`);
```

#### Understanding Weighted Votes vs Raw Staked Amount

- **Raw Staked IOTX** (`bucket.stakedAmount`): The actual IOTX tokens locked in a bucket
- **Weighted Votes** (`delegate.votes`): Raw staked × lock duration multiplier

Longer lock durations receive higher voting power multipliers. To get a delegate's total raw staked IOTX (without multiplier), sum all bucket `stakedAmount` values.

## Network Configuration

### Mainnet

```typescript
const sdk = IoTeXSDK.mainnet();
```

### Testnet

```typescript
const sdk = IoTeXSDK.testnet();
```

### Custom Endpoint

```typescript
const sdk = new IoTeXSDK({
  endpoint: 'your-node:443',
  secure: true,
  timeout: 30000
});
```

## Utility Functions

### Amount Conversion

```typescript
import { iotxToRau, rauToIotx } from 'iotex-node-sdk';

// Convert IOTX to Rau (smallest unit)
const rau = iotxToRau('100');        // 100000000000000000000n

// Convert Rau to IOTX
const iotx = rauToIotx(rau);         // "100"
```

### Validation

```typescript
import {
  isValidIoAddress,
  isValidEthAddress,
  isValidAddress
} from 'iotex-node-sdk';

isValidIoAddress('io1hp6y4eqr90j7tmul4w2wa8pm7wx462hq0mg4tw');  // true
isValidEthAddress('0xb8744ae4032be5e5ef9fab94ee9c3bf38d5d2ae0'); // true
isValidAddress('io1...');                                        // true
```

## Error Handling

```typescript
import { IoTeXError } from 'iotex-node-sdk';

try {
  const balance = await sdk.account.getBalance('invalid-address');
} catch (error) {
  if (error instanceof IoTeXError) {
    console.error('Error:', error.code, error.message);
  }
}
```

## Common Error Codes

- `CONNECTION_ERROR` - Cannot connect to endpoint
- `NOT_FOUND` - Resource not found
- `INVALID_ADDRESS` - Invalid address format
- `ACCOUNT_NOT_FOUND` - Account does not exist
- `BLOCK_NOT_FOUND` - Block does not exist

## Real-World Use Cases

### Monitor Account Balance

```typescript
async function monitorBalance(address: string) {
  const sdk = IoTeXSDK.mainnet();
  await sdk.connect();

  setInterval(async () => {
    try {
      const balance = await sdk.account.getBalance(address);
      console.log(`[${new Date().toISOString()}] Balance: ${balance} IOTX`);
    } catch (error) {
      console.error('Error:', error);
    }
  }, 60000); // Check every minute
}
```

### Track Blockchain Height

```typescript
async function trackHeight() {
  const sdk = IoTeXSDK.mainnet();
  await sdk.connect();

  let lastHeight = 0n;

  setInterval(async () => {
    const chainMeta = await sdk.blockchain.getChainMeta();
    if (chainMeta.height > lastHeight) {
      console.log(`New block: ${chainMeta.height}`);
      lastHeight = chainMeta.height;
    }
  }, 5000); // Check every 5 seconds
}
```

### Get Delegate Rankings

```typescript
async function getDelegateRankings() {
  const sdk = IoTeXSDK.mainnet();
  await sdk.connect();

  const delegates = await sdk.node.getDelegates({ all: true });

  // Sort by votes
  const sorted = delegates.sort((a, b) => {
    const votesA = parseFloat(a.votes);
    const votesB = parseFloat(b.votes);
    return votesB - votesA;
  });

  // Top 10
  console.log('Top 10 Delegates by Votes:');
  sorted.slice(0, 10).forEach((d, i) => {
    console.log(`${i + 1}. ${d.name}: ${d.votes} IOTX`);
  });

  sdk.disconnect();
}
```

### Export Account Data to CSV

```typescript
import * as fs from 'fs';

async function exportAccountData(addresses: string[]) {
  const sdk = IoTeXSDK.mainnet();
  await sdk.connect();

  const csv = ['Address,Balance,Nonce,NumActions'];

  for (const addr of addresses) {
    const meta = await sdk.account.getMeta(addr);
    csv.push(`${addr},${meta.balance},${meta.nonce},${meta.numActions}`);
  }

  fs.writeFileSync('accounts.csv', csv.join('\n'));
  console.log('Exported to accounts.csv');

  sdk.disconnect();
}
```

## TypeScript Support

Full TypeScript support with comprehensive type definitions:

```typescript
import {
  IoTeXSDK,
  ChainMeta,
  Block,
  EpochMeta,
  Delegate,
  AccountMeta
} from 'iotex-node-sdk';

const sdk: IoTeXSDK = IoTeXSDK.mainnet();
const chainMeta: ChainMeta = await sdk.blockchain.getChainMeta();
const block: Block = await sdk.blockchain.getBlock(1000000);
```

## Performance Tips

### Reuse SDK Instance

```typescript
// Good - reuse connection
const sdk = IoTeXSDK.mainnet();
await sdk.connect();

for (const addr of addresses) {
  const balance = await sdk.account.getBalance(addr);
}

sdk.disconnect();
```

### Batch Queries

```typescript
// Query multiple things in parallel
const [chainMeta, delegates, balance] = await Promise.all([
  sdk.blockchain.getChainMeta(),
  sdk.node.getDelegates(),
  sdk.account.getBalance(address)
]);
```

### Connection Pooling

The SDK automatically manages gRPC connections. Just remember to disconnect when done:

```typescript
try {
  await sdk.connect();
  // Your queries
} finally {
  sdk.disconnect(); // Always disconnect
}
```

## What's NOT Included (By Design)

This is a **query-only** SDK. The following features are intentionally excluded:

- ❌ Account creation/management
- ❌ Transaction signing
- ❌ Action sending (transfers, staking, etc.)
- ❌ Smart contract interaction
- ❌ Keystore management

If you need write operations, consider using the full SDK or `ioctl` CLI.

## Dependencies

- `@grpc/grpc-js` ^1.12.0 - Modern gRPC client
- `@grpc/proto-loader` ^0.7.13 - Protobuf loading
- `@iotexproject/iotex-address-ts` ^1.0.2 - Official address conversion

## Requirements

- Node.js 18.0.0 or higher
- Network access to IoTeX endpoints

## License

MIT

## Support

- Documentation: [Full README](./README.md)
- IoTeX Docs: https://docs.iotex.io
- Discord: https://discord.gg/iotex
