Skip to content
49 changes: 25 additions & 24 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This is a Cloudflare Workers project that implements various services for [Go Na
It uses [Bun](https://bun.com/) for JavaScript and Typescript runtime and package management (instead of Nodejs + npm).

The content is organized into a Bun workspace. See @README.md for:

- High-level architecture and component interactions
- Detailed setup instructions
- Functional flow documentation
Expand All @@ -15,13 +16,13 @@ The content is organized into a Bun workspace. See @README.md for:

All packages are in the `./packages` directory:

| Package | Type | Purpose |
|---------|------|---------|
| `btcindexer` | Service (Worker) | Bitcoin-to-Sui bridging and minting |
| `sui-indexer` | Service (Worker) | Sui blockchain monitoring and redemption |
| `block-ingestor` | Service (Worker) | Receives Bitcoin blocks via REST API |
| `compliance` | Service (Worker) | Sanctions and geo-blocking data |
| `lib` | Shared Library | Common utilities and types |
| Package | Type | Purpose |
| ---------------- | ---------------- | ---------------------------------------- |
| `btcindexer` | Service (Worker) | Bitcoin-to-Sui bridging and minting |
| `sui-indexer` | Service (Worker) | Sui blockchain monitoring and redemption |
| `block-ingestor` | Service (Worker) | Receives Bitcoin blocks via REST API |
| `compliance` | Service (Worker) | Sanctions and geo-blocking data |
| `lib` | Shared Library | Common utilities and types |

### Core Technologies

Expand Down Expand Up @@ -96,7 +97,7 @@ See @README.md for Cloudflare RPC documentation and examples.

**Location**: `./packages/btcindexer`

### Architecture
### BTCIndexer Architecture

- `src/index.ts` - Main entry with HTTP handlers and scheduled cron
- `src/btcindexer.ts` - Bitcoin indexing and deposit detection logic
Expand All @@ -105,14 +106,14 @@ See @README.md for Cloudflare RPC documentation and examples.
- `src/cf-storage.ts` - Cloudflare D1/KV storage layer
- `src/bitcoin-merkle-tree.ts` - Merkle proof generation

### Key Features
### BTCIndexer Key Features

1. **Bitcoin Block Processing**: Cron job runs every minute to scan blocks, identify nBTC deposits via OP_RETURN outputs
2. **Cross-Chain Minting**: Tracks deposits and mints corresponding nBTC on Sui with Merkle proof validation
3. **Data Storage**: Uses D1 for transaction data, KV for block storage
4. **Queue Consumption**: Consumes blocks from `block-queue` populated by block-ingestor

### Configuration
### BTCIndexer Configuration

- **Cron**: Every minute (`* * * * *`)
- **D1 Database**: `btcindexer-dev`
Expand All @@ -121,7 +122,7 @@ See @README.md for Cloudflare RPC documentation and examples.
- **Service Bindings**: `SuiIndexer`, `Compliance`
- **Secrets**: `NBTC_MINTING_SIGNER_MNEMONIC` (via Secrets Store)

### Database Schema
### BTCIndexer Database Schema

See migration files in `packages/btcindexer/db/migrations/`:

Expand All @@ -138,7 +139,7 @@ See migration files in `packages/btcindexer/db/migrations/`:

**Location**: `./packages/sui-indexer`

### Architecture
### Sui Indexer Architecture

- `src/index.ts` - Entry point with scheduled task
- `src/processor.ts` - Sui event indexing
Expand All @@ -156,13 +157,13 @@ The Sui Indexer integrates with IKA (MPC service) for threshold signature operat
- Manages presign objects for Bitcoin transaction signing
- Implements coin selection logic for redemption transactions

### Key Features
### Sui Indexer Key Features

1. **Event Monitoring**: Indexes Sui events for nBTC redemption requests
2. **Redemption Processing**: Handles burn-and-redeem flow with IKA MPC
3. **UTXO Management**: Manages UTXO lifecycle (available → locked → spent)

### Configuration
### Sui Indexer Configuration

- **Cron**: Every minute (`* * * * *`)
- **D1 Database**: Shared `btcindexer-dev`
Expand All @@ -176,18 +177,18 @@ The Sui Indexer integrates with IKA (MPC service) for threshold signature operat

See @packages/block-ingestor/README.md for detailed architecture.

### Architecture
### Block Ingestor Architecture

- `src/index.ts` - HTTP router and handlers
- `src/ingest.ts` - Block ingestion logic
- `src/api/put-blocks.ts` - msgpack encoding/decoding
- `src/api/client.ts` - Client for sending blocks

### Key Features
### Block Ingestor Key Features

Receives Bitcoin blocks via REST API, validates them, and enqueues to `block-queue` for processing by BTCIndexer.

### Configuration
### Block Ingestor Configuration

- **KV Namespace**: `BtcBlocks` (shared with btcindexer)
- **Queue Producer**: `block-queue`
Expand All @@ -197,33 +198,33 @@ Receives Bitcoin blocks via REST API, validates them, and enqueues to `block-que

**Location**: `./packages/compliance`

### Architecture
### Compliance Architecture

- `src/index.ts` - Scheduled worker entry point
- `src/sanction.ts` - Sanctions list updating logic
- `src/storage.ts` - D1 storage for sanctions
- `src/rpc.ts` - RPC interface for other services to query compliance data

### Key Features
### Compliance Key Features

1. **Sanctions List Updates**: Daily cron job fetches and updates sanctions data
2. **Compliance API**: Exposes RPC methods for other services to check addresses
3. **Geo-blocking**: Supports geo-blocking rules

### Configuration
### Compliance Configuration

- **Cron**: Daily at 1am (`0 1 * * *`)
- **D1 Database**: `compliance`

### Database Schema
### Compliance Database Schema

See migration files in `packages/compliance/db/migrations/`.

## Lib

**Location**: `./packages/lib`

### Architecture
### Lib Architecture

Shared library package containing utilities used across all services:

Expand All @@ -237,7 +238,7 @@ Shared library package containing utilities used across all services:
- `src/rpc-types.ts` - Shared RPC type definitions
- `src/test-helpers/` - Test utilities including D1 initialization

### Key Features
### Lib Key Features

1. **Shared Types**: Network enums, block records, transaction status types
2. **Testing Support**: Mock RPC implementations and D1 test helpers
Expand All @@ -255,7 +256,7 @@ Shared library package containing utilities used across all services:

- **Framework**: Bun's built-in test framework with Miniflare for mocking Workers
- **Mock RPC**: Each service provides `RPCMock` implementation for isolated testing
- **Test Data**: Real Bitcoin regtest blocks from https://learnmeabitcoin.com/explorer/
- **Test Data**: Real Bitcoin regtest blocks from <https://learnmeabitcoin.com/explorer/>
- **Test Helpers**: Located in `packages/lib/src/test-helpers/`

### Configuration Pattern
Expand Down
4 changes: 2 additions & 2 deletions packages/block-ingestor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## Key Features

#### 1. Block Reception
### 1. Block Reception

- Exposes REST API endpoint to receive new Bitcoin blocks
- Uses msgpack encoding for efficient data transmission
- Stores blocks in Cloudflare KV for fast retrieval

#### 2. Queue Processing
### 2. Queue Processing

- Enqueues blocks to Cloudflare Queue for downstream processing
- Implements proper batching for efficient processing
Expand Down
3 changes: 2 additions & 1 deletion packages/btcindexer/db/migrations/0001_initial_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CREATE INDEX IF NOT EXISTS btc_blocks_is_scanned_height ON btc_blocks (is_scanne

-- This table tracks the nBTC deposit txs (minting)
CREATE TABLE IF NOT EXISTS nbtc_minting (
tx_id TEXT NOT NULL PRIMARY KEY,
tx_id TEXT NOT NULL,
address_id INTEGER NOT NULL, -- nbtc pkg is linked through address_id -> nbtc_deposit_addresses.setup_id
sender TEXT NOT NULL,
vout INTEGER NOT NULL,
Expand All @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS nbtc_minting (
updated_at INTEGER NOT NULL, -- timestamp_ms
sui_tx_id TEXT,
retry_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (tx_id, address_id),
FOREIGN KEY (address_id) REFERENCES nbtc_deposit_addresses(id)
) STRICT;

Expand Down
18 changes: 13 additions & 5 deletions packages/btcindexer/src/btcindexer.helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ export interface TestIndexerHelper {

expectMintingCount: (count: number) => Promise<void>;
expectSenderCount: (count: number, expectedAddress?: string) => Promise<void>;
expectTxStatus: (txId: string, expectedStatus: MintTxStatus | string) => Promise<void>;
expectTxStatus: (
txId: string,
expectedStatus: MintTxStatus | string,
setupId: number,
) => Promise<void>;
}

// test suite helper functions constructor.
Expand Down Expand Up @@ -336,11 +340,15 @@ export async function setupTestIndexerSuite(
const expectTxStatus = async (
txId: string,
expectedStatus: MintTxStatus | string,
setupId: number,
): Promise<void> => {
const { results } = await db
.prepare("SELECT status FROM nbtc_minting WHERE tx_id = ?")
.bind(txId)
.all();
const query = db
.prepare(
"SELECT status FROM nbtc_minting WHERE tx_id = ? AND address_id IN (SELECT id FROM nbtc_deposit_addresses WHERE setup_id = ?)",
)
.bind(txId, setupId);

const { results } = await query.all<{ status: string }>();
expect(results.length).toEqual(1);
expect(results[0]).toBeDefined();
expect(results[0]!.status).toEqual(expectedStatus);
Expand Down
19 changes: 11 additions & 8 deletions packages/btcindexer/src/btcindexer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ describe("Indexer.splitActiveInactiveTxs", () => {
block_height: 100,
btc_network: BtcNet.REGTEST,
deposit_address: REGTEST_DATA[329]!.depositAddr,
setup_id: 1,
};
const { activeTxIds } = indexer.splitActiveInactiveTxs([pendingTx]);
expect(activeTxIds.length).toEqual(1);
Expand All @@ -247,6 +248,7 @@ describe("Indexer.splitActiveInactiveTxs (Inactive)", () => {
block_height: 100,
btc_network: BtcNet.REGTEST,
deposit_address: REGTEST_DATA[329]!.depositAddr,
setup_id: 1,
};
const result = indexer.splitActiveInactiveTxs([pendingTx]);

Expand All @@ -264,6 +266,7 @@ describe("Indexer.splitActiveInactiveTxs (Inactive)", () => {
block_height: 100,
btc_network: BtcNet.REGTEST,
deposit_address: "inactive_address",
setup_id: 1,
};

const originalMap = indexer.nbtcDepositAddrMap;
Expand Down Expand Up @@ -355,7 +358,7 @@ describe("Indexer.registerBroadcastedNbtcTx", () => {

describe("Indexer.hasNbtcMintTx", () => {
it("should return false when transaction does not exist", async () => {
const result = await indexer.hasNbtcMintTx("nonexistent_tx_id");
const result = await indexer.hasNbtcMintTx("nonexistent_tx_id", 1);
expect(result).toBe(false);
});

Expand All @@ -365,7 +368,7 @@ describe("Indexer.hasNbtcMintTx", () => {

await indexer.registerBroadcastedNbtcTx(txHex, BtcNet.REGTEST);

const result = await indexer.hasNbtcMintTx(txInfo.id);
const result = await indexer.hasNbtcMintTx(txInfo.id, 1);
expect(result).toBe(true);
});
});
Expand Down Expand Up @@ -579,9 +582,9 @@ describe("Indexer.detectMintedReorgs", () => {
.bind(blockData.hash, blockData.height, BtcNet.REGTEST, Date.now(), 1)
.run();

await indexer.detectMintedReorgs(blockData.height);
await indexer.detectMintedReorgs(blockData.height, 1);

const status = await indexer.storage.getTxStatus(txData.id);
const status = await indexer.storage.getTxStatus(txData.id, 1);
expect(status).toEqual(MintTxStatus.Minted);
});
});
Expand Down Expand Up @@ -622,7 +625,7 @@ describe("Indexer.processBlock", () => {
await suite.setupBlock(327);
await indexer.processBlock(reorgBlockInfo);

const status = await indexer.storage.getTxStatus(txData.id);
const status = await indexer.storage.getTxStatus(txData.id, 1);
expect(status).toEqual(MintTxStatus.MintedReorg);
});
});
Expand Down Expand Up @@ -726,7 +729,7 @@ describe("Indexer.verifyConfirmingBlocks", () => {
suite.mockSuiClient.verifyBlocks,
"Verify that verifyBlocks was called with the correct block hash",
).toHaveBeenCalledWith([block329.hash]);
await suite.expectTxStatus(tx329.id, "reorg");
await suite.expectTxStatus(tx329.id, "reorg", 1);
});

it("should verify confirming blocks and not update status if blocks are still valid", async () => {
Expand All @@ -749,7 +752,7 @@ describe("Indexer.verifyConfirmingBlocks", () => {
).toHaveBeenCalledWith([block329.hash]);

// Check that the transaction status remains 'confirming' since block is still valid
await suite.expectTxStatus(tx329.id, "confirming");
await suite.expectTxStatus(tx329.id, "confirming", 1);
});

it("should handle empty confirming blocks list", async () => {
Expand All @@ -772,7 +775,7 @@ describe("Indexer.verifyConfirmingBlocks", () => {
await indexer.verifyConfirmingBlocks();

expect(suite.mockSuiClient.verifyBlocks).toHaveBeenCalledWith([block329.hash]);
await suite.expectTxStatus(tx329.id, "confirming");
await suite.expectTxStatus(tx329.id, "confirming", 1);
});
});

Expand Down
Loading