Skip to content

feat: don't process sanctioned BTC and SUI address#336

Merged
rmeena840 merged 4 commits intomasterfrom
323-workers-dont-process-sanctioned-addresses
Feb 12, 2026
Merged

feat: don't process sanctioned BTC and SUI address#336
rmeena840 merged 4 commits intomasterfrom
323-workers-dont-process-sanctioned-addresses

Conversation

@rmeena840
Copy link
Contributor

@rmeena840 rmeena840 commented Feb 6, 2026

Description

Closes: #323


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • included the correct type prefix in the PR title
  • added ! to the type prefix if API or client breaking change
  • added appropriate labels to the PR
  • provided a link to the relevant issue or specification
  • added a changelog entry to CHANGELOG.md
  • included doc comments for public functions
  • updated the relevant documentation or specification
  • reviewed "Files changed" and left comments if necessary

Summary by Sourcery

Integrate sanctions data into the BTC indexer to prevent minting for sanctioned BTC and SUI addresses and wire this into scheduled processing and Sui transaction handling.

New Features:

  • Introduce a scheduled job that downloads OFAC sanctions wallet data and stores sanctioned BTC and SUI addresses in a new database table.
  • Add sender BTC address extraction and sanctions checking to Sui minting so that transactions from sanctioned wallets are skipped.

Enhancements:

  • Refactor BTC network configuration into a shared model for reuse across the indexer and Sui client.
  • Extend the Sui client and indexer wiring to accept a database handle for sanctions lookups.
  • Adjust the scheduled handler to branch logic based on cron triggers and guard confirmation updates on the presence of indexed blocks.

Chores:

  • Add a SanctionedCryptoAddresses table and index to the database schema to persist sanctioned wallet addresses.

@rmeena840 rmeena840 self-assigned this Feb 6, 2026
@rmeena840 rmeena840 requested a review from a team as a code owner February 6, 2026 10:07
@rmeena840 rmeena840 linked an issue Feb 6, 2026 that may be closed by this pull request
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 6, 2026

Reviewer's Guide

Adds sanctions-awareness to the BTC indexer and Sui minting flow by persisting sanctioned BTC/SUI addresses from OpenSanctions into a new table, wiring a scheduled job to refresh that data, checking BTC senders against sanctions before minting, and refactoring BTC network config sharing.

Sequence diagram for scheduled sanctions sync job

sequenceDiagram
  actor Scheduler
  participant Worker as BtcIndexerWorker
  participant SanctionJob as processSanctionedAddress
  participant OpenSanctionsAPI
  participant DB as D1Database

  Scheduler->>Worker: cron 0_1_*_*_* scheduled event
  Worker->>SanctionJob: processSanctionedAddress(env)
  SanctionJob->>OpenSanctionsAPI: HTTP GET targets.nested.json
  OpenSanctionsAPI-->>SanctionJob: Streamed JSON lines
  loop Stream JSON
    SanctionJob->>SanctionJob: Decode chunk and buffer lines
    SanctionJob->>SanctionJob: Extract BTC and SUI addresses
  end
  SanctionJob->>DB: DELETE FROM SanctionedCryptoAddresses
  SanctionJob->>DB: INSERT OR IGNORE BTC wallet_address rows
  SanctionJob->>DB: INSERT SUI wallet_address rows
  DB-->>SanctionJob: Batch completed
  SanctionJob-->>Worker: Success
  Worker-->>Scheduler: Log cron job finished
Loading

Sequence diagram for BTC minting flow with sanctions check

