Skip to content

SDK: harden network and response binding#960

Open
mswilkison wants to merge 5 commits into
mainfrom
codex/sdk-client-hardening
Open

SDK: harden network and response binding#960
mswilkison wants to merge 5 commits into
mainfrom
codex/sdk-client-hardening

Conversation

@mswilkison

@mswilkison mswilkison commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bind Solana interface loading to the expected genesis hash from chain mapping
  • Reject Electrum raw transaction responses that do not decode to the requested transaction hash
  • Use HTTPS for the Solana reveal relayer, require a configured deposit owner, and validate relayer receipt shape

Validation

  • Added focused regression coverage for Solana genesis mismatch and Electrum transaction hash mismatch
  • CI passes: TypeScript format, build/test, and docs
  • git diff --check passed locally for the safe-fix batch

Notes

This is public-safe client hardening only and does not include any private deployment mitigation. StarkNet provider-chain binding remains a follow-up because the first implementation made existing tests depend on a retired live RPC endpoint.

Summary by CodeRabbit

  • New Features

    • Enhanced StarkNet address validation against field-prime bounds
    • Solana cross-chain interfaces now validate genesis hash
    • StarkNet cross-chain operations now require an explicit provider
  • Bug Fixes

    • Stronger Electrum transaction hash verification
    • Detect and reject provider chain ID mismatches
    • Enforced deposit-owner presence and stricter relayer response validation
    • Relayer requests use HTTPS
  • Documentation

    • Updated API reference source links
  • Tests

    • Added/expanded tests covering provider/genesis/hash/address validation and integration scenarios

Review Change Stack

@mswilkison mswilkison force-pushed the codex/sdk-client-hardening branch from a915f7b to 5b4f837 Compare May 5, 2026 15:44
@mswilkison mswilkison marked this pull request as ready for review May 5, 2026 16:45
lrsaturnino
lrsaturnino previously approved these changes May 6, 2026

@lrsaturnino lrsaturnino left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances cross-chain deposit flows with mandatory provider validation, stricter address bounds checking, and deposit owner extraction from transaction data. Key changes include Electrum transaction hash verification, Solana genesis hash validation, StarkNet field prime address validation, StarkNet chain ID enforcement, and consistent deposit owner derivation across Solana, Sui, and StarkNet chains.

Changes

Cross-Chain Deposit Validation & Owner Extraction

Layer / File(s) Summary
Electrum Transaction Validation Infrastructure
typescript/src/lib/electrum/client.ts, typescript/test/lib/electrum.test.ts
ElectrumClient methods (getTransaction, getRawTransaction, getTransactionConfirmations) now use shared decodeTransaction and verifyTransactionHash helpers; getTransactionMerkle validates block height matches; new helpers centralize "transaction not found" errors and hash integrity checks with tests for hash mismatch rejection.
StarkNet Address Field Prime Validation
typescript/src/lib/starknet/address.ts, typescript/test/lib/starknet/address.test.ts
STARKNET_FIELD_PRIME constant added; StarkNetAddress constructor now rejects addresses with values ≥ field prime; tests verify maximum valid address acceptance and out-of-range rejection with updated error message.
Solana Genesis Hash Validation
typescript/src/lib/solana/index.ts, typescript/test/lib/solana/index.test.ts
loadSolanaCrossChainInterfaces now requires mandatory genesisHash: Chains.Solana parameter; validates provider's reported genesis hash against expected value and throws detailed mismatch error; test verifies rejection on devnet genesis mismatch.
StarkNet Provider Requirement & Chain Validation
typescript/src/lib/starknet/index.ts, typescript/test/lib/starknet/index.test.ts
loadStarkNetCrossChainInterfaces requires non-optional provider parameter; adds validateProviderChain, resolveProviderChainId, and normalizeStarkNetChainId helpers; removes mock provider fallback; tests verify provider-requirement and chain-mismatch rejection with injected mock providers.
Deposit Owner Extraction (StarkNet, Sui, Solana)
typescript/src/lib/starknet/starknet-depositor.ts, typescript/src/lib/sui/bitcoin-depositor.ts, typescript/src/lib/solana/solana-depositor-interface.ts, typescript/test/lib/starknet/starknet-depositor-payload.test.ts, typescript/test/lib/sui/bitcoin-depositor.test.ts
StarkNetBitcoinDepositor derives l2DepositOwner and l2Sender from deposit.extraData via StarkNetExtraDataEncoder, formats as bytes32, simplifies address validation via StarkNetAddress.from().toBytes32(); SuiBitcoinDepositor.setDepositOwner accepts undefined to clear owner, derives from extraData or stored value, throws on missing owner; SolanaDepositorInterface requires #depositOwner, posts to https relayer, validates response shape via type guard; tests verify address padding, owner extraction from extraData, and owner-requirement errors.
TBTC Service StarkNet Integration
typescript/src/services/tbtc.ts, typescript/test/services/tbtc-starknet-address.test.ts
extractStarkNetAddress now rejects ethers signers and getNetwork-shaped objects with "Expected a StarkNet provider or account" error; replaces regex normalization with StarkNetAddress.from(...).toString(); removes backward-compatible fallback to "0x0" placeholder; test verifies ethers signer rejection.
Test Infrastructure & Helpers
typescript/test/lib/starknet/test-helpers.ts, typescript/test/lib/starknet/extra-data-encoder.test.ts
Mock providers (createMockProvider, createMockStarkNetProvider) now report "SN_SEPOLIA" chain ID instead of "SN_MAIN"; STARKNET_ERROR_MESSAGES adds ADDRESS_EXCEEDS_FIELD_PRIME key; TEST_ADDRESSES.maxFieldElement uses fixed field-prime hex constant; extra-data-encoder tests consolidate full-length address constant validFullLengthAddress.
StarkNet Integration Tests
typescript/test/lib/starknet/integration.test.ts, typescript/test/lib/starknet/module-integration.test.ts
All loadStarkNetCrossChainInterfaces calls now inject mock provider; tests verify rapid sequential loads and instance isolation with provider; module-integration test passes provider to loadStarkNetCrossChainContracts.
API Reference Documentation
typescript/api-reference/README.md, typescript/api-reference/classes/..., typescript/api-reference/interfaces/...
loadSolanaCrossChainInterfaces signature adds genesisHash parameter with throws condition; loadStarkNetCrossChainContracts and loadStarkNetCrossChainInterfaces signatures mark provider as non-optional (no longer optional); numerous "Defined in" line references updated across ElectrumClient, StarkNetAddress, StarkNetBitcoinDepositor, TBTC, and StarkNetBitcoinDepositorConfig documentation.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With validation strong from genesis hash to field prime's bound,
Cross-chains now dance with owners proven found,
From extra data they emerge so bright,
Helper functions keep the flow just right,
Tests bloom abundant—all is tight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'SDK: harden network and response binding' directly reflects the main objective: strengthening network validation (genesis hash, chain binding) and response integrity checks (transaction hash verification, receipt validation).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/sdk-client-hardening

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
typescript/src/lib/starknet/starknet-depositor.ts (1)

