Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
15 changes: 8 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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 @@

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 @@ -138,7 +139,7 @@

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

### Architecture

Check failure on line 142 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:142 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Architecture"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

- `src/index.ts` - Entry point with scheduled task
- `src/processor.ts` - Sui event indexing
Expand All @@ -156,13 +157,13 @@
- Manages presign objects for Bitcoin transaction signing
- Implements coin selection logic for redemption transactions

### Key Features

Check failure on line 160 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:160 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Key Features"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

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

Check failure on line 166 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:166 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Configuration"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

- **Cron**: Every minute (`* * * * *`)
- **D1 Database**: Shared `btcindexer-dev`
Expand All @@ -176,18 +177,18 @@

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

### Architecture

Check failure on line 180 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:180 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Architecture"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

- `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

Check failure on line 187 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:187 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Key Features"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

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

### Configuration

Check failure on line 191 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:191 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Configuration"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

- **KV Namespace**: `BtcBlocks` (shared with btcindexer)
- **Queue Producer**: `block-queue`
Expand All @@ -197,25 +198,25 @@

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

### Architecture

Check failure on line 201 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:201 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Architecture"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

- `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

Check failure on line 208 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:208 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Key Features"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

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

Check failure on line 214 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:214 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Configuration"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

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

### Database Schema

Check failure on line 219 in AGENTS.md

View workflow job for this annotation

GitHub Actions / lint

Multiple headings with the same content

AGENTS.md:219 MD024/no-duplicate-heading Multiple headings with the same content [Context: "Database Schema"] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md024.md

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

Expand Down
51 changes: 31 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,40 @@ 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,
});
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
17 changes: 10 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 }>;
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 }> {
const tx = new Transaction();

tx.add(
Expand All @@ -135,11 +139,10 @@ 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,
};
}

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