sequenceDiagram
  actor User
  participant BTCNet as BitcoinNetwork
  participant Indexer as BtcIndexer
  participant SuiClient
  participant DB as D1Database
  participant SuiChain

  User->>BTCNet: Send BTC from wallet_address
  BTCNet-->>Indexer: Block with BTC transaction
  Indexer->>Indexer: updateConfirmationsAndFinalize()
  Indexer->>Indexer: processFinalizedTransactions()
  Indexer->>SuiClient: mintNbtcBatch(args)
  SuiClient->>SuiClient: extractSenderAddress(tx)
  alt SegWit input
    SuiClient->>SuiClient: payments.p2wpkh(pubkey, network)
  else Legacy input
    SuiClient->>SuiClient: script.decompile(scriptSig)
    SuiClient->>SuiClient: payments.p2pkh(pubkey, network)
  end
  SuiClient->>DB: SELECT 1 FROM SanctionedCryptoAddresses WHERE wallet_address = btcAddress AND address_type = BTC
  DB-->>SuiClient: Row or null
  alt Address sanctioned
    SuiClient->>Indexer: Log and skip mint for tx
    Indexer-->>User: No nBTC minted for this deposit
  else Address not sanctioned
    SuiClient->>SuiChain: Submit nBTC mint transaction
    SuiChain-->>SuiClient: SuiTxDigest
    SuiClient-->>Indexer: Mint result
    Indexer-->>User: nBTC minted on Sui
  end
Loading

Entity relationship diagram for sanctioned crypto addresses table

erDiagram
  SanctionedCryptoAddresses {
    string wallet_address PK
    string address_type
  }
Loading

Updated class diagram for SuiClient sanctions-aware minting

classDiagram
  class SuiClient {
    - Client client
    - Signer signer
    - NbtcPkgCfg config
    - D1Database db
    + SuiClient(config NbtcPkgCfg, mnemonic string, db D1Database)
    + mintNbtcBatch(args MintBatchArg) Promise_MintBatchResult_
    - extractSenderAddress(tx Transaction) Promise_string_
    - isSanctioned(btcAddress string) Promise_boolean_
  }

  class NewSuiClientFactory {
    + NewSuiClient(mnemonic string, db D1Database) SuiClientConstructor
  }

  class Models {
    + btcNetworkCfg : Record_BtcNet_Network_
  }

  NewSuiClientFactory ..> SuiClient : creates
  SuiClient ..> Models : uses btcNetworkCfg
  SuiClient ..> D1Database : reads_sanctioned_addresses
  class D1Database
  class MintBatchArg
  class NbtcPkgCfg
  class Transaction
  class Client
  class Signer

  SuiClient o-- Client
  SuiClient o-- Signer
  SuiClient o-- NbtcPkgCfg
  SuiClient o-- D1Database
Loading

File-Level Changes

Change Details Files
Introduce sanctions ingestion pipeline from OpenSanctions into Cloudflare D1 and wire it into the scheduled worker.
  • Add processSanctionedAddress function to stream and parse us_ofac_sdn targets JSON, extracting BTC (XBT) and SUI wallet addresses.
  • Batch delete-and-reinsert sanctioned addresses into a new SanctionedCryptoAddresses table with type-specific inserts for BTC and SUI.
  • Update scheduled handler to branch on cron expression: a daily job to refresh sanctions data and the existing per-minute job for indexing and mint processing.
packages/btcindexer/src/sanction.ts
packages/btcindexer/src/index.ts
Persist sanctioned crypto addresses in the indexer database.
  • Add SanctionedCryptoAddresses table with wallet_address primary key, address_type discriminator, and a check constraint limiting types to BTC/SUI.
  • Create an index on address_type to optimize lookups by currency type.
  • Wire the new schema into the initial migration so new environments have the sanctions table by default.
packages/btcindexer/db/migrations/0001_initial_schema.sql
Prevent minting for transactions whose BTC sender address is sanctioned.
  • Inject D1Database into SuiClient via constructor/NewSuiClient and plumb env.DB in indexerFromEnv so the Sui client can query sanctions.
  • Implement extractSenderAddress to derive the BTC sender from the first input, supporting P2WPKH (witness) and P2PKH (scriptSig) using bitcoinjs-lib and shared btcNetworkCfg.
  • Implement isSanctioned to query SanctionedCryptoAddresses for BTC addresses and early-continue mintNbtcBatch when a sanctioned sender is detected, with structured logging for detections and errors.
packages/btcindexer/src/sui_client.ts
packages/btcindexer/src/btcindexer.ts
Centralize and share BTC network configuration across modules.
  • Move btcNetworkCfg definition from btcindexer.ts into models.ts so it can be reused.
  • Update Sui client and indexer code to import btcNetworkCfg from models instead of local definition.
