# Tutorial 4: Working with the Synor API

Learn to interact with Synor nodes via JSON-RPC, WebSocket subscriptions, and the public API gateway.

## What You'll Learn

- Query blockchain data
- Subscribe to real-time events
- Build transactions programmatically
- Use the rate-limited public API

## Prerequisites

- Completed [Tutorial 1: Getting Started](./01-getting-started.md)
- Running Synor node (local or testnet)

---

## Part 1: API Overview

### Connection Options

| Method | URL | Use Case |
|--------|-----|----------|
| HTTP RPC | `http://localhost:17110` | Query, send tx |
| WebSocket | `ws://localhost:17111` | Real-time subscriptions |
| Public API | `https://api.synor.cc/rpc` | Rate-limited access |

### Authentication (Public API)

```bash
# Anonymous (100 req/min)
curl https://api.synor.cc/rpc ...

# With API key (1000 req/min)
curl https://api.synor.cc/rpc \
  -H "X-API-Key: sk_developer_abc123..."
```

---

## Part 2: Basic Queries

### Get Block Count

```javascript
async function getBlockCount() {
  const response = await fetch('http://localhost:17110', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'synor_getBlockCount',
      params: [],
      id: 1
    })
  });

  const { result } = await response.json();
  return result; // e.g., 1234567
}
```

### Get Block by Hash

```javascript
async function getBlock(hash) {
  const response = await rpc('synor_getBlock', [hash]);
  return response;
}

// Response:
// {
//   hash: "0x...",
//   height: 1234567,
//   timestamp: 1704067200,
//   transactions: [...],
//   parents: ["0x...", "0x..."],  // DAG parents
//   difficulty: "1234567890",
//   nonce: 12345
// }
```

### Get Transaction

```javascript
async function getTransaction(txId) {
  return rpc('synor_getTransaction', [txId]);
}

// Response:
// {
//   txId: "0x...",
//   version: 1,
//   inputs: [...],
//   outputs: [...],
//   confirmations: 50,
//   blockHash: "0x...",
//   isHybrid: true
// }
```

---

## Part 3: Address Operations

### Get Balance

```javascript
async function getBalance(address) {
  const balance = await rpc('synor_getBalance', [address]);

  return {
    confirmed: parseFloat(balance.confirmed),
    pending: parseFloat(balance.pending),
    total: parseFloat(balance.confirmed) + parseFloat(balance.pending)
  };
}
```

### Get UTXOs

```javascript
async function getUtxos(address) {
  const utxos = await rpc('synor_getUtxos', [address]);

  return utxos.map(utxo => ({
    txId: utxo.txId,
    outputIndex: utxo.outputIndex,
    amount: parseFloat(utxo.amount),
    confirmations: utxo.confirmations
  }));
}
```

### Get Transaction History

```javascript
async function getTransactionHistory(address, options = {}) {
  const { limit = 50, offset = 0 } = options;

  const transactions = await rpc('synor_getAddressTransactions', [
    address,
    { limit, offset }
  ]);

  return transactions.map(tx => ({
    ...tx,
    type: tx.outputs.some(o => o.address === address) ? 'receive' : 'send',
    date: new Date(tx.timestamp * 1000)
  }));
}
```

---

## Part 4: Building Transactions

### Simple Transfer

```javascript
import { buildTransaction, signTransactionHybrid, serializeTransaction } from '@synor/sdk';

async function sendTokens(wallet, toAddress, amount) {
  // 1. Get UTXOs
  const utxos = await rpc('synor_getUtxos', [wallet.address]);

  // 2. Select UTXOs
  const selectedUtxos = selectUtxos(utxos, amount);

  // 3. Build transaction
  const tx = buildTransaction({
    inputs: selectedUtxos,
    outputs: [{ address: toAddress, amount }],
    changeAddress: wallet.address
  });

  // 4. Sign with hybrid signatures
  const signedTx = await signTransactionHybrid(
    tx,
    wallet.seed,
    wallet.keypair.publicKey,
    wallet.dilithiumPublicKey
  );

  // 5. Broadcast
  const result = await rpc('synor_sendRawTransaction', [
    serializeTransaction(signedTx)
  ]);

  return result.txId;
}

// UTXO selection helper
function selectUtxos(utxos, targetAmount) {
  const sorted = [...utxos].sort((a, b) => b.amount - a.amount);
  const selected = [];
  let total = 0;

  for (const utxo of sorted) {
    selected.push(utxo);
    total += utxo.amount;
    if (total >= targetAmount + 0.0001) { // Include fee estimate
      break;
    }
  }

  if (total < targetAmount) {
    throw new Error('Insufficient funds');
  }

  return selected;
}
```

