Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 [learnmeabitcoin.com](https://learnmeabitcoin.com/explorer/)
- **Test Helpers**: Located in `packages/lib/src/test-helpers/`

### Configuration Pattern
Expand Down
52 changes: 32 additions & 20 deletions packages/sui-indexer/src/redeem-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,6 @@ export class RedeemService {
const availableUtxos = await this.storage.getAvailableUtxos(req.setup_id);
const selectedUtxos = selectUtxos(availableUtxos, req.amount);

// TODO: we should continue only if our solution is better than the existing one - in case
// someone frontrun us.

if (!selectedUtxos) {
logger.warn({
msg: "Insufficient UTXOs for request",
Expand All @@ -338,26 +335,41 @@ export class RedeemService {
return;
}

const client = this.getSuiClient(req.sui_network);
const proposalArgs = {
redeemId: req.redeem_id,
utxoIds: selectedUtxos.map((u) => u.nbtc_utxo_id),
nbtcPkg: req.nbtc_pkg,
nbtcContract: req.nbtc_contract,
};

try {
const client = this.getSuiClient(req.sui_network);
const txDigest = await client.proposeRedeemUtxos({
redeemId: req.redeem_id,
utxoIds: selectedUtxos.map((u) => u.nbtc_utxo_id),
nbtcPkg: req.nbtc_pkg,
nbtcContract: req.nbtc_contract,
});
const result = await client.proposeRedeemUtxos(proposalArgs);

logger.info({
msg: "Proposed UTXOs for redeem request",
redeemId: req.redeem_id,
txDigest: txDigest,
});
await this.storage.markRedeemProposed(
req.redeem_id,
selectedUtxos.map((u) => u.nbtc_utxo_id),
this.utxoLockTimeMs,
);
if (result.success) {
logger.info({
msg: "Proposed UTXOs for redeem request",
redeemId: req.redeem_id,
txDigest: result.digest,
});
await this.storage.markRedeemProposed(
req.redeem_id,
selectedUtxos.map((u) => u.nbtc_utxo_id),
this.utxoLockTimeMs,
);
} else {
// Contract abort: someone already proposed a better solution.
// Mark as proposed without locking UTXOs so the redeem continue.
logger.info({
msg: "Proposal rejected on-chain, another worker proposed a better solution",
redeemId: req.redeem_id,
txDigest: result.digest,
error: result.error,
});
await this.storage.markRedeemProposed(req.redeem_id, [], 0);
}
} catch (e: unknown) {
// Network error: leave as pending so next cron retry.
logError(
{
msg: "Failed to propose UTXOs for redeem request",
Expand Down
18 changes: 11 additions & 7 deletions packages/sui-indexer/src/redeem-sui-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export interface SuiClientCfg {

export interface SuiClient {
ikaClient(): IkaClient;
proposeRedeemUtxos(args: ProposeRedeemCall): Promise<string>;
proposeRedeemUtxos(
args: ProposeRedeemCall,
): Promise<{ success: boolean; digest: string | null; error: string | null }>;
solveRedeemRequest(args: SolveRedeemCall): Promise<string>;
finalizeRedeem(args: FinalizeRedeemCall): Promise<string>;
requestIkaPresigns(count: number): Promise<string[]>;
Expand Down Expand Up @@ -111,7 +113,9 @@ class SuiClientImp implements SuiClient {
return Buffer.from(decoded).toString("hex");
}

async proposeRedeemUtxos(args: ProposeRedeemCall): Promise<string> {
async proposeRedeemUtxos(
args: ProposeRedeemCall,
): Promise<{ success: boolean; digest: string | null; error: string | null }> {
const tx = new Transaction();

tx.add(
Expand All @@ -135,11 +139,11 @@ class SuiClientImp implements SuiClient {
},
});

if (result.effects?.status.status !== "success") {
throw new Error(`Transaction failed: ${result.effects?.status.error}`);
}

return result.digest;
return {
success: result.effects?.status.status === "success",
digest: result.digest ?? null,
error: result.effects?.status.error ?? null,
};
}

async solveRedeemRequest(args: SolveRedeemCall): Promise<string> {
Expand Down