packages/btcindexer/src/btcindexer.ts
packages/btcindexer/src/models.ts
packages/btcindexer/src/sui_client.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#323 Ensure workers do not process requests/transactions involving sanctioned Bitcoin (BTC) addresses.
#323 Ensure workers do not process requests/transactions involving sanctioned Sui (SUI) addresses. While SUI addresses are fetched from the sanctions source and stored in the SanctionedCryptoAddresses table, there is no code in the PR that checks SUI addresses against this table or skips processing when a SUI address is sanctioned.
#323 Implement a mechanism (either cached or on-demand) for obtaining and using the list of sanctioned BTC and SUI addresses.

Possibly linked issues

  • #unknown: PR implements sanctioned BTC/SUI detection, storage, and skipping of mints/processing as requested in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In btcindexer.ts the ConfirmingTxCandidate interface is now declared twice (once with network and once without), which will cause a duplicate identifier error; keep only the updated definition and remove the old one.
  • The SuiClient constructor takes db?: D1Database but then assigns this.db = db! and isSanctioned assumes it is always present; either make db required everywhere or guard access to this.db to avoid a potential runtime failure.
  • In processSanctionedAddress you’re using StringDecoder and Buffer, which are Node-specific; in a Workers environment you should switch to TextDecoder and work with the Uint8Array chunks directly to ensure this code runs correctly on the target platform.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `btcindexer.ts` the `ConfirmingTxCandidate` interface is now declared twice (once with `network` and once without), which will cause a duplicate identifier error; keep only the updated definition and remove the old one.
- The `SuiClient` constructor takes `db?: D1Database` but then assigns `this.db = db!` and `isSanctioned` assumes it is always present; either make `db` required everywhere or guard access to `this.db` to avoid a potential runtime failure.
- In `processSanctionedAddress` you’re using `StringDecoder` and `Buffer`, which are Node-specific; in a Workers environment you should switch to `TextDecoder` and work with the `Uint8Array` chunks directly to ensure this code runs correctly on the target platform.

## Individual Comments

### Comment 1
<location> `packages/btcindexer/src/sanction.ts:2-11` </location>
<code_context>
+import { StringDecoder } from "string_decoder";
</code_context>

<issue_to_address>
**issue (bug_risk):** Use of Node.js `StringDecoder` and `Buffer` is incompatible with the Cloudflare Workers runtime.

Workers don’t include Node core modules like `string_decoder` or `Buffer`, so this streaming code will not run there. Use the standard Web `TextDecoder` on the `Uint8Array` chunks from `reader.read()`, e.g. `const decoder = new TextDecoder(); buffer += decoder.decode(value, { stream: true });`, and remove all `Buffer` usage.
</issue_to_address>

