Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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,
address_id INTEGER NOT NULL, -- nbtc pkg is linked through address_id
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,
block_hash TEXT,
Expand Down
2 changes: 1 addition & 1 deletion packages/btcindexer/scripts/seed-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async function main() {
continue;
}

const checkAddrQuery = `SELECT id FROM nbtc_deposit_addresses WHERE setup_id = ${setupId} AND deposit_address = '${entry.btc_address}'`;
const checkAddrQuery = `SELECT 1 FROM nbtc_deposit_addresses WHERE setup_id = ${setupId} AND deposit_address = '${entry.btc_address}'`;
const existingAddrId = await executeQuery<number>(checkAddrQuery, DB_NAME, local, "id");

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The change from SELECT id FROM nbtc_deposit_addresses ... to SELECT 1 FROM ... means the result set no longer has an id column, but executeQuery is still called with field: "id". As a result, executeQuery will always return null for existingAddrId, so the script will try to insert the address every time and can hit the UNIQUE(deposit_address) constraint. Either keep selecting id (so the field lookup remains valid) or drop the field argument and treat a non-empty result as existence.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should be fixed now, please recheck

if (existingAddrId) {
Expand Down
43 changes: 16 additions & 27 deletions packages/btcindexer/src/btcindexer.helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export async function setupTestIndexerSuite(
mockSuiClient.tryMintNbtcBatch.mockResolvedValue(result);
};

const insertTx = async (options: {
const insertTx = async (args: {
txId: string;
status: MintTxStatus | string;
retryCount?: number;
Expand All @@ -276,39 +276,28 @@ export async function setupTestIndexerSuite(
const defaultBlock = testData[329] || testData[327] || Object.values(testData)[0];
if (!defaultBlock) throw new Error("No test data available for default values");

const depositAddr = options.depositAddress || defaultBlock.depositAddr;

// Validate that the deposit address exists in the database
const addressResult = await db
.prepare(`SELECT id FROM nbtc_deposit_addresses WHERE deposit_address = ?`)
.bind(depositAddr)
.first<{ id: number }>();

if (!addressResult) {
throw new Error(
`Deposit address '${depositAddr}' not found in database. ` +
`Make sure to include it in the depositAddresses array during setupTestIndexer().`,
);
}

const depositAddr = args.depositAddress || defaultBlock.depositAddr;
await db
.prepare(
`INSERT INTO nbtc_minting (tx_id, address_id, sender, vout, block_hash, block_height, sui_recipient, amount, status, created_at, updated_at, retry_count)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
VALUES (
?,
(SELECT id FROM nbtc_deposit_addresses WHERE deposit_address = ?),
?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.bind(
options.txId,
addressResult.id,
options.sender || "sender_address",
options.vout ?? 0,
options.blockHash || defaultBlock.hash,
options.blockHeight || defaultBlock.height,
options.suiRecipient || "0xtest_recipient",
options.amount || 10000,
options.status,
args.txId,
depositAddr,
args.sender || "sender_address",
args.vout ?? 0,
args.blockHash || defaultBlock.hash,
args.blockHeight || defaultBlock.height,
args.suiRecipient || "0xtest_recipient",
args.amount || 10000,
args.status,
Date.now(),
Date.now(),
options.retryCount || 0,
args.retryCount || 0,
)
.run();
};
Expand Down
30 changes: 14 additions & 16 deletions packages/btcindexer/src/cf-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,22 +185,21 @@ export class CFStorage implements Storage {
const now = Date.now();
const insertOrUpdateNbtcTxStmt = this.d1.prepare(
`INSERT INTO nbtc_minting (tx_id, address_id, sender, vout, block_hash, block_height, sui_recipient, amount, status, created_at, updated_at, sui_tx_id, retry_count)
VALUES (?, (SELECT a.id FROM nbtc_deposit_addresses a JOIN setups p ON a.setup_id = p.id WHERE p.btc_network = ? AND p.sui_network = ? AND p.nbtc_pkg = ? AND a.deposit_address = ?), ?, ?, ?, ?, ?, ?, '${MintTxStatus.Confirming}', ?, ?, NULL, 0)
ON CONFLICT(tx_id) DO UPDATE SET
block_hash = excluded.block_hash,
block_height = excluded.block_height,
status = '${MintTxStatus.Confirming}',
updated_at = excluded.updated_at,
address_id = excluded.address_id,
sender = excluded.sender`,
VALUES (?,
(SELECT a.id FROM nbtc_deposit_addresses a WHERE a.deposit_address = ?),
?, ?, ?, ?, ?, ?, '${MintTxStatus.Confirming}', ?, ?, NULL, 0)
ON CONFLICT(tx_id) DO UPDATE SET
block_hash = excluded.block_hash,
block_height = excluded.block_height,
status = '${MintTxStatus.Confirming}',
updated_at = excluded.updated_at,
address_id = excluded.address_id,
sender = excluded.sender`,
);
const statements = txs.map((tx) =>
insertOrUpdateNbtcTxStmt.bind(
tx.txId,
tx.btcNetwork,
tx.suiNetwork,
tx.nbtcPkg,
tx.depositAddress,
tx.depositAddress, // inner select param
tx.sender,
tx.vout,
tx.blockHash,
Expand Down Expand Up @@ -419,15 +418,14 @@ export class CFStorage implements Storage {
const now = Date.now();
const insertStmt = this.d1.prepare(
`INSERT OR IGNORE INTO nbtc_minting (tx_id, address_id, sender, vout, sui_recipient, amount, status, created_at, updated_at, sui_tx_id, retry_count)
VALUES (?, (SELECT a.id FROM nbtc_deposit_addresses a JOIN setups p ON a.setup_id = p.id WHERE p.btc_network = ? AND p.sui_network = ? AND p.nbtc_pkg = ? AND a.deposit_address = ?), ?, ?, ?, ?, '${MintTxStatus.Broadcasting}', ?, ?, NULL, 0)`,
VALUES (?,
(SELECT a.id FROM nbtc_deposit_addresses a WHERE a.deposit_address = ?),
?, ?, ?, ?, '${MintTxStatus.Broadcasting}', ?, ?, NULL, 0)`,
);

const statements = deposits.map((deposit) =>
insertStmt.bind(
deposit.txId,
deposit.btcNetwork,
deposit.suiNetwork,
deposit.nbtcPkg,
deposit.depositAddress,
deposit.sender,
deposit.vout,
Expand Down
21 changes: 5 additions & 16 deletions packages/sui-indexer/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,29 +155,18 @@ export class D1Storage {
throw new Error(`Failed to derive address from script_pubkey: ${e}`);
}

const addrRow = await this.db
.prepare(
"SELECT id FROM nbtc_deposit_addresses WHERE setup_id = ? AND deposit_address = ?",
)
.bind(u.setup_id, depositAddress)
.first<{ id: number }>();

if (!addrRow) {
throw new Error(
`Deposit address not found for setup_id=${u.setup_id}, address=${depositAddress}`,
);
}

const stmt = this.db.prepare(
`INSERT OR REPLACE INTO nbtc_utxos
(nbtc_utxo_id, address_id, dwallet_id, txid, vout, amount, script_pubkey, status, locked_until)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
(nbtc_utxo_id, address_id, dwallet_id, txid, vout, amount, script_pubkey, status, locked_until)
VALUES (?,
(SELECT id FROM nbtc_deposit_addresses WHERE deposit_address = ?),
?, ?, ?, ?, ?, ?, ?)`,
);
try {
await stmt
.bind(
u.nbtc_utxo_id,
addrRow.id,
depositAddress,
u.dwallet_id,
u.txid,
u.vout,
Expand Down
Loading