111-121: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Accept canonical StarkNet chain ID aliases in relayer URL mapping.

Line [118] looks up chainNameMap with raw config.chainId, but the current map shape rejects valid alias inputs (SN_MAIN), which is already causing runtime construction failures in CI.

💡 Proposed fix
-      const chainNameMap: Record<string, string> = {
+      const chainNameMap: Record<string, string> = {
+        SN_GOERLI: "StarknetTestnet",
+        SN_SEPOLIA: "StarknetTestnet",
+        SN_MAIN: "StarknetMainnet",
         "0x534e5f474f45524c49": "StarknetTestnet", // SN_GOERLI
         "0x534e5f5345504f4c4941": "StarknetTestnet", // SN_SEPOLIA - mapped to StarknetTestnet in relayer
         "0x534e5f4d41494e": "StarknetMainnet", // SN_MAIN
         "0x534e5f544553544e4554": "StarknetTestnet", // Test chain ID
       }
-
-      const chainName = chainNameMap[config.chainId]
+      const normalizedChainId = config.chainId.startsWith("0x")
+        ? config.chainId.toLowerCase()
+        : config.chainId.toUpperCase()
+      const chainName = chainNameMap[normalizedChainId]
       if (!chainName) {
         throw new Error(`Unsupported StarkNet chain ID: ${config.chainId}`)
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/lib/starknet/starknet-depositor.ts` around lines 111 - 121,
The chain lookup in chainNameMap using config.chainId fails for canonical alias
inputs like "SN_MAIN"; update the logic so aliases are accepted by either adding
alias keys ("SN_GOERLI", "SN_SEPOLIA", "SN_MAIN") to chainNameMap mapping to the
same values as their hex counterparts, or normalize config.chainId before lookup
(e.g., translate known aliases to their hex form) so the existing lookup for
chainName succeeds; adjust the code that computes chainName (variable chainName
and map chainNameMap) accordingly to ensure both hex IDs and canonical alias
strings resolve to "StarknetMainnet" or "StarknetTestnet" as intended.
🧹 Nitpick comments (1)
typescript/test/lib/starknet/starknet-depositor-payload.test.ts (1)

245-280: ⚡ Quick win

Restore console.log in a finally block to avoid cross-test leakage.

If this test fails before Line [279], the stub can persist and affect later tests.

🧪 Proposed hardening
   it("should not log reveal request payloads", async () => {
     const consoleLogStub = sinon.stub(console, "log")
-
-    axiosStub.resolves({
-      data: {
-        success: true,
-        receipt: {
-          transactionHash:
-            "0x366220f9853aa8ad83376bcb3fd9377da7b55f03fc3a3aa4aed7b57f7cc60745",
-          blockNumber: 8486402,
-        },
-      },
-    })
-
-    const depositTx: BitcoinRawTxVectors = {
-      version: Hex.from("02000000"),
-      inputs: Hex.from("01" + "a".repeat(64)),
-      outputs: Hex.from("02" + "e".repeat(64)),
-      locktime: Hex.from("00000000"),
-    }
-
-    const deposit: DepositReceipt = {
-      depositor: EthereumAddress.from("0x" + "0".repeat(40)),
-      walletPublicKeyHash: Hex.from("ef5a2946f294f1742a779c9ac034bc3fa5d417b8"),
-      refundPublicKeyHash: Hex.from("b4f19a044feea3aa4a7d3f494433a11d0f1c400e"),
-      blindingFactor: Hex.from("b3460f26eda61ad1"),
-      refundLocktime: Hex.from("a1faa569"),
-      extraData: Hex.from(testAddress),
-    }
-
-    await depositor.initializeDeposit(depositTx, 0, deposit)
-
-    expect(consoleLogStub.called).to.be.false
-
-    consoleLogStub.restore()
+    try {
+      axiosStub.resolves({
+        data: {
+          success: true,
+          receipt: {
+            transactionHash:
+              "0x366220f9853aa8ad83376bcb3fd9377da7b55f03fc3a3aa4aed7b57f7cc60745",
+            blockNumber: 8486402,
+          },
+        },
+      })
+
+      const depositTx: BitcoinRawTxVectors = {
+        version: Hex.from("02000000"),
+        inputs: Hex.from("01" + "a".repeat(64)),
+        outputs: Hex.from("02" + "e".repeat(64)),
+        locktime: Hex.from("00000000"),
+      }
+
+      const deposit: DepositReceipt = {
+        depositor: EthereumAddress.from("0x" + "0".repeat(40)),
+        walletPublicKeyHash: Hex.from("ef5a2946f294f1742a779c9ac034bc3fa5d417b8"),
+        refundPublicKeyHash: Hex.from("b4f19a044feea3aa4a7d3f494433a11d0f1c400e"),
+        blindingFactor: Hex.from("b3460f26eda61ad1"),
+        refundLocktime: Hex.from("a1faa569"),
+        extraData: Hex.from(testAddress),
+      }
+
+      await depositor.initializeDeposit(depositTx, 0, deposit)
+      expect(consoleLogStub.called).to.be.false
+    } finally {
+      consoleLogStub.restore()
+    }
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/test/lib/starknet/starknet-depositor-payload.test.ts` around lines
245 - 280, The test "should not log reveal request payloads" currently stubs
console.log into consoleLogStub and restores it only at the end, which can leak
if the test throws; wrap the body that uses consoleLogStub and calls
depositor.initializeDeposit(...) in a try/finally so that
consoleLogStub.restore() is invoked in the finally block (keeping the assert
expect(consoleLogStub.called).to.be.false inside the try or move it before
restore), ensuring console.log is always restored even on failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@typescript/api-reference/README.md`:
- Around line 1685-1688: The README's "Defined in" source link is stale (points
to lib/starknet/index.ts:95) and must be regenerated so it matches the current
source (should point to lib/starknet/index.ts:92); run the TypeScript docs
generation script used by the repo (the docs build/generation command that
updates typescript/api-reference/README.md), commit the regenerated README so
the "Defined in" link for the StarkNet export in README reflects
lib/starknet/index.ts:92, and re-run the CI checks.

In `@typescript/src/lib/solana/solana-depositor-interface.ts`:
- Around line 74-82: The relayer POST call using
axios.post("https://relayer.tbtcscan.com/api/reveal", ...) must include a
per-request timeout to avoid hanging; update the axios.post invocation in the
SolanaDepositorInterface (the code that constructs
fundingTx/reveal/l2DepositOwner/l2Sender) to pass a third argument with a
timeout (e.g., { timeout: 10000 }) or use a shared configurable constant, and
ensure any timeout error is handled consistently with existing error handling in
that method.

In `@typescript/src/lib/starknet/index.ts`:
- Around line 52-79: validateProviderChain currently accepts alias chain IDs but
the code continues to use the original chainId for lookups (e.g.,
TBTC_CONTRACT_ADDRESSES) and config; this can pass validation but fail contract
resolution. Change the call to capture and use the canonical/normalized chain id
(e.g., const canonicalChainId = await validateProviderChain(provider, chainId))
and then use canonicalChainId everywhere downstream: build
depositorConfig.chainId = canonicalChainId, tokenConfig.chainId =
canonicalChainId, and use canonicalChainId when indexing TBTC_CONTRACT_ADDRESSES
before constructing StarkNetBitcoinDepositor and StarkNetTBTCTokenConfig; if
validateProviderChain does not currently return the canonical id, update it to
return the normalized id or add a helper normalizeChainId(provider, chainId) to
obtain it and use that value consistently.

In `@typescript/src/lib/sui/bitcoin-depositor.ts`:
- Around line 59-60: The exported setter setDepositOwner currently types its
parameter as ChainIdentifier | undefined but its implementation accepts null;
update the public signature to accept null as well (e.g., change the parameter
type to ChainIdentifier | undefined | null or ChainIdentifier | null) so callers
can pass null to clear the owner, and update any related type annotations or
exports referencing setDepositOwner to match the new nullable type; ensure
usages/call sites compile and adjust tests/types if necessary.
- Around line 90-92: The decode of deposit.extraData is happening outside the
initializeDeposit error handling and can throw a raw Error; move or wrap the
call to this.#extraDataEncoder.decodeDepositOwner(deposit.extraData) inside the
existing try/catch in initializeDeposit (or add a small try/catch around the
call) and on failure throw a SuiError with a clear message (include the original
error as cause or message) so that failures from decodeDepositOwner are surfaced
as SuiError like the rest of initializeDeposit; update the assignment to
depositOwner to use the decoded value only after successful decoding, e.g., by
referencing decodeDepositOwner within the protected block and falling back to
this.#depositOwner when appropriate.

In `@typescript/src/services/tbtc.ts`:
- Around line 293-295: The switch on l2ChainName has const declarations inside
case "StarkNet"/"Sui"/"Solana" which violates the noSwitchDeclarations rule and
can cause TDZ issues; fix it by wrapping each case body in its own block (case
"StarkNet": { ... break; }) so consts like the ones used with
TBTC.extractStarkNetAddress/starknetProvider are declared in block scope,
ensuring each case's variables are isolated and the linter error is resolved.

---

Outside diff comments:
In `@typescript/src/lib/starknet/starknet-depositor.ts`:
- Around line 111-121: The chain lookup in chainNameMap using config.chainId
fails for canonical alias inputs like "SN_MAIN"; update the logic so aliases are
accepted by either adding alias keys ("SN_GOERLI", "SN_SEPOLIA", "SN_MAIN") to
chainNameMap mapping to the same values as their hex counterparts, or normalize
config.chainId before lookup (e.g., translate known aliases to their hex form)
so the existing lookup for chainName succeeds; adjust the code that computes
chainName (variable chainName and map chainNameMap) accordingly to ensure both
hex IDs and canonical alias strings resolve to "StarknetMainnet" or
"StarknetTestnet" as intended.

---

Nitpick comments:
In `@typescript/test/lib/starknet/starknet-depositor-payload.test.ts`:
- Around line 245-280: The test "should not log reveal request payloads"
currently stubs console.log into consoleLogStub and restores it only at the end,
which can leak if the test throws; wrap the body that uses consoleLogStub and
calls depositor.initializeDeposit(...) in a try/finally so that
consoleLogStub.restore() is invoked in the finally block (keeping the assert
expect(consoleLogStub.called).to.be.false inside the try or move it before
restore), ensuring console.log is always restored even on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6b63ef4-01af-47cb-8a9c-06cb2783ee92

📥 Commits

Reviewing files that changed from the base of the PR and between 52c02d1 and 686d5e7.

📒 Files selected for processing (25)
  • typescript/api-reference/README.md
  • typescript/api-reference/classes/ElectrumClient.md
  • typescript/api-reference/classes/StarkNetAddress.md
  • typescript/api-reference/classes/StarkNetBitcoinDepositor.md
  • typescript/api-reference/classes/TBTC.md
  • typescript/api-reference/interfaces/StarkNetBitcoinDepositorConfig.md
  • typescript/src/lib/electrum/client.ts
  • typescript/src/lib/solana/index.ts
  • typescript/src/lib/solana/solana-depositor-interface.ts
  • typescript/src/lib/starknet/address.ts
  • typescript/src/lib/starknet/index.ts
  • typescript/src/lib/starknet/starknet-depositor.ts
  • typescript/src/lib/sui/bitcoin-depositor.ts
  • typescript/src/services/tbtc.ts
  • typescript/test/lib/electrum.test.ts
  • typescript/test/lib/solana/index.test.ts
  • typescript/test/lib/starknet/address.test.ts
  • typescript/test/lib/starknet/extra-data-encoder.test.ts
  • typescript/test/lib/starknet/index.test.ts
  • typescript/test/lib/starknet/integration.test.ts
  • typescript/test/lib/starknet/module-integration.test.ts
  • typescript/test/lib/starknet/starknet-depositor-payload.test.ts
  • typescript/test/lib/starknet/test-helpers.ts
  • typescript/test/lib/sui/bitcoin-depositor.test.ts
  • typescript/test/services/tbtc-starknet-address.test.ts

Comment thread typescript/api-reference/README.md Outdated
Comment on lines 74 to 82
const response = await axios.post(
"http://relayer.tbtcscan.com/api/reveal",
"https://relayer.tbtcscan.com/api/reveal",
{
fundingTx,
reveal,
l2DepositOwner: extraData,
l2Sender: `0x${this.#depositOwner?.identifierHex}`,
l2Sender: `0x${this.#depositOwner.identifierHex}`,
}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section in the target file
sed -n '1,140p' typescript/src/lib/solana/solana-depositor-interface.ts | nl -ba | sed -n '60,110p'

# Search for axios.post usages around the relayer URL / file
rg -n "axios\.post\(|relayer\.tbtcscan\.com|api/reveal|timeout" typescript/src/lib/solana/solana-depositor-interface.ts typescript/src/lib -S

# Find axios configuration / default timeout patterns in the repo
rg -n "axios\.create\(|defaults\.timeout|timeout\s*:" typescript/src -S

Repository: threshold-network/tbtc-v2

Length of output: 112


🏁 Script executed:

#!/bin/bash
set +euo pipefail

FILE="typescript/src/lib/solana/solana-depositor-interface.ts"

# Show the relevant section with line numbers via awk
awk 'NR>=60 && NR<=110 {printf "%4d\t%s\n", NR, $0}' "$FILE"

echo "---- axios.post / relayer usage in solana-depositor-interface.ts ----"
rg -n "axios\.post\(|relayer\.tbtcscan\.com|api/reveal|timeout" "$FILE" -S

echo "---- axios configuration / defaults across repo (axios.create, defaults.timeout, timeout:) ----"
rg -n "axios\.create\(|axios\.defaults\.(timeout|common)|defaults\.timeout|timeout\s*:" typescript/src -S

Repository: threshold-network/tbtc-v2

Length of output: 2282


🏁 Script executed:

#!/bin/bash
set +euo pipefail

FILE="typescript/src/lib/solana/solana-depositor-interface.ts"

echo "---- axios import / config in target file ----"
rg -n "import .*axios|axios\.defaults|axios\.create" "$FILE" -S
echo "---- top of file (imports section) ----"
head -n 60 "$FILE" | nl -ba | sed -n '1,60p' || head -n 60 "$FILE"

echo "---- repo-wide axios defaults / create ----"
rg -n "axios\.defaults\.timeout|axios\.defaults|axios\.create\(|axios\.post\(" typescript/src -S

Repository: threshold-network/tbtc-v2

Length of output: 387


🏁 Script executed:

#!/bin/bash
set +euo pipefail

FILE="typescript/src/lib/solana/solana-depositor-interface.ts"

echo "---- timeout occurrences in target file ----"
rg -n "timeout" "$FILE" -S || true

echo "---- axios.post calls passing timeout option (repo-wide) ----"
rg -n "axios\.post\([^\\)]*(?:timeout|timeout\s*:)" typescript/src -S || true

echo "---- axios.defaults or axios.create occurrences (repo-wide) ----"
rg -n "axios\.defaults|axios\.create" typescript/src -S || true

Repository: threshold-network/tbtc-v2

Length of output: 241


Add an explicit timeout to the relayer POST call.

axios.post("https://relayer.tbtcscan.com/api/reveal", ...) is made without a per-request timeout (no axios.defaults.timeout / axios.create-level timeout found), so it can hang indefinitely during network/relayer degradation.

Suggested fix
       const response = await axios.post(
         "https://relayer.tbtcscan.com/api/reveal",
         {
           fundingTx,
           reveal,
           l2DepositOwner: extraData,
           l2Sender: `0x${this.#depositOwner.identifierHex}`,
-        }
+        },
+        {
+          timeout: 15_000,
+        }
       )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/lib/solana/solana-depositor-interface.ts` around lines 74 -
82, The relayer POST call using
axios.post("https://relayer.tbtcscan.com/api/reveal", ...) must include a
per-request timeout to avoid hanging; update the axios.post invocation in the
SolanaDepositorInterface (the code that constructs
fundingTx/reveal/l2DepositOwner/l2Sender) to pass a third argument with a
timeout (e.g., { timeout: 10000 }) or use a shared configurable constant, and
ensure any timeout error is handled consistently with existing error handling in
that method.

Comment on lines +52 to +79
await validateProviderChain(provider, chainId)

// Build depositor configuration with environment variable support
const depositorConfig: StarkNetBitcoinDepositorConfig = {
chainId,
relayerUrl: process.env.STARKNET_RELAYER_URL, // Optional override
defaultVault: process.env.STARKNET_TBTC_VAULT, // Optional override
}

// Create provider if not provided (for testing/backward compatibility)
const actualProvider = provider || createMockProvider()

// Create the main depositor instance
const starkNetBitcoinDepositor = new StarkNetBitcoinDepositor(
depositorConfig,
"StarkNet",
actualProvider
provider
)

// Set the deposit owner
starkNetBitcoinDepositor.setDepositOwner(StarkNetAddress.from(walletAddress))

// Create token instance
let starkNetTbtcToken: StarkNetTBTCToken

if (provider) {
// Provider available - create full implementation with balance queries
const tokenContract = TBTC_CONTRACT_ADDRESSES[chainId]
if (!tokenContract) {
throw new Error(`No tBTC contract address for chain ${chainId}`)
}

const tokenConfig: StarkNetTBTCTokenConfig = {
chainId,
tokenContract,
}

starkNetTbtcToken = new StarkNetTBTCToken(tokenConfig, provider)
} else {
// No provider - create interface-only implementation
// This maintains backward compatibility for tests
const mockConfig: StarkNetTBTCTokenConfig = {
chainId,
tokenContract: "0x0", // Placeholder
}

// Create a mock provider that throws errors
const mockProvider = {
getChainId: () => Promise.resolve(chainId),
} as any

starkNetTbtcToken = new StarkNetTBTCToken(mockConfig, mockProvider)
const tokenContract = TBTC_CONTRACT_ADDRESSES[chainId]
if (!tokenContract) {
throw new Error(`No tBTC contract address for chain ${chainId}`)
}

const tokenConfig: StarkNetTBTCTokenConfig = {
chainId,
tokenContract,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize chainId once and use the canonical value for downstream config/lookups.

validateProviderChain accepts alias chain IDs, but TBTC_CONTRACT_ADDRESSES[chainId] and config still use the raw value. That can pass validation and then fail contract resolution for alias inputs.

Suggested fix
 export async function loadStarkNetCrossChainInterfaces(
   walletAddress: string,
   provider: StarkNetProvider,
   chainId: string = Chains.StarkNet.Sepolia
 ): Promise<DestinationChainInterfaces> {
   if (!provider) {
     throw new Error("StarkNet provider is required")
   }

-  await validateProviderChain(provider, chainId)
+  const normalizedChainId = normalizeStarkNetChainId(chainId)
+  await validateProviderChain(provider, normalizedChainId)

   // Build depositor configuration with environment variable support
   const depositorConfig: StarkNetBitcoinDepositorConfig = {
-    chainId,
+    chainId: normalizedChainId,
     relayerUrl: process.env.STARKNET_RELAYER_URL, // Optional override
     defaultVault: process.env.STARKNET_TBTC_VAULT, // Optional override
   }
@@
-  const tokenContract = TBTC_CONTRACT_ADDRESSES[chainId]
+  const tokenContract = TBTC_CONTRACT_ADDRESSES[normalizedChainId]
   if (!tokenContract) {
-    throw new Error(`No tBTC contract address for chain ${chainId}`)
+    throw new Error(`No tBTC contract address for chain ${normalizedChainId}`)
   }

   const tokenConfig: StarkNetTBTCTokenConfig = {
-    chainId,
+    chainId: normalizedChainId,
     tokenContract,
   }

Also applies to: 126-134

🧰 Tools
🪛 GitHub Actions: TypeScript bindings / 1_typescript-build-and-test.txt

[error] 52-52: Failed to load StarkNet cross-chain interfaces because starknet_chainId RPC call failed.

🪛 GitHub Actions: TypeScript bindings / typescript-build-and-test

[error] 52-110: RPC failed during StarkNet chain id resolution: RpcError: RPC: starknet_chainId with params undefined; -32000: Blast API is no longer available. Please update your integration to use Alchemy's API instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/lib/starknet/index.ts` around lines 52 - 79,
validateProviderChain currently accepts alias chain IDs but the code continues
to use the original chainId for lookups (e.g., TBTC_CONTRACT_ADDRESSES) and
config; this can pass validation but fail contract resolution. Change the call
to capture and use the canonical/normalized chain id (e.g., const
canonicalChainId = await validateProviderChain(provider, chainId)) and then use
canonicalChainId everywhere downstream: build depositorConfig.chainId =
canonicalChainId, tokenConfig.chainId = canonicalChainId, and use
canonicalChainId when indexing TBTC_CONTRACT_ADDRESSES before constructing
StarkNetBitcoinDepositor and StarkNetTBTCTokenConfig; if validateProviderChain
does not currently return the canonical id, update it to return the normalized
id or add a helper normalizeChainId(provider, chainId) to obtain it and use that
value consistently.

Comment on lines +59 to +60
setDepositOwner(depositOwner: ChainIdentifier | undefined): void {
if (depositOwner === undefined || depositOwner === null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include null in the exported setter type.

The body treats null as a supported way to clear the owner, but the public signature only accepts undefined. That means TS consumers still can't use the nullable API this change is trying to expose.

Suggested fix
-  setDepositOwner(depositOwner: ChainIdentifier | undefined): void {
+  setDepositOwner(depositOwner: ChainIdentifier | null | undefined): void {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setDepositOwner(depositOwner: ChainIdentifier | undefined): void {
if (depositOwner === undefined || depositOwner === null) {
setDepositOwner(depositOwner: ChainIdentifier | null | undefined): void {
if (depositOwner === undefined || depositOwner === null) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/lib/sui/bitcoin-depositor.ts` around lines 59 - 60, The
exported setter setDepositOwner currently types its parameter as ChainIdentifier
| undefined but its implementation accepts null; update the public signature to
accept null as well (e.g., change the parameter type to ChainIdentifier |
undefined | null or ChainIdentifier | null) so callers can pass null to clear
the owner, and update any related type annotations or exports referencing
setDepositOwner to match the new nullable type; ensure usages/call sites compile
and adjust tests/types if necessary.

Comment on lines +90 to +92
const depositOwner = deposit.extraData
? this.#extraDataEncoder.decodeDepositOwner(deposit.extraData)
: this.#depositOwner

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap malformed extraData decode failures in SuiError.

decodeDepositOwner(...) runs before the existing try/catch, so invalid external deposit.extraData escapes as a raw Error instead of the typed SuiError used for the rest of initializeDeposit.

Suggested fix
-    const depositOwner = deposit.extraData
-      ? this.#extraDataEncoder.decodeDepositOwner(deposit.extraData)
-      : this.#depositOwner
+    let depositOwner = this.#depositOwner
+    if (deposit.extraData) {
+      try {
+        depositOwner = this.#extraDataEncoder.decodeDepositOwner(
+          deposit.extraData
+        )
+      } catch (error) {
+        throw new SuiError("Invalid SUI deposit owner in extra data", error)
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/lib/sui/bitcoin-depositor.ts` around lines 90 - 92, The decode
of deposit.extraData is happening outside the initializeDeposit error handling
and can throw a raw Error; move or wrap the call to
this.#extraDataEncoder.decodeDepositOwner(deposit.extraData) inside the existing
try/catch in initializeDeposit (or add a small try/catch around the call) and on
failure throw a SuiError with a clear message (include the original error as
cause or message) so that failures from decodeDepositOwner are surfaced as
SuiError like the rest of initializeDeposit; update the assignment to
depositOwner to use the decoded value only after successful decoding, e.g., by
referencing decodeDepositOwner within the protected block and falling back to
this.#depositOwner when appropriate.

Comment on lines +293 to +295
const walletAddressHex = await TBTC.extractStarkNetAddress(
starknetProvider
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="typescript/src/services/tbtc.ts"

# Show the referenced area and a bit more context to understand the surrounding switch/case.
sed -n '240,380p' "$FILE" | nl -ba | sed -n '1,200p'

# Specifically locate the switch and the StarkNet/Sui/Solana cases (in case line numbers drift).
rg -n --context 3 'switch\s*\(' "$FILE"
rg -n 'case\s+"(StarkNet|Sui|Solana)"' "$FILE" --context 5

# Search for the other referenced lines region.
sed -n '300,360p' "$FILE" | nl -ba

# If eslint plugin is configured in repo, locate the rule name to confirm intended lint behavior.
rg -n 'noSwitchDeclarations' -S .

Repository: threshold-network/tbtc-v2

Length of output: 112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="typescript/src/services/tbtc.ts"

echo "== Context around lines ~250-380 =="
awk 'NR>=240 && NR<=380 {printf "%5d  %s\n", NR, $0}' "$FILE"

echo
echo "== Cases for StarkNet/Sui/Solana =="
rg -n 'case\s+"(StarkNet|Sui|Solana)"' "$FILE" -n --context 8

echo
echo "== Find any switch() in this file =="
rg -n 'switch\s*\(' "$FILE"

echo
echo "== Confirm lint rule presence (search for noSwitchDeclarations) =="
rg -n 'noSwitchDeclarations' -S . || true

Repository: threshold-network/tbtc-v2

Length of output: 7519


Block-scope switch case const declarations for l2ChainName (StarkNet/Sui/Solana)—Biome noSwitchDeclarations is set to error.

typescript/src/services/tbtc.ts declares const directly inside case "StarkNet", case "Sui", and case "Solana" without wrapping each case body in { ... }, which both triggers the configured lint rule and risks switch-scope/TDZ hazards.

🧰 Tools
🪛 Biome (2.4.15)

[error] 293-295: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

(lint/correctness/noSwitchDeclarations)

🪛 GitHub Actions: TypeScript bindings / 1_typescript-build-and-test.txt

[error] 293-293: TBTC.initializeCrossChain failed because extractStarkNetAddress rejected the provided StarkNet provider.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/src/services/tbtc.ts` around lines 293 - 295, The switch on
l2ChainName has const declarations inside case "StarkNet"/"Sui"/"Solana" which
violates the noSwitchDeclarations rule and can cause TDZ issues; fix it by
wrapping each case body in its own block (case "StarkNet": { ... break; }) so
consts like the ones used with TBTC.extractStarkNetAddress/starknetProvider are
declared in block scope, ensuring each case's variables are isolated and the
linter error is resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
typescript/test/services/tbtc-starknet.test.ts (1)

65-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test intent and assertion diverge for _l2Signer.

At Line 65, the test says it verifies _l2Signer storage, but it only asserts contracts exist. This won’t catch _l2Signer regressions; either rename the test or assert the expected _l2Signer state explicitly.

Suggested fix
- it("should store StarkNet provider in _l2Signer property", async () => {
+ it("should initialize StarkNet contracts in single-parameter mode", async () => {
   const starknetProvider = createMockProvider()

   // Act
   await tbtc.initializeCrossChain("StarkNet", starknetProvider as any)

   // Assert - check internal _l2Signer property
-  // Note: This would require making _l2Signer accessible for testing
-  // or using a getter method
   const contracts = tbtc.crossChainContracts("StarkNet")
   expect(contracts).to.not.be.undefined
+  expect((tbtc as any)._l2Signer).to.be.undefined
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/test/services/tbtc-starknet.test.ts` around lines 65 - 76, The
test name claims to verify storage of the internal _l2Signer but only checks
crossChainContracts; update the test to either (A) rename it to reflect that it
only asserts contracts exist, or (B) assert the actual _l2Signer is set by
exposing it via a test accessor/getter or using an existing method: call
tbtc.initializeCrossChain("StarkNet", starknetProvider) and then read/assert
tbtc._l2Signer (or tbtc.getL2Signer()/equivalent) equals the provided
provider/mock; if no accessor exists add a minimal test-only getter that returns
_l2Signer so the test can assert identity/instance rather than only checking
crossChainContracts("StarkNet").
typescript/test/lib/starknet/provider-integration.test.ts (1)

52-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix async assertion in provider-integration.test.ts deposit-flow test

expect(async () => { ... }).to.not.throw() won’t reliably fail on async rejection (the throw check is sync-oriented). Use the repo’s promise matcher style instead (await expect(promise).to.not.be.rejected).

Suggested change
-      // Act & Assert - should not throw
-      expect(async () => {
-        await tbtc.initializeCrossChain("StarkNet", starknetProvider)
-        // Verify contracts were created (not _l2Signer)
-        const contracts = tbtc.crossChainContracts("StarkNet")
-        expect(contracts).to.exist
-      }).to.not.throw()
+      await expect(
+        tbtc.initializeCrossChain("StarkNet", starknetProvider)
+      ).to.not.be.rejected
+
+      // Verify contracts were created (not _l2Signer)
+      const contracts = tbtc.crossChainContracts("StarkNet")
+      expect(contracts).to.exist
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@typescript/test/lib/starknet/provider-integration.test.ts` around lines 52 -
77, The test uses expect(async () => { ... }).to.not.throw(), which doesn't
catch async rejections; instead call the async flow as a promise and use the
promise matcher: create a promise that runs await
tbtc.initializeCrossChain("StarkNet", starknetProvider) and then checks
tbtc.crossChainContracts("StarkNet") exists, then assert with await
expect(promise).to.not.be.rejected (or await
expect(promise).to.eventually.be.fulfilled depending on chai setup); replace the
current expect wrapper around the async arrow with this promise-based assertion
and keep references to initializeCrossChain and crossChainContracts so the test
verifies the same behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@typescript/test/lib/starknet/provider-integration.test.ts`:
- Around line 52-77: The test uses expect(async () => { ... }).to.not.throw(),
which doesn't catch async rejections; instead call the async flow as a promise
and use the promise matcher: create a promise that runs await
tbtc.initializeCrossChain("StarkNet", starknetProvider) and then checks
tbtc.crossChainContracts("StarkNet") exists, then assert with await
expect(promise).to.not.be.rejected (or await
expect(promise).to.eventually.be.fulfilled depending on chai setup); replace the
current expect wrapper around the async arrow with this promise-based assertion
and keep references to initializeCrossChain and crossChainContracts so the test
verifies the same behavior.

In `@typescript/test/services/tbtc-starknet.test.ts`:
- Around line 65-76: The test name claims to verify storage of the internal
_l2Signer but only checks crossChainContracts; update the test to either (A)
rename it to reflect that it only asserts contracts exist, or (B) assert the
actual _l2Signer is set by exposing it via a test accessor/getter or using an
existing method: call tbtc.initializeCrossChain("StarkNet", starknetProvider)
and then read/assert tbtc._l2Signer (or tbtc.getL2Signer()/equivalent) equals
the provided provider/mock; if no accessor exists add a minimal test-only getter
that returns _l2Signer so the test can assert identity/instance rather than only
checking crossChainContracts("StarkNet").

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dfa990b-0bd4-48d8-a58a-777ec279fefb

📥 Commits

Reviewing files that changed from the base of the PR and between 686d5e7 and 31d669a.

📒 Files selected for processing (10)
  • typescript/api-reference/README.md
  • typescript/test/integration/starknet-single-param-flow.test.ts
  • typescript/test/lib/starknet/provider-integration.test.ts
  • typescript/test/lib/starknet/starknet-depositor-implementation.test.ts
  • typescript/test/lib/starknet/starknet-depositor.test.ts
  • typescript/test/services/tbtc-refactored-init.test.ts
  • typescript/test/services/tbtc-single-param.test.ts
  • typescript/test/services/tbtc-starknet.test.ts
  • typescript/test/services/tbtc-t003-l2signer.test.ts
  • typescript/test/services/threshold-context-provider-compatibility.test.ts
✅ Files skipped from review due to trivial changes (2)
  • typescript/test/lib/starknet/starknet-depositor-implementation.test.ts
  • typescript/test/lib/starknet/starknet-depositor.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • typescript/api-reference/README.md

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.

2 participants