### Comment 2
<location> `packages/btcindexer/src/sanction.ts:78-87` </location>
<code_context>
+			env.DB.prepare("DELETE FROM SanctionedCryptoAddresses"),
+		];
+
+		// Add BTC inserts
+		btcAddresses.forEach((addr) => {
+			statements.push(
+				env.DB.prepare(
+					"INSERT OR IGNORE INTO SanctionedCryptoAddresses (wallet_address, address_type) VALUES (?, ?)",
+				).bind(addr, "BTC"),
+			);
+		});
+
+		// Add SUI inserts
+		suiAddresses.forEach((addr) => {
+			statements.push(
+				env.DB.prepare(
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Inconsistent use of `INSERT OR IGNORE` for BTC vs SUI can cause constraint violations on duplicates.

For BTC you use `INSERT OR IGNORE`, but for SUI you use a plain `INSERT` into a table where `wallet_address` is the primary key. Any duplicate SUI address (or rerun with the same data) will cause the batch to fail with a constraint error. To avoid this and align behaviors, switch SUI inserts to `INSERT OR IGNORE` (or `ON CONFLICT DO NOTHING` when available).
</issue_to_address>

### Comment 3
<location> `packages/btcindexer/src/sui_client.ts:186-195` </location>
<code_context>
 		return [success, result.digest];
 	}

+	private async extractSenderAddress(tx: Transaction): Promise<string | null> {
+		try {
+			if (tx.ins.length === 0) return null;
+
+			const firstInput = tx.ins[0];
+			const network = btcNetworkCfg[this.config.btc_network];
+
+			if (firstInput && firstInput.witness && firstInput.witness.length >= 2) {
+				const pubKey = firstInput.witness[1];
+				const { address } = payments.p2wpkh({
+					pubkey: pubKey,
+					network: network,
+				});
+				return address || null;
+			}
+
+			const scriptSig = firstInput?.script;
+			if (scriptSig && scriptSig.length >= 65) {
+				const chunks = script.decompile(scriptSig);
+				if (!chunks) return null;
+
+				const pubKey = chunks[chunks.length - 1] as Buffer;
+
+				const { address } = payments.p2pkh({
+					pubkey: pubKey,
+					network: network,
+				});
+				return address || null;
+			}
+
+			return null;
+		} catch (e) {
+			console.error("Failed to extract sender address", e);
+			return null;
</code_context>

<issue_to_address>
**nitpick:** Falling back to `console.error` here is inconsistent with the rest of the logging approach.

This method is the only place using `console.error` instead of `logger`/`logError`. For consistency and better observability/structured logging—especially given the sanctions impact—please route this through `logError` or `logger.error` and include relevant context.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@rmeena840 rmeena840 force-pushed the 323-workers-dont-process-sanctioned-addresses branch from d9d9ce2 to 9813c3d Compare February 6, 2026 11:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds sanctions-awareness to the BTC indexer by ingesting OFAC-related wallet data into D1 and skipping Sui minting when the originating BTC sender address is sanctioned (plus wiring a daily cron to refresh the dataset).

Changes:

  • Add a daily scheduled job to download/parse OFAC sanctions data and persist BTC/SUI addresses to a new D1 table.
  • Add BTC sender extraction + sanctions lookup to skip mint calls originating from sanctioned wallets.
  • Refactor BTC network config into a shared btcNetworkCfg model and wire env.DB into the Sui client.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
packages/lib/src/test-helpers/init_db.ts Adds the new sanctions table to test DB cleanup/init helpers.
packages/btcindexer/wrangler.jsonc Adds a daily cron trigger for sanctions ingestion.
packages/btcindexer/src/sui_client.ts Injects D1 DB handle and checks sanctions before adding mint calls.
packages/btcindexer/src/sanction.ts New: downloads/parses sanctions dataset and updates D1 table.
packages/btcindexer/src/models.ts New shared btcNetworkCfg mapping for bitcoinjs-lib networks.
packages/btcindexer/src/index.ts Routes scheduled events by cron string and runs sanctions ingestion daily.
packages/btcindexer/src/btcindexer.ts Passes env.DB into SuiClient and imports shared btcNetworkCfg.
packages/btcindexer/db/migrations/0001_initial_schema.sql Adds SanctionedCryptoAddresses table + index to schema.

@rmeena840 rmeena840 force-pushed the 323-workers-dont-process-sanctioned-addresses branch from faabbac to 916b2de Compare February 9, 2026 13:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@rmeena840 rmeena840 force-pushed the 323-workers-dont-process-sanctioned-addresses branch 4 times, most recently from a4df735 to 3c25424 Compare February 11, 2026 04:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 19 changed files in this pull request and generated 15 comments.

Copy link
Contributor

@robert-zaremba robert-zaremba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look good

  1. code is not clear and good for testing
  2. need to improve code organization
  3. functions are too complex rather being broken down

@rmeena840 rmeena840 force-pushed the 323-workers-dont-process-sanctioned-addresses branch from 984c3c9 to 773d99b Compare February 12, 2026 03:21
Signed-off-by: Ravindra Meena <rmeena840@gmail.com>
@rmeena840 rmeena840 force-pushed the 323-workers-dont-process-sanctioned-addresses branch from 773d99b to 721b792 Compare February 12, 2026 11:55
Signed-off-by: Ravindra Meena <rmeena840@gmail.com>
Signed-off-by: Ravindra Meena <rmeena840@gmail.com>
Signed-off-by: Ravindra Meena <rmeena840@gmail.com>
@rmeena840 rmeena840 enabled auto-merge (squash) February 12, 2026 15:40
@rmeena840 rmeena840 merged commit 96baa6f into master Feb 12, 2026
12 checks passed
@rmeena840 rmeena840 deleted the 323-workers-dont-process-sanctioned-addresses branch February 12, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Workers: don't process sanctioned addresses

3 participants