### Fee Estimation

```javascript
async function estimateFee(fromAddress, toAddress, amount) {
  const estimate = await rpc('synor_estimateFee', [{
    from: fromAddress,
    to: toAddress,
    amount: amount.toString()
  }]);

  return {
    fee: parseFloat(estimate.fee),
    feePerByte: estimate.feePerByte,
    estimatedSize: estimate.size, // Bytes (larger for hybrid signatures)
    priority: estimate.priority // 'low', 'medium', 'high'
  };
}
```

---

## Part 5: WebSocket Subscriptions

### Connect to WebSocket

```javascript
class SynorWebSocket {
  constructor(url = 'ws://localhost:17111') {
    this.url = url;
    this.ws = null;
    this.handlers = new Map();
    this.reconnectAttempts = 0;
  }

  connect() {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.url);

      this.ws.onopen = () => {
        this.reconnectAttempts = 0;
        resolve();
      };

      this.ws.onerror = reject;

      this.ws.onmessage = (event) => {
        const msg = JSON.parse(event.data);
        this.handleMessage(msg);
      };

      this.ws.onclose = () => {
        this.scheduleReconnect();
      };
    });
  }

  send(method, params) {
    const id = Date.now();
    this.ws.send(JSON.stringify({
      jsonrpc: '2.0',
      method,
      params,
      id
    }));
    return id;
  }

  handleMessage(msg) {
    if (msg.method) {
      // Subscription notification
      const handler = this.handlers.get(msg.method);
      if (handler) handler(msg.params);
    }
  }

  on(event, handler) {
    this.handlers.set(event, handler);
  }

  scheduleReconnect() {
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    this.reconnectAttempts++;
    setTimeout(() => this.connect(), delay);
  }
}
```

### Subscribe to New Blocks

```javascript
const ws = new SynorWebSocket();
await ws.connect();

// Subscribe
ws.send('synor_subscribeBlocks', []);

// Handle new blocks
ws.on('synor_newBlock', (block) => {
  console.log('New block:', block.hash);
  console.log('Height:', block.height);
  console.log('Transactions:', block.transactions.length);
});
```

### Subscribe to Address Transactions

```javascript
const myAddress = 'tsynor1...';

// Subscribe to address
ws.send('synor_subscribeAddress', [myAddress]);

// Handle transactions
ws.on('synor_addressNotification', (notification) => {
  const { txId, type, amount, confirmations } = notification;

  if (type === 'receive') {
    console.log(`Received ${amount} SYNOR!`);

    if (confirmations >= 10) {
      console.log('Transaction confirmed!');
    }
  }
});
```

### Subscribe to Mempool

```javascript
ws.send('synor_subscribeMempool', []);

ws.on('synor_mempoolTransaction', (tx) => {
  console.log('New pending tx:', tx.txId);
  console.log('Value:', tx.totalValue);
});
```

---

## Part 6: Advanced Queries

### Get DAG Information

```javascript
async function getDAGInfo() {
  const info = await rpc('synor_getDAGInfo', []);

  return {
    tips: info.tips,           // Current DAG tips
    blueScore: info.blueScore, // SPECTRE blue score
    virtualParent: info.virtualParent,
    pruningPoint: info.pruningPoint
  };
}
```

### Get Network Stats

```javascript
async function getNetworkStats() {
  const stats = await rpc('synor_getNetworkStats', []);

  return {
    hashrate: stats.hashrate,
    difficulty: stats.difficulty,
    blockRate: stats.blocksPerSecond,
    peers: stats.connectedPeers,
    mempoolSize: stats.mempoolSize
  };
}
```

### Search Transactions

```javascript
async function searchTransactions(query) {
  // Search by txId prefix
  if (query.length === 64) {
    return rpc('synor_getTransaction', [query]);
  }

  // Search by address
  if (query.startsWith('synor1') || query.startsWith('tsynor1')) {
    return rpc('synor_getAddressTransactions', [query, 10]);
  }

  // Search by block height
  if (/^\d+$/.test(query)) {
    const hash = await rpc('synor_getBlockHash', [parseInt(query)]);
    return rpc('synor_getBlock', [hash]);
  }

  throw new Error('Invalid search query');
}
```

---

## Part 7: Error Handling

### Common Error Codes

| Code | Meaning | Action |
|------|---------|--------|
| -32600 | Invalid Request | Check JSON format |
| -32601 | Method not found | Check method name |
| -32602 | Invalid params | Verify parameters |
| -32603 | Internal error | Retry or report |
| -32001 | Invalid API key | Check authentication |
| -32005 | Rate limit exceeded | Wait and retry |
| -32010 | Transaction rejected | Check validation |

### Retry Logic

```javascript
async function rpcWithRetry(method, params, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await rpc(method, params);
    } catch (error) {
      if (error.code === -32005) {
        // Rate limited - wait and retry
        const retryAfter = error.data?.retryAfter || 60;
        await sleep(retryAfter * 1000);
        continue;
      }

      if (error.code === -32603 && i < maxRetries - 1) {
        // Internal error - retry with backoff
        await sleep(1000 * Math.pow(2, i));
        continue;
      }

      throw error;
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

---

## Part 8: Building an API Client

### Complete TypeScript Client

```typescript
// synor-client.ts

interface RpcResponse<T> {
  jsonrpc: '2.0';
  result?: T;
  error?: { code: number; message: string; data?: any };
  id: number;
}

export class SynorClient {
  private baseUrl: string;
  private apiKey?: string;

  constructor(options: { url?: string; apiKey?: string } = {}) {
    this.baseUrl = options.url || 'http://localhost:17110';
    this.apiKey = options.apiKey;
  }

  private async call<T>(method: string, params: unknown[] = []): Promise<T> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json'
    };

    if (this.apiKey) {
      headers['X-API-Key'] = this.apiKey;
    }

    const response = await fetch(this.baseUrl, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        jsonrpc: '2.0',
        method,
        params,
        id: Date.now()
      })
    });

    const data: RpcResponse<T> = await response.json();

    if (data.error) {
      const error = new Error(data.error.message);
      (error as any).code = data.error.code;
      (error as any).data = data.error.data;
      throw error;
    }

    return data.result as T;
  }

  // Chain methods
  async getBlockCount(): Promise<number> {
    return this.call('synor_getBlockCount');
  }

  async getBlock(hash: string): Promise<Block> {
    return this.call('synor_getBlock', [hash]);
  }

  async getBlockHash(height: number): Promise<string> {
    return this.call('synor_getBlockHash', [height]);
  }

  // Transaction methods
  async getTransaction(txId: string): Promise<Transaction> {
    return this.call('synor_getTransaction', [txId]);
  }

  async sendRawTransaction(serializedTx: string): Promise<{ txId: string }> {
    return this.call('synor_sendRawTransaction', [serializedTx]);
  }

  // Address methods
  async getBalance(address: string): Promise<Balance> {
    return this.call('synor_getBalance', [address]);
  }

  async getUtxos(address: string): Promise<Utxo[]> {
    return this.call('synor_getUtxos', [address]);
  }

  async getAddressTransactions(address: string, limit = 50): Promise<Transaction[]> {
    return this.call('synor_getAddressTransactions', [address, limit]);
  }

  // Network methods
  async getNetworkStats(): Promise<NetworkStats> {
    return this.call('synor_getNetworkStats');
  }

  async estimateFee(params: FeeEstimateParams): Promise<FeeEstimate> {
    return this.call('synor_estimateFee', [params]);
  }
}

// Usage
const client = new SynorClient({
  url: 'https://api.synor.cc/rpc',
  apiKey: 'sk_developer_abc123...'
});

const balance = await client.getBalance('tsynor1...');
console.log(balance);
```

---

## API Reference Summary

### Chain Methods
- `synor_getBlockCount` - Current block height
- `synor_getBlockHash` - Hash by height
- `synor_getBlock` - Block details
- `synor_getDAGInfo` - DAG structure

### Transaction Methods
- `synor_sendRawTransaction` - Broadcast tx
- `synor_getTransaction` - TX details
- `synor_getMempool` - Pending transactions
- `synor_estimateFee` - Fee estimation

### Address Methods
- `synor_getBalance` - Address balance
- `synor_getUtxos` - Unspent outputs
- `synor_getAddressTransactions` - TX history

### Contract Methods
- `synor_deployContract` - Deploy WASM
- `synor_callContract` - Call method
- `synor_getContractState` - Read storage

---

## Complete Documentation

- [Full API Reference](../API_REFERENCE.md)
- [WebSocket Subscriptions](../WEBSOCKET_GUIDE.md)
- [Exchange Integration](../EXCHANGE_INTEGRATION.md)

---

*Congratulations! You've completed the Synor Developer Tutorial Series!*
