Repository: zeta-chain/toolkit
Pull request #325: feat(bitcoin): deposit/call commands & push data larger than 520 bytes in chunks
Author: login: fadeev
Labels: name: feat
Created: 2025-05-08T06:57:26Z | Merged: 2025-05-15T07:55:43Z

* push data larger than 520 bytes in chunks

Commit TXID: 8f0056a8786680fe2a5b170a530f53513bf25d2a0680b9d3a63d760edf676f22 (Signet)
Reveal TXID: b7293f17c6c3d29615feaedac6304b5823b4325c7b6672865ec966bf767932d9

✅ https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/crosschain/inboundHashToCctxData/b7293f17c6c3d29615feaedac6304b5823b4325c7b6672865ec966bf767932d9

* added `--data` param, which can be used **instead** of types/values to be compatible with `bitcoin encode`.

```
yarn zetachain bitcoin deposit-and-call \
  --private-key $PRIVATE_KEY \
  --amount 0.0001 \
  --data $(yarn -s zetachain bitcoin encode --receiver 0x9ee1cD7Bce3a03E73ca7851c19D9746Fbe482C76 --values hello --types string --revert-address tb1qw9tydqfkhcnl2f2m0m0xus3h90s9zd6zj7rhs8) 
```

```
Commit TXID: ec1d9078affd6ce20b0b57a2cdd853b9224a2a9fae9ddf759082d7a944dddab4
Reveal TXID: 0a05ed49545204d03db88daf5bfa93cc5e9177075701a4f27a3cb97d898a4576
```

✅ https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/crosschain/inboundHashToCctxData/0a05ed49545204d03db88daf5bfa93cc5e9177075701a4f27a3cb97d898a4576

* `bitcoin encode` should accept empty types/values (for inscription based regular deposits)

```
yarn -s zetachain bitcoin encode \
  --receiver 0x9ee1cD7Bce3a03E73ca7851c19D9746Fbe482C76 \
  --values hello \
  --types string \
  --revert-address tb1qw9tydqfkhcnl2f2m0m0xus3h90s9zd6zj7rhs8
```

```
5a0010070000000000000000000000009ee1cd7bce3a03e73ca7851c19d9746fbe482c76000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a74623171773974796471666b68636e6c3266326d306d307875733368393073397a64367a6a377268733800000000000000000000000000000000000000000000
```

```
yarn -s zetachain bitcoin encode \
  --receiver 0x9ee1cD7Bce3a03E73ca7851c19D9746Fbe482C76 \
  --revert-address tb1qw9tydqfkhcnl2f2m0m0xus3h90s9zd6zj7rhs8                
```

```
5a0010070000000000000000000000009ee1cd7bce3a03e73ca7851c19d9746fbe482c76000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a74623171773974796471666b68636e6c3266326d306d307875733368393073397a64367a6a377268733800000000000000000000000000000000000000000000
```

* `deposit` command

```
yarn zetachain bitcoin deposit \
  --amount 0.001 \
  --private-key $PRIVATE_KEY \
  --receiver 0x4955a3F38ff86ae92A914445099caa8eA2B9bA32
```

```
Commit TXID: eaaabfe041c0784d31a5bb8db3ff255b31ae5bd4a81f918a73e39ab3d4f3cd8c
Reveal TXID: b1934876ab53b211fc1e3168bd0b4e2df6a5d9f3bd1be6c77a88666a7c9e926e
```

✅ https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/crosschain/inboundHashToCctxData/b1934876ab53b211fc1e3168bd0b4e2df6a5d9f3bd1be6c77a88666a7c9e926e

* `call` command

```
yarn zetachain bitcoin call \
  --receiver 0x9ee1cD7Bce3a03E73ca7851c19D9746Fbe482C76 \
  --values hello \
  --types string  \
  --private-key $PRIVATE_KEY \
  --revert-address tb1qw9tydqfkhcnl2f2m0m0xus3h90s9zd6zj7rhs8
```

```
Commit TXID: 6c92cb80f093176b865c1431770e43c9264074d797acabbfee244f96751aac61
Reveal TXID: cdb52721e9787c94cda196304d9f699cc89d661c9946bac32f7bdfcf17e08eaa
```

✅ https://zetachain-athens.blockpi.network/lcd/v1/public/zeta-chain/crosschain/inboundHashToCctxData/cdb52721e9787c94cda196304d9f699cc89d661c9946bac32f7bdfcf17e08eaa

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added new Bitcoin CLI commands for contract calls and deposits on ZetaChain with flexible options for contract data input.
  - Introduced detailed fee calculation and display for commit, reveal, and total fees in Bitcoin transactions.
  - Enabled raw hex data input alongside ABI encoding for deposit, call, and combined deposit-and-call commands.
  - Added user confirmation prompts displaying comprehensive transaction details before broadcasting.

- **Improvements**
  - Enhanced CLI input validation with optional and mutually exclusive parameters for better usability.
  - Refined transaction creation to enforce minimum commit amounts, chunk large data for Bitcoin scripts, and improve fee calculations.
  - Standardized private key naming across account-related schemas, utilities, and tests for consistency.
  - Updated test coverage to verify encoding behavior with empty payloads.

- **Documentation**
  - Updated command option descriptions and help texts to reflect new features and input validations accurately.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/commands/src/accounts/show.ts b/packages/commands/src/accounts/show.ts
index 18c7915e..fdd182ee 100644
--- a/packages/commands/src/accounts/show.ts
+++ b/packages/commands/src/accounts/show.ts
@@ -60,7 +60,7 @@ const getBitcoinAccountDetails = (
     mainnetAddress: keyData.mainnetAddress,
     mainnetWIF: keyData.mainnetWIF,
     name: keyData.name || "N/A",
-    privateKeyBytes: keyData.privateKeyBytes,
+    privateKeyBytes: keyData.privateKey,
     testnetAddress: keyData.testnetAddress,
     testnetWIF: keyData.testnetWIF,
     type: "bitcoin",
diff --git a/packages/commands/src/bitcoin/call.ts b/packages/commands/src/bitcoin/call.ts
new file mode 100644
index 00000000..33444867
--- /dev/null
+++ b/packages/commands/src/bitcoin/call.ts
@@ -0,0 +1,127 @@
+import { Command, Option } from "commander";
+import { ethers } from "ethers";
+import { z } from "zod";
+
+import { BITCOIN_LIMITS } from "../../../../types/bitcoin.constants";
+import { callOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  addCommonOptions,
+  createAndBroadcastTransactions,
+  displayAndConfirmTransaction,
+  fetchUtxos,
+  setupBitcoinKeyPair,
+} from "../../../../utils/bitcoin.command.helpers";
+import { calculateFees } from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+  trimOx,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+
+type CallOptions = z.infer<typeof callOptionsSchema>;
+
+/**
+ * Main function that executes the call operation.
+ * Creates and broadcasts both commit and reveal transactions to perform a cross-chain call.
+ *
+ * @param options - Command options including amounts, addresses, and contract parameters
+ */
+const main = async (options: CallOptions) => {
+  const { key, address } = setupBitcoinKeyPair(
+    options.privateKey,
+    options.name
+  );
+  const utxos = await fetchUtxos(address, options.api);
+
+  let data;
+  let payload;
+  if (
+    options.types &&
+    options.values &&
+    options.receiver &&
+    options.revertAddress
+  ) {
+    payload = new ethers.AbiCoder().encode(options.types, options.values);
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(trimOx(payload), "hex"),
+        options.revertAddress,
+        OpCode.Call,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error(
+      "Provide either data or types, values, receiver, and revert address"
+    );
+  }
+
+  const amount =
+    BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE;
+
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
+
+  await displayAndConfirmTransaction({
+    commitFee,
+    encodedMessage: payload,
+    encodingFormat: "ABI",
+    gateway: options.gateway,
+    network: "Signet",
+    operation: "Call",
+    rawInscriptionData: data.toString("hex"),
+    receiver: options.receiver,
+    revealFee,
+    revertAddress: options.revertAddress,
+    sender: address,
+    totalFee,
+  });
+
+  await createAndBroadcastTransactions(
+    key,
+    utxos,
+    address,
+    data,
+    options.api,
+    amount,
+    options.gateway
+  );
+};
+
+/**
+ * Command definition for call
+ * This allows users to call contracts on ZetaChain
+ */
+export const callCommand = new Command()
+  .name("call")
+  .description("Call a contract on ZetaChain")
+  .option("-r, --receiver <address>", "ZetaChain receiver address")
+  .requiredOption(
+    "-g, --gateway <address>",
+    "Bitcoin gateway (TSS) address",
+    "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur"
+  )
+  .option("-t, --types <types...>", "ABI types")
+  .option("-v, --values <values...>", "Values corresponding to types")
+  .option("-a, --revert-address <address>", "Revert address")
+  .addOption(
+    new Option("--data <data>", "Pass raw data").conflicts([
+      "types",
+      "values",
+      "revert-address",
+      "receiver",
+    ])
+  )
+  .action(async (opts) => {
+    const validated = validateAndParseSchema(opts, callOptionsSchema, {
+      exitOnError: true,
+    });
+    await main(validated);
+  });
+
+addCommonOptions(callCommand);
diff --git a/packages/commands/src/bitcoin/deposit.ts b/packages/commands/src/bitcoin/deposit.ts
new file mode 100644
index 00000000..edce79fc
--- /dev/null
+++ b/packages/commands/src/bitcoin/deposit.ts
@@ -0,0 +1,109 @@
+import { Command, Option } from "commander";
+import { z } from "zod";
+
+import { BITCOIN_LIMITS } from "../../../../types/bitcoin.constants";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  addCommonOptions,
+  createAndBroadcastTransactions,
+  displayAndConfirmTransaction,
+  fetchUtxos,
+  setupBitcoinKeyPair,
+} from "../../../../utils/bitcoin.command.helpers";
+import { calculateFees } from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+
+type DepositOptions = z.infer<typeof depositOptionsSchema>;
+
+const main = async (options: DepositOptions) => {
+  const { key, address } = setupBitcoinKeyPair(
+    options.privateKey,
+    options.name
+  );
+  const utxos = await fetchUtxos(address, options.api);
+
+  const revertAddress = options.revertAddress || address;
+
+  let data;
+  if (options.receiver && revertAddress) {
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(""), // Empty payload for deposit
+        revertAddress,
+        OpCode.Deposit,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error("Provide either --receiver or receiver encoded in --data");
+  }
+
+  const amount =
+    BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE;
+
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
+
+  await displayAndConfirmTransaction({
+    amount: options.amount,
+    commitFee,
+    encodingFormat: "ABI",
+    gateway: options.gateway,
+    network: "Signet",
+    operation: "Deposit",
+    rawInscriptionData: data.toString("hex"),
+    receiver: options.receiver,
+    revealFee,
+    revertAddress,
+    sender: address,
+    totalFee,
+  });
+
+  await createAndBroadcastTransactions(
+    key,
+    utxos,
+    address,
+    data,
+    options.api,
+    amount,
+    options.gateway
+  );
+};
+
+/**
+ * Command definition for deposit
+ * This allows users to deposit BTC to EOAs and contracts on ZetaChain
+ */
+export const depositCommand = new Command()
+  .name("deposit")
+  .description("Deposit BTC to ZetaChain")
+  .option("-r, --receiver <address>", "ZetaChain receiver address")
+  .requiredOption(
+    "-g, --gateway <address>",
+    "Bitcoin gateway (TSS) address",
+    "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur"
+  )
+  .option("-a, --revert-address <address>", "Revert address")
+  .requiredOption("--amount <btcAmount>", "BTC amount to send (in BTC)")
+  .addOption(
+    new Option("--data <data>", "Pass raw data").conflicts([
+      "revert-address",
+      "receiver",
+    ])
+  )
+  .action(async (opts) => {
+    const validated = validateAndParseSchema(opts, depositOptionsSchema, {
+      exitOnError: true,
+    });
+    await main(validated);
+  });
+
+addCommonOptions(depositCommand);
diff --git a/packages/commands/src/bitcoin/depositAndCall.ts b/packages/commands/src/bitcoin/depositAndCall.ts
index d93e55f6..bfca93d3 100644
--- a/packages/commands/src/bitcoin/depositAndCall.ts
+++ b/packages/commands/src/bitcoin/depositAndCall.ts
@@ -1,20 +1,17 @@
-import confirm from "@inquirer/confirm";
-import axios from "axios";
-import * as bitcoin from "bitcoinjs-lib";
-import { Command } from "commander";
-import ECPairFactory from "ecpair";
+import { Command, Option } from "commander";
 import { ethers } from "ethers";
-import * as ecc from "tiny-secp256k1";
 import { z } from "zod";
 
-import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
-import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { BITCOIN_LIMITS } from "../../../../types/bitcoin.constants";
 import { depositAndCallOptionsSchema } from "../../../../types/bitcoin.types";
 import {
-  makeCommitTransaction,
-  makeRevealTransaction,
-  SIGNET,
-} from "../../../../utils/bitcoin.helpers";
+  addCommonOptions,
+  createAndBroadcastTransactions,
+  displayAndConfirmTransaction,
+  fetchUtxos,
+  setupBitcoinKeyPair,
+} from "../../../../utils/bitcoin.command.helpers";
+import { calculateFees } from "../../../../utils/bitcoin.helpers";
 import {
   bitcoinEncode,
   EncodingFormat,
@@ -32,99 +29,71 @@ type DepositAndCallOptions = z.infer<typeof depositAndCallOptionsSchema>;
  * @param options - Command options including amounts, addresses, and contract parameters
  */
 const main = async (options: DepositAndCallOptions) => {
-  // Initialize Bitcoin library with ECC implementation
-  bitcoin.initEccLib(ecc);
-
-  // Set up Bitcoin key pair
-  const ECPair = ECPairFactory(ecc);
-  const key = ECPair.fromPrivateKey(Buffer.from(options.privateKey, "hex"), {
-    network: SIGNET,
-  });
-  const { address } = bitcoin.payments.p2wpkh({
-    network: SIGNET,
-    pubkey: key.publicKey,
-  });
-
-  // Get available UTXOs
-  const utxos = (
-    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
-  ).data;
-
-  // Encode contract call data for inscription
-  const encodedPayload = new ethers.AbiCoder().encode(
-    options.types,
-    options.values
-  );
-  const inscriptionData = Buffer.from(
-    bitcoinEncode(
-      options.receiver,
-      Buffer.from(trimOx(encodedPayload), "hex"),
-      options.revertAddress,
-      OpCode.DepositAndCall,
-      EncodingFormat.EncodingFmtABI
-    ),
-    "hex"
+  const { key, address } = setupBitcoinKeyPair(
+    options.privateKey,
+    options.name
   );
+  const utxos = await fetchUtxos(address, options.api);
 
-  // Convert BTC amount to satoshis
-  const amountSatBig = ethers.parseUnits(options.amount, 8);
-  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
-    throw new Error("Amount exceeds JS safe-integer range");
+  let data;
+  let payload;
+  if (
+    options.types &&
+    options.values &&
+    options.receiver &&
+    options.revertAddress
+  ) {
+    // Encode contract call data for inscription
+    payload = new ethers.AbiCoder().encode(options.types, options.values);
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(trimOx(payload), "hex"),
+        options.revertAddress,
+        OpCode.DepositAndCall,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error(
+      "Provide either data or types, values, receiver, and revert address"
+    );
   }
-  const amountSat = Number(amountSatBig);
+
+  const amount =
+    BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE;
+
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
 
   // Display transaction information and confirm
-  console.log(`
-Network: Signet
-Amount: ${options.amount} BTC
-Gateway: ${options.gateway}
-Universal Contract: ${options.receiver}
-Revert Address: ${options.revertAddress}
-Operation: DepositAndCall
-Encoded Message: ${encodedPayload}
-Encoding Format: ABI
-Raw Inscription Data: ${inscriptionData.toString("hex")}
-`);
-  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+  await displayAndConfirmTransaction({
+    amount: options.amount,
+    commitFee,
+    encodedMessage: payload,
+    encodingFormat: "ABI",
+    gateway: options.gateway,
+    network: "Signet",
+    operation: "DepositAndCall",
+    rawInscriptionData: data.toString("hex"),
+    receiver: options.receiver,
+    revealFee,
+    revertAddress: options.revertAddress,
+    sender: address,
+    totalFee,
+  });
 
-  // Create and broadcast commit transaction
-  const commit = await makeCommitTransaction(
+  await createAndBroadcastTransactions(
     key,
     utxos,
-    address!,
-    inscriptionData,
+    address,
+    data,
     options.api,
-    amountSat
-  );
-
-  const commitTxid = (
-    await axios.post<string>(`${options.api}/tx`, commit.txHex, {
-      headers: { "Content-Type": "text/plain" },
-    })
-  ).data;
-
-  console.log("Commit TXID:", commitTxid);
-
-  // Create and broadcast reveal transaction
-  const revealHex = makeRevealTransaction(
-    commitTxid,
-    0,
-    amountSat,
-    options.gateway,
-    BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE,
-    {
-      controlBlock: commit.controlBlock,
-      internalKey: commit.internalKey,
-      leafScript: commit.leafScript,
-    },
-    key
+    amount,
+    options.gateway
   );
-  const revealTxid = (
-    await axios.post<string>(`${options.api}/tx`, revealHex, {
-      headers: { "Content-Type": "text/plain" },
-    })
-  ).data;
-  console.log("Reveal TXID:", revealTxid);
 };
 
 /**
@@ -136,18 +105,24 @@ export const depositAndCallCommand = new Command()
   .description(
     "Deposit BTC and call a contract on ZetaChain (using inscriptions)"
   )
-  .requiredOption("-r, --receiver <address>", "ZetaChain receiver address")
+  .option("-r, --receiver <address>", "ZetaChain receiver address")
   .requiredOption(
     "-g, --gateway <address>",
     "Bitcoin gateway (TSS) address",
     "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur"
   )
-  .requiredOption("-t, --types <types...>", "ABI types")
-  .requiredOption("-v, --values <values...>", "Values corresponding to types")
-  .requiredOption("-a, --revert-address <address>", "Revert address")
-  .requiredOption("--amount <btcAmount>", "BTC amount to inscribe (in BTC)")
-  .option("--api <url>", "Bitcoin API", "https://mempool.space/signet/api")
-  .requiredOption("--private-key <key>", "Bitcoin private key")
+  .option("-t, --types <types...>", "ABI types")
+  .option("-v, --values <values...>", "Values corresponding to types")
+  .option("-a, --revert-address <address>", "Revert address")
+  .requiredOption("--amount <btcAmount>", "BTC amount to send (in BTC)")
+  .addOption(
+    new Option("--data <data>", "Pass raw data").conflicts([
+      "types",
+      "values",
+      "revert-address",
+      "receiver",
+    ])
+  )
   .action(async (opts) => {
     const validated = validateAndParseSchema(
       opts,
@@ -158,3 +133,5 @@ export const depositAndCallCommand = new Command()
     );
     await main(validated);
   });
+
+addCommonOptions(depositAndCallCommand);
diff --git a/packages/commands/src/bitcoin/encode.ts b/packages/commands/src/bitcoin/encode.ts
index a93f3953..2421c09c 100644
--- a/packages/commands/src/bitcoin/encode.ts
+++ b/packages/commands/src/bitcoin/encode.ts
@@ -1,7 +1,9 @@
 import { Buffer } from "buffer";
 import { Command, Option } from "commander";
 import { ethers } from "ethers";
+import { z } from "zod";
 
+import { typesAndValuesLengthRefineRule } from "../../../../types/shared.schema";
 import {
   bitcoinEncode,
   EncodingFormat,
@@ -9,25 +11,36 @@ import {
   trimOx,
 } from "../../../../utils/bitcoinEncode";
 
-interface EncodeOptions {
-  format?: string;
-  opCode?: string;
-  receiver: string;
-  revertAddress: string;
-  types: string[];
-  values: string[];
-}
+const encodeOptionsSchema = z
+  .object({
+    format: z.string().optional(),
+    opCode: z.string().optional(),
+    receiver: z.string(),
+    revertAddress: z.string(),
+    types: z.array(z.string()).optional().default([]),
+    values: z.array(z.string()).optional().default([]),
+  })
+  .refine(typesAndValuesLengthRefineRule.rule, {
+    message: typesAndValuesLengthRefineRule.message,
+    path: typesAndValuesLengthRefineRule.path,
+  });
+
+type EncodeOptions = z.infer<typeof encodeOptionsSchema>;
 
 const main = (options: EncodeOptions) => {
-  if (options.types.length !== options.values.length) {
-    throw new Error("Number of types must match number of values");
-  }
+  // Ensure types and values are arrays even if not provided
+  const types = options.types || [];
+  const values = options.values || [];
 
-  const encodedPayload = new ethers.AbiCoder().encode(
-    options.types,
-    options.values
-  );
-  const payloadBuffer = Buffer.from(trimOx(encodedPayload), "hex");
+  let payloadBuffer: Buffer;
+
+  if (types.length === 0 && values.length === 0) {
+    // Empty payload
+    payloadBuffer = Buffer.from([]);
+  } else {
+    const encodedPayload = new ethers.AbiCoder().encode(types, values);
+    payloadBuffer = Buffer.from(trimOx(encodedPayload), "hex");
+  }
 
   // Encode the data
   const result = bitcoinEncode(
@@ -45,8 +58,8 @@ export const encodeCommand = new Command()
   .name("encode")
   .description("Encode data for Bitcoin transactions using ABI encoding")
   .requiredOption("-r, --receiver <address>", "Receiver address")
-  .requiredOption("-t, --types <types...>", "ABI types (e.g. string uint256)")
-  .requiredOption("-v, --values <values...>", "Values corresponding to types")
+  .option("-t, --types <types...>", "ABI types (e.g. string uint256)", [])
+  .option("-v, --values <values...>", "Values corresponding to types", [])
   .requiredOption("-a, --revert-address <address>", "Bitcoin revert address")
   .addOption(
     new Option("-o, --op-code <code>", "Operation code")
diff --git a/packages/commands/src/bitcoin/index.ts b/packages/commands/src/bitcoin/index.ts
index f151644a..5e6ed6e8 100644
--- a/packages/commands/src/bitcoin/index.ts
+++ b/packages/commands/src/bitcoin/index.ts
@@ -1,11 +1,14 @@
 import { Command } from "commander";
 
+import { callCommand } from "./call";
+import { depositCommand } from "./deposit";
 import { depositAndCallCommand } from "./depositAndCall";
 import { encodeCommand } from "./encode";
-
 export const bitcoinCommand = new Command("bitcoin")
   .description("Bitcoin-related commands")
   .helpCommand(false);
 
+bitcoinCommand.addCommand(callCommand);
 bitcoinCommand.addCommand(depositAndCallCommand);
+bitcoinCommand.addCommand(depositCommand);
 bitcoinCommand.addCommand(encodeCommand);
diff --git a/test/addressResolver.test.ts b/test/addressResolver.test.ts
index 68591c49..1f122a37 100644
--- a/test/addressResolver.test.ts
+++ b/test/addressResolver.test.ts
@@ -236,7 +236,7 @@ describe("Address Resolver Utils", () => {
         JSON.stringify({
           mainnetAddress: VALID_BTC_MAINNET_ADDRESS,
           mainnetWIF: "L1YYZngaKvau5egL8WSMnB9qoxoVGnkh1qVxe3SEsLWGpLCEMgpV",
-          privateKeyBytes:
+          privateKey:
             "1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd",
           testnetAddress: VALID_BTC_TESTNET_ADDRESS,
           testnetWIF: "cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs",
@@ -257,7 +257,7 @@ describe("Address Resolver Utils", () => {
         JSON.stringify({
           mainnetAddress: VALID_BTC_MAINNET_ADDRESS,
           mainnetWIF: "L1YYZngaKvau5egL8WSMnB9qoxoVGnkh1qVxe3SEsLWGpLCEMgpV",
-          privateKeyBytes:
+          privateKey:
             "1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd",
           testnetAddress: VALID_BTC_TESTNET_ADDRESS,
           testnetWIF: "cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs",
diff --git a/test/bitcoinEncode.test.ts b/test/bitcoinEncode.test.ts
index ee42d20a..2a512afc 100644
--- a/test/bitcoinEncode.test.ts
+++ b/test/bitcoinEncode.test.ts
@@ -48,6 +48,27 @@ describe("bitcoinEncode", () => {
     );
   });
 
+  it("should encode with empty payload when no types and values are provided", () => {
+    // Create an empty payload buffer
+    const emptyPayload = Buffer.from([]);
+
+    const result = bitcoinEncode(
+      receiverAddress,
+      emptyPayload,
+      btcRevertAddress
+    );
+
+    // The result should still be a valid encoded string
+    expect(result).toBeTruthy();
+    expect(typeof result).toBe("string");
+
+    // The encoded result should contain the receiver and revert address
+    // but have an empty payload section
+    expect(result.includes(receiverAddress.toLowerCase().substring(2))).toBe(
+      true
+    );
+  });
+
   it("should produce different outputs for different payloads", () => {
     const payload1 = Buffer.from("payload1");
     const payload2 = Buffer.from("payload2");
diff --git a/types/accounts.types.ts b/types/accounts.types.ts
index b3ca2fe1..c3989822 100644
--- a/types/accounts.types.ts
+++ b/types/accounts.types.ts
@@ -36,7 +36,7 @@ const bitcoinAccountDataSchema = z.object({
   mainnetAddress: z.string(),
   mainnetWIF: z.string(),
   name: z.string().optional(),
-  privateKeyBytes: z.string(),
+  privateKey: z.string(),
   testnetAddress: z.string(),
   testnetWIF: z.string(),
 });
diff --git a/types/bitcoin.constants.ts b/types/bitcoin.constants.ts
index 225d087a..499b65ec 100644
--- a/types/bitcoin.constants.ts
+++ b/types/bitcoin.constants.ts
@@ -42,6 +42,9 @@ export const BITCOIN_LIMITS = {
     P2TR: 330,
     P2WPKH: 294,
   },
+  // Minimum amount for commit output
+  ESTIMATED_REVEAL_FEE: 5000,
+  MIN_COMMIT_AMOUNT: 546, // Estimated fee for reveal transaction
 };
 
 /**
@@ -50,4 +53,5 @@ export const BITCOIN_LIMITS = {
 export const BITCOIN_FEES = {
   DEFAULT_COMMIT_FEE_SAT: 15000,
   DEFAULT_REVEAL_FEE_RATE: 10,
+  MIN_TOTAL_FEE: 20000, // Minimum total fee for both transactions
 };
diff --git a/types/bitcoin.types.ts b/types/bitcoin.types.ts
index c52084b4..3bf48e73 100644
--- a/types/bitcoin.types.ts
+++ b/types/bitcoin.types.ts
@@ -1,6 +1,12 @@
 import { z } from "zod";
 
-import { validAmountSchema } from "./shared.schema";
+import { DEFAULT_ACCOUNT_NAME } from "./shared.constants";
+import {
+  hexStringSchema,
+  typesAndDataExclusivityRefineRule,
+  typesAndValuesLengthRefineRule,
+  validAmountSchema,
+} from "./shared.schema";
 
 export interface BtcUtxo {
   status: {
@@ -46,21 +52,58 @@ export interface BtcTxById {
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: validAmountSchema,
     api: z.string().url(),
+    data: hexStringSchema.optional(),
     gateway: z.string(),
-    privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+    privateKey: z.string().optional(),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
-  .refine((data) => data.types.length === data.values.length, {
-    message: "The 'types' and 'values' arrays must have the same length",
-    path: ["values"],
+  .refine(typesAndValuesLengthRefineRule.rule, {
+    message: typesAndValuesLengthRefineRule.message,
+    path: typesAndValuesLengthRefineRule.path,
+  })
+  .refine(typesAndDataExclusivityRefineRule.rule, {
+    message: typesAndDataExclusivityRefineRule.message,
+    path: typesAndDataExclusivityRefineRule.path,
+  });
+
+export const depositOptionsSchema = z.object({
+  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
+    message: "Amount must be a valid positive number",
+  }),
+  api: z.string().url(),
+  data: hexStringSchema.optional(),
+  gateway: z.string(),
+  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+  privateKey: z.string().optional(),
+  receiver: z.string().optional(),
+  revertAddress: z.string().optional(),
+});
+
+export const callOptionsSchema = z
+  .object({
+    api: z.string().url(),
+    data: hexStringSchema.optional(),
+    gateway: z.string(),
+    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+    privateKey: z.string().optional(),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
+  })
+  .refine(typesAndValuesLengthRefineRule.rule, {
+    message: typesAndValuesLengthRefineRule.message,
+    path: typesAndValuesLengthRefineRule.path,
+  })
+  .refine(typesAndDataExclusivityRefineRule.rule, {
+    message: typesAndDataExclusivityRefineRule.message,
+    path: typesAndDataExclusivityRefineRule.path,
   });
diff --git a/types/shared.schema.ts b/types/shared.schema.ts
index 6b80fd76..d685c418 100644
--- a/types/shared.schema.ts
+++ b/types/shared.schema.ts
@@ -35,6 +35,10 @@ export const numericStringSchema = z
   .refine((val) => /^\d+$/.test(val), {
     message: "Must be a string containing only numbers",
   });
+export const hexStringSchema = z
+  .string()
+  .regex(/^(0x)?[0-9a-fA-F]*$/, { message: "data must be hex encoded" });
+
 export const evmPrivateKeySchema = z
   .string()
   .refine((val) => /^(0x)?[0-9a-fA-F]{64}$/.test(val), {
@@ -53,3 +57,33 @@ export const evmPrivateKeySchema = z
     },
     { message: "Invalid private key (must be in secp256k1 range)" }
   );
+
+export const typesAndValuesLengthRefineRule = {
+  message:
+    "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+  path: ["values"],
+  rule: (data: { types?: string[]; values?: string[] }) => {
+    // Only check length equality if both arrays exist
+    if (data.types && data.values) {
+      return data.types.length === data.values.length;
+    }
+    // If one exists and the other doesn't, that's invalid
+    if ((data.types && !data.values) || (!data.types && data.values)) {
+      return false;
+    }
+    // If both are undefined/not provided, that's valid
+    return true;
+  },
+};
+export const typesAndDataExclusivityRefineRule = {
+  message: "Provide either --data or --types/--values (not both)",
+  path: ["data"],
+  rule: (data: { data?: string; types?: string[]; values?: string[] }) => {
+    // Prevent providing both data and types/values
+    if (data.data && (data.types || data.values)) {
+      return false;
+    }
+
+    return true;
+  },
+};
diff --git a/utils/accounts.ts b/utils/accounts.ts
index e8966f27..0b6e0e63 100644
--- a/utils/accounts.ts
+++ b/utils/accounts.ts
@@ -86,22 +86,20 @@ const createBitcoinAccount = (): AccountData => {
   const keyPair = ECPair.makeRandom();
 
   // Store the raw private key bytes (hex encoded for storage)
-  const privateKeyBytes = keyPair.privateKey?.toString("hex") || "";
-  if (!privateKeyBytes) {
+  const privateKey = keyPair.privateKey?.toString("hex") || "";
+  if (!privateKey) {
     throw new Error("Failed to generate Bitcoin private key");
   }
 
   // Create testnet WIF
-  const testnetWIF = ECPair.fromPrivateKey(
-    Buffer.from(privateKeyBytes, "hex"),
-    { network: bitcoin.networks.testnet }
-  ).toWIF();
+  const testnetWIF = ECPair.fromPrivateKey(Buffer.from(privateKey, "hex"), {
+    network: bitcoin.networks.testnet,
+  }).toWIF();
 
   // Create mainnet WIF
-  const mainnetWIF = ECPair.fromPrivateKey(
-    Buffer.from(privateKeyBytes, "hex"),
-    { network: bitcoin.networks.bitcoin }
-  ).toWIF();
+  const mainnetWIF = ECPair.fromPrivateKey(Buffer.from(privateKey, "hex"), {
+    network: bitcoin.networks.bitcoin,
+  }).toWIF();
 
   // Generate a SegWit (P2WPKH) address for mainnet
   const { address: mainnetAddress } = bitcoin.payments.p2wpkh({
@@ -121,7 +119,7 @@ const createBitcoinAccount = (): AccountData => {
   return {
     mainnetAddress,
     mainnetWIF,
-    privateKeyBytes,
+    privateKey,
     testnetAddress,
     testnetWIF,
   };
diff --git a/utils/bitcoin.command.helpers.ts b/utils/bitcoin.command.helpers.ts
new file mode 100644
index 00000000..22de4dca
--- /dev/null
+++ b/utils/bitcoin.command.helpers.ts
@@ -0,0 +1,203 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory, { ECPairInterface } from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+
+import { BitcoinAccountData } from "../types/accounts.types";
+import { BITCOIN_FEES } from "../types/bitcoin.constants";
+import type { BtcUtxo } from "../types/bitcoin.types";
+import { DEFAULT_ACCOUNT_NAME } from "../types/shared.constants";
+import { getAccountData } from "./accounts";
+import {
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "./bitcoin.helpers";
+import { handleError } from "./handleError";
+
+export interface BitcoinKeyPair {
+  address: string;
+  key: ECPairInterface;
+}
+
+export interface TransactionInfo {
+  amount?: string;
+  commitFee: number;
+  encodedMessage?: string;
+  encodingFormat: string;
+  gateway: string;
+  network: string;
+  operation: string;
+  rawInscriptionData: string;
+  receiver?: string;
+  revealFee: number;
+  revertAddress?: string;
+  sender: string;
+  totalFee: number;
+}
+
+/**
+ * Sets up a Bitcoin key pair using either a provided private key or one stored in the account data
+ */
+export const setupBitcoinKeyPair = (
+  privateKey: string | undefined,
+  name: string
+): BitcoinKeyPair => {
+  const keyPrivateKey =
+    privateKey ||
+    getAccountData<BitcoinAccountData>("bitcoin", name)?.privateKey;
+
+  if (!keyPrivateKey) {
+    const errorMessage = handleError({
+      context: "Failed to retrieve private key",
+      error: new Error("Private key not found"),
+      shouldThrow: false,
+    });
+
+    throw new Error(errorMessage);
+  }
+
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(keyPrivateKey, "hex"), {
+    network: SIGNET,
+  });
+
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  return { address: address!, key };
+};
+
+/**
+ * Fetches unspent transaction outputs (UTXOs) for the given address
+ */
+export const fetchUtxos = async (
+  address: string,
+  api: string
+): Promise<BtcUtxo[]> => {
+  return (await axios.get<BtcUtxo[]>(`${api}/address/${address}/utxo`)).data;
+};
+
+/**
+ * Displays transaction details to the user and asks for confirmation before proceeding
+ */
+export const displayAndConfirmTransaction = async (info: TransactionInfo) => {
+  const notApplicable = "encoded in raw inscription data";
+
+  console.log(`
+Network: ${info.network}
+${info.amount ? `Amount: ${info.amount} BTC` : ""}
+Gateway: ${info.gateway}
+Sender: ${info.sender}
+Universal Contract: ${info.receiver || notApplicable}
+Revert Address: ${info.revertAddress || notApplicable}
+Operation: ${info.operation}
+${info.encodedMessage ? `Encoded Message: ${info.encodedMessage}` : ""}
+Encoding Format: ${info.encodingFormat}
+Raw Inscription Data: ${info.rawInscriptionData}
+Fees:
+  - Commit Fee: ${info.commitFee} sat
+  - Reveal Fee: ${info.revealFee} sat
+  - Total Fee: ${info.totalFee} sat (${(info.totalFee / 100000000).toFixed(
+    8
+  )} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+};
+
+/**
+ * Broadcasts a raw Bitcoin transaction to the network
+ */
+export const broadcastBtcTransaction = async (
+  txHex: string,
+  api: string
+): Promise<string> => {
+  const { data } = await axios.post<string>(`${api}/tx`, txHex, {
+    headers: { "Content-Type": "text/plain" },
+  });
+
+  return data;
+};
+
+/**
+ * Creates and broadcasts both commit and reveal transactions for Bitcoin inscriptions
+ */
+export const createAndBroadcastTransactions = async (
+  key: ECPairInterface,
+  utxos: BtcUtxo[],
+  address: string,
+  data: Buffer,
+  api: string,
+  amount: number,
+  gateway: string
+) => {
+  // Create and broadcast commit transaction
+  const commit = await makeCommitTransaction(
+    key,
+    utxos,
+    address,
+    data,
+    api,
+    amount
+  );
+
+  const commitTxid = await broadcastBtcTransaction(commit.txHex, api);
+  console.log("Commit TXID:", commitTxid);
+
+  // Create and broadcast reveal transaction
+  const revealHex = makeRevealTransaction(
+    commitTxid,
+    0,
+    amount,
+    gateway,
+    BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE,
+    {
+      controlBlock: commit.controlBlock,
+      internalKey: commit.internalKey,
+      leafScript: commit.leafScript,
+    },
+    key
+  );
+  const revealTxid = await broadcastBtcTransaction(revealHex, api);
+  console.log("Reveal TXID:", revealTxid);
+
+  return { commitTxid, revealTxid };
+};
+
+/**
+ * Adds common Bitcoin-related command options to a Commander command
+ */
+export const addCommonOptions = (command: Command) => {
+  return command
+    .option("--api <url>", "Bitcoin API", "https://mempool.space/signet/api")
+    .addOption(
+      new Option("--private-key <key>", "Bitcoin private key").conflicts([
+        "name",
+      ])
+    )
+    .addOption(
+      new Option("--name <name>", "Account name")
+        .default(DEFAULT_ACCOUNT_NAME)
+        .conflicts(["private-key"])
+    );
+};
+
+/**
+ * Parses a Bitcoin amount string and converts it to satoshis as a number
+ */
+export const parseAmount = (amount: string): number => {
+  const amountSatBig = ethers.parseUnits(amount, 8);
+  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
+    throw new Error("Amount exceeds JS safe-integer range");
+  }
+  return Number(amountSatBig);
+};
diff --git a/utils/bitcoin.helpers.ts b/utils/bitcoin.helpers.ts
index e781c983..120d796e 100644
--- a/utils/bitcoin.helpers.ts
+++ b/utils/bitcoin.helpers.ts
@@ -102,8 +102,10 @@ export const makeCommitTransaction = async (
   amountSat: number,
   feeSat = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT
 ) => {
-  const DUST_THRESHOLD_P2TR = BITCOIN_LIMITS.DUST_THRESHOLD.P2TR;
-  if (amountSat < DUST_THRESHOLD_P2TR) throw new Error("Amount below dust");
+  const effectiveAmount = Math.max(
+    amountSat,
+    BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE
+  );
 
   /* pick utxos */
   utxos.sort((a, b) => a.value - b.value);
@@ -112,20 +114,32 @@ export const makeCommitTransaction = async (
   for (const u of utxos) {
     inTotal += u.value;
     picks.push(u);
-    if (inTotal >= amountSat + feeSat) break;
+    if (inTotal >= effectiveAmount + feeSat) break;
   }
-  if (inTotal < amountSat + feeSat) throw new Error("Not enough funds");
-  const changeSat = inTotal - amountSat - feeSat;
+  if (inTotal < effectiveAmount + feeSat) throw new Error("Not enough funds");
+  const changeSat = inTotal - effectiveAmount - feeSat;
 
-  /* leaf script */
-  const leafScript = bitcoin.script.compile([
+  const scriptItems = [
     key.publicKey.slice(1, 33),
     bitcoin.opcodes.OP_CHECKSIG,
     bitcoin.opcodes.OP_FALSE,
     bitcoin.opcodes.OP_IF,
-    inscriptionData,
-    bitcoin.opcodes.OP_ENDIF,
-  ]);
+  ];
+
+  // Add inscription data in chunks if it exceeds 520 bytes (max script element size)
+  const MAX_SCRIPT_ELEMENT_SIZE = 520;
+  if (inscriptionData.length > MAX_SCRIPT_ELEMENT_SIZE) {
+    for (let i = 0; i < inscriptionData.length; i += MAX_SCRIPT_ELEMENT_SIZE) {
+      const end = Math.min(i + MAX_SCRIPT_ELEMENT_SIZE, inscriptionData.length);
+      scriptItems.push(inscriptionData.slice(i, end));
+    }
+  } else {
+    scriptItems.push(inscriptionData);
+  }
+
+  scriptItems.push(bitcoin.opcodes.OP_ENDIF);
+
+  const leafScript = bitcoin.script.compile(scriptItems);
 
   /* p2tr */
   const { output: commitScript, witness } = bitcoin.payments.p2tr({
@@ -137,7 +151,7 @@ export const makeCommitTransaction = async (
   if (!commitScript || !witness) throw new Error("taproot build failed");
 
   const psbt = new bitcoin.Psbt({ network: SIGNET });
-  psbt.addOutput({ script: commitScript, value: amountSat });
+  psbt.addOutput({ script: commitScript, value: effectiveAmount });
   if (changeSat > 0)
     psbt.addOutput({ address: changeAddress, value: changeSat });
   for (const u of picks) {
@@ -213,14 +227,38 @@ export const makeRevealTransaction = (
   const vsize = txOverhead + inputVbytes + outputVbytes;
   const feeSat = Math.ceil(vsize * feeRate);
 
-  const DUST_THRESHOLD_P2WPKH = BITCOIN_LIMITS.DUST_THRESHOLD.P2WPKH;
-  if (commitValue - feeSat < DUST_THRESHOLD_P2WPKH)
-    throw new Error("reveal would be dust");
+  // Ensure we have enough value for the output after fee
+  const outputValue = commitValue - feeSat;
+  if (outputValue < BITCOIN_LIMITS.DUST_THRESHOLD.P2WPKH) {
+    throw new Error(
+      `Insufficient value in commit output (${commitValue} sat) to cover reveal fee (${feeSat} sat) and maintain minimum output (${BITCOIN_LIMITS.DUST_THRESHOLD.P2WPKH} sat)`
+    );
+  }
 
-  psbt.addOutput({ address: to, value: commitValue - feeSat });
+  psbt.addOutput({ address: to, value: outputValue });
 
   psbt.signInput(0, key);
   psbt.finalizeAllInputs();
 
   return psbt.extractTransaction(true).toHex();
 };
+
+/**
+ * Calculates the total fees for a Bitcoin inscription transaction
+ * @param data - The inscription data buffer
+ * @returns Object containing commit fee, reveal fee, and total fee
+ */
+export const calculateFees = (data: Buffer) => {
+  const commitFee = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT;
+  const revealFee = Math.ceil(
+    (BITCOIN_TX.TX_OVERHEAD +
+      36 +
+      1 +
+      43 +
+      Math.ceil(data.length / 4) +
+      BITCOIN_TX.P2WPKH_OUTPUT_VBYTES) *
+      BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE
+  );
+  const totalFee = commitFee + revealFee;
+  return { commitFee, revealFee, totalFee };
+};


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/call.ts (1)</summary><blockquote>

`104-106`: **Same as deposit – ignore `confirm()` return value**  

A negative answer does not abort; see earlier suggestion for the quick fix.

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (1)</summary><blockquote>

`90-108`: **Ignored confirmation prompt again** – apply the same fix as in the other two commands.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>types/bitcoin.constants.ts (1)</summary><blockquote>

`45-47`: **Swap comments to match constant definitions**

The comments for `ESTIMATED_REVEAL_FEE` and `MIN_COMMIT_AMOUNT` appear to be reversed based on their values and names. The comment "Estimated fee for reveal transaction" should be next to `ESTIMATED_REVEAL_FEE`, not `MIN_COMMIT_AMOUNT`.


```diff
-  // Minimum amount for commit output
-  ESTIMATED_REVEAL_FEE: 5000,
-  MIN_COMMIT_AMOUNT: 546, // Estimated fee for reveal transaction
+  // Estimated fee for reveal transaction
+  ESTIMATED_REVEAL_FEE: 5000,
+  // Minimum amount for commit output
+  MIN_COMMIT_AMOUNT: 546,
```

</blockquote></details>
<details>
<summary>test/bitcoinEncode.test.ts (1)</summary><blockquote>

`51-70`: **Test for empty payload looks good but could be more specific**

The test correctly verifies that encoding with an empty payload produces a valid result containing the receiver address. This aligns with the updates to support empty types and values.


Consider enhancing the test with more specific assertions about the expected format of the encoded string, such as verifying the header bytes or checking that the payload section is indeed empty. This would provide more confidence in the encoding behavior.

```diff
 // The encoded result should contain the receiver and revert address
 // but have an empty payload section
 expect(result.includes(receiverAddress.toLowerCase().substring(2))).toBe(
   true
 );
+// Check that the result has the expected format with empty payload
+expect(result.startsWith("5a0010")).toBe(true); // Check header bytes
+expect(result.includes(btcRevertAddress)).toBe(true); // Check revert address
```

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/encode.ts (1)</summary><blockquote>

`25-28`: **Redundant null checks**

Since the Zod schema already provides default empty arrays for `types` and `values`, the null checks here are redundant.


```diff
-  // Ensure types and values are arrays even if not provided
-  const types = options.types || [];
-  const values = options.values || [];
+  // Destructure with defaults already applied by Zod
+  const { types, values } = options;
```

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/deposit.ts (1)</summary><blockquote>

`141-149`: **Security heads-up: accepting private keys via CLI flag**  

Suppressing secrets in shell history is hard. Consider reading the WIF / raw key from `stdin` or an env var instead of `--private-key`, or at least document the risk.

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/call.ts (2)</summary><blockquote>

`56-82`: **Unchecked `types`/`values` length correspondence**  

You rely on `callOptionsSchema` for validation, but inside the code you still assume both arrays are present and aligned.  
If the schema ever loosens, an out-of-bounds encode will throw. Safer to assert:

```ts
if (options.types?.length !== options.values?.length) {
  throw new Error("types and values length mismatch");
}
```

---

`106-118`: **Double-counting MIN_COMMIT_AMOUNT is avoided but opaque**  

You pre-compute `effectiveAmount`, then pass it to `makeCommitTransaction`, which itself applies `Math.max(amountSat, MIN_COMMIT_AMOUNT + …)`.  
The current numbers still match, yet the coupling is brittle. Consider exposing a `requiredCommitAmount()` helper and/or passing the user-requested logical amount to make the intent explicit.

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (1)</summary><blockquote>

`160-178`: **Commander option set relaxed, but help text still implies required ABI params**  

`--types`, `--values`, `--receiver`, `--revert-address` are now optional, yet the descriptions do not mention that raw `--data` may be used instead. Update the `.description` / option docs to prevent user confusion.

</blockquote></details>
<details>
<summary>types/bitcoin.types.ts (1)</summary><blockquote>

`93-122`: **Duplicate validation logic – consider extracting a helper.**

`callOptionsSchema` re-implements the same “types/values length equality & exclusivity” rule that appears above.  To avoid divergence, extract a small helper:

```ts
const abiPairRule = (schema: z.ZodTypeAny) =>
  schema.refine(/* …shared predicate… */ , { message: /* … */ });
```

and reuse it across all three schemas.

</blockquote></details>
<details>
<summary>utils/bitcoin.helpers.ts (1)</summary><blockquote>

`252-265`: **Fee estimate may under-count large inscriptions.**

`revealFee` is derived from `data.length/4`, but the real witness size grows with:

* `leafScript` (includes opcodes, pubkey, and potentially many 520-byte chunks)  
* 33-byte control block  
* 64/65-byte signature  

Using only `data.length` under-prices fees for very large or highly-chunked inscriptions and risks `mempool` rejection.

Consider:

```diff
-  const revealFee = Math.ceil(/* … */ + Math.ceil(data.length / 4) + …)
+  const scriptOverhead = 35;          // pubkey + opcodes
+  const chunkedData = Math.ceil(data.length / 4);
+  const controlBlock = 33 / 4;
+  const sig = 65 / 4;
+  const revealFee = Math.ceil(
+    (BITCOIN_TX.TX_OVERHEAD + 36 + 1 + 43 + chunkedData + controlBlock + sig +
+      BITCOIN_TX.P2WPKH_OUTPUT_VBYTES) *
+    BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE
+  );
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 62976ed70c7892262848e05f5c0a992c5f750c2b and ccbc8716458edf061e91d7bb0825d7ba53c8d09f.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `packages/commands/src/bitcoin/call.ts` (1 hunks)
* `packages/commands/src/bitcoin/deposit.ts` (1 hunks)
* `packages/commands/src/bitcoin/depositAndCall.ts` (5 hunks)
* `packages/commands/src/bitcoin/encode.ts` (3 hunks)
* `packages/commands/src/bitcoin/index.ts` (1 hunks)
* `test/bitcoinEncode.test.ts` (1 hunks)
* `types/bitcoin.constants.ts` (2 hunks)
* `types/bitcoin.types.ts` (1 hunks)
* `utils/bitcoin.helpers.ts` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (5)</summary>

<details>
<summary>packages/commands/src/bitcoin/index.ts (3)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (1)</summary>

* `depositAndCallCommand` (154-188)

</details>
<details>
<summary>packages/commands/src/bitcoin/deposit.ts (1)</summary>

* `depositCommand` (136-160)

</details>
<details>
<summary>packages/commands/src/bitcoin/call.ts (1)</summary>

* `callCommand` (153-180)

</details>

</blockquote></details>
<details>
<summary>test/bitcoinEncode.test.ts (1)</summary><blockquote>

<details>
<summary>utils/bitcoinEncode.ts (1)</summary>

* `bitcoinEncode` (57-71)

</details>

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/deposit.ts (5)</summary><blockquote>

<details>
<summary>types/bitcoin.types.ts (2)</summary>

* `depositOptionsSchema` (81-91)
* `BtcUtxo` (3-13)

</details>
<details>
<summary>utils/bitcoin.helpers.ts (4)</summary>

* `SIGNET` (17-27)
* `calculateFees` (252-265)
* `makeCommitTransaction` (96-178)
* `makeRevealTransaction` (193-245)

</details>
<details>
<summary>utils/bitcoinEncode.ts (1)</summary>

* `bitcoinEncode` (57-71)

</details>
<details>
<summary>types/bitcoin.constants.ts (1)</summary>

* `BITCOIN_FEES` (53-57)

</details>
<details>
<summary>utils/validateAndParseSchema.ts (1)</summary>

* `validateAndParseSchema` (28-59)

</details>

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (2)</summary><blockquote>

<details>
<summary>utils/bitcoinEncode.ts (2)</summary>

* `bitcoinEncode` (57-71)
* `trimOx` (183-185)

</details>
<details>
<summary>utils/bitcoin.helpers.ts (1)</summary>

* `calculateFees` (252-265)

</details>

</blockquote></details>
<details>
<summary>utils/bitcoin.helpers.ts (1)</summary><blockquote>

<details>
<summary>types/bitcoin.constants.ts (3)</summary>

* `BITCOIN_LIMITS` (40-48)
* `BITCOIN_FEES` (53-57)
* `BITCOIN_TX` (28-35)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/index.ts (1)</summary>

`3-4`: **Command registration looks good**

The new `depositCommand` and `callCommand` are correctly imported and registered in the Bitcoin command group. This aligns well with the PR objectives of providing dedicated deposit and call commands.




Also applies to: 12-13

</details>
<details>
<summary>packages/commands/src/bitcoin/encode.ts (3)</summary>

`13-22`: **Good use of Zod schema for validation**

The Zod schema implementation provides proper validation with appropriate defaults for optional fields. This is a good improvement over manual validation.

---

`33-41`: **Good handling of empty payload case**

The added logic to handle empty types and values by creating an empty payload buffer is a good enhancement. This supports the PR objective of making the encode command work with empty parameters for inscription-based regular deposits.

---

`59-60`: **CLI options correctly updated**

The CLI options for types and values are now optional with default empty arrays, matching the schema changes. This allows for more flexible command usage.

</details>
<details>
<summary>types/bitcoin.types.ts (1)</summary>

`81-91`: **`receiver` should probably be required for a plain deposit.**

A deposit without a destination address is unlikely to be valid.  Making `receiver` optional at the schema level shifts the validation burden downstream and may surface as run-time errors (e.g. when constructing the reveal tx).

Please confirm that a `receiver` can indeed be omitted; if not, change it to:

```diff
-receiver: z.string().optional(),
+receiver: z.string().min(1, "Receiver address is required"),
```

</details>
<details>
<summary>utils/bitcoin.helpers.ts (2)</summary>

`105-109`: **User-specified amount can be silently inflated.**

`effectiveAmount = max(userAmount, MIN_COMMIT_AMOUNT + ESTIMATED_REVEAL_FEE)`  
If a user asks to deposit 1 000 sat but the minimum + fee is 5 546 sat, they will involuntarily lock >5× the requested funds, and the gateway will ultimately receive that higher amount.

Unless this behaviour is clearly documented, consider:

* Rejecting under-minimum amounts up-front with a validation error, or  
* Showing an explicit confirmation prompt displaying the adjusted amount.

---

`231-238`: **Good dust-threshold guard 👍**

The explicit check prevents creating unspendable outputs – nice touch.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:52:48Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>packages/commands/src/accounts/show.ts (1)</summary><blockquote>

`58-66`: _⚠️ Potential issue_

**Rename property to `privateKey` for consistency with the renamed schema field**

The `bitcoinAccountDataSchema` and all other updated helpers now expose the field as `privateKey`.  
Returning it here under the old key (`privateKeyBytes`) breaks JSON output consistency and makes `console.table` omit the value expected by users/tools consuming `"privateKey"`.

```diff
-    privateKeyBytes: keyData.privateKey,
+    privateKey: keyData.privateKey,
```

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>types/bitcoin.types.ts (2)</summary><blockquote>

`94-124`: _🛠️ Refactor suggestion_

**Apply the same mutual-exclusivity rule to callOptionsSchema**

The same issue with the refinement logic exists in this schema - it should enforce mutual exclusivity between using `data` versus using `types`/`values`.


```diff
  .refine(
    (data) => {
+      // Enforce mutual exclusivity between data and types/values
+      if (data.data && (data.types || data.values)) {
+        return false;
+      }
      // Only check length equality if both arrays exist
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      // If one exists and the other doesn't, that's invalid
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      // If both are undefined/not provided, that's valid
      return true;
    },
    {
      message:
-        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+        "Provide either --data OR matching --types/--values (not both); when using types and values, both must exist and have the same length",
      path: ["values"],
    }
  );
```

---

`54-80`: _🛠️ Refactor suggestion_

**Enforce mutual-exclusivity between `--data` and `--types/--values`**

The refinement logic validates the presence and length matching of `types` and `values` arrays, but it doesn't enforce mutual exclusivity between using `data` versus using `types`/`values`. According to the PR objectives, `--data` should be used *instead of* specifying types and values separately.


```diff
  .refine(
    (data) => {
+      // Enforce mutual exclusivity between data and types/values
+      if (data.data && (data.types || data.values)) {
+        return false;
+      }
      // Only check length equality if both arrays exist
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      // If one exists and the other doesn't, that's invalid
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      // If both are undefined/not provided, that's valid
      return true;
    },
    {
      message:
-        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+        "Provide either --data OR matching --types/--values (not both); when using types and values, both must exist and have the same length",
      path: ["values"],
    }
  );
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/call.ts (1)</summary><blockquote>

`1-32`: **Import list violates project ESLint “simple-import-sort” rule**

CI is failing with “Run autofix to sort these imports”.  
Running `pnpm lint --fix` (or the equivalent) will reorder the block automatically.

_No functional impact, but it's blocking the pipeline._

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-32: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>
<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
Run autofix to sort these imports!

</details>
<details>
<summary>🪛 GitHub Actions: Lint</summary>

[error] 1-1: ESLint: Run autofix to sort these imports! (simple-import-sort/imports)

</details>

</details>

</blockquote></details>
<details>
<summary>types/bitcoin.types.ts (3)</summary><blockquote>

`62-80`: **Extract common validation logic to follow DRY principles**

The refinement logic is duplicated between `depositAndCallOptionsSchema` and `callOptionsSchema`. Consider extracting it to a reusable function to avoid duplication.


```diff
+ // Define a reusable refinement function
+ const validateTypeValuesAndData = (data: any) => {
+   // Enforce mutual exclusivity between data and types/values
+   if (data.data && (data.types || data.values)) {
+     return false;
+   }
+   // Only check length equality if both arrays exist
+   if (data.types && data.values) {
+     return data.types.length === data.values.length;
+   }
+   // If one exists and the other doesn't, that's invalid
+   if ((data.types && !data.values) || (!data.types && data.values)) {
+     return false;
+   }
+   // If both are undefined/not provided, that's valid
+   return true;
+ };
+
+ const typeValuesRefinementMessage = 
+   "Provide either --data OR matching --types/--values (not both); when using types and values, both must exist and have the same length";

  // Then use it in both schemas
  export const depositAndCallOptionsSchema = z
    .object({
      // ... object definition ...
    })
-   .refine(
-     (data) => {
-       // Only check length equality if both arrays exist
-       if (data.types && data.values) {
-         return data.types.length === data.values.length;
-       }
-       // If one exists and the other doesn't, that's invalid
-       if ((data.types && !data.values) || (!data.types && data.values)) {
-         return false;
-       }
-       // If both are undefined/not provided, that's valid
-       return true;
-     },
-     {
-       message:
-         "If provided, the 'types' and 'values' arrays must both exist and have the same length",
-       path: ["values"],
-     }
-   );
+   .refine(validateTypeValuesAndData, {
+     message: typeValuesRefinementMessage,
+     path: ["values"],
+   });

  // Same for callOptionsSchema
  export const callOptionsSchema = z
    .object({
      // ... object definition ...
    })
-   .refine(
-     (data) => {
-       // Only check length equality if both arrays exist
-       if (data.types && data.values) {
-         return data.types.length === data.values.length;
-       }
-       // If one exists and the other doesn't, that's invalid
-       if ((data.types && !data.values) || (!data.types && data.values)) {
-         return false;
-       }
-       // If both are undefined/not provided, that's valid
-       return true;
-     },
-     {
-       message:
-         "If provided, the 'types' and 'values' arrays must both exist and have the same length",
-       path: ["values"],
-     }
-   );
+   .refine(validateTypeValuesAndData, {
+     message: typeValuesRefinementMessage,
+     path: ["values"],
+   });
```


Also applies to: 106-124

---

`82-86`: **Standardize amount validation across schemas**

The `depositOptionsSchema` implements its own validation logic for the amount field, while `depositAndCallOptionsSchema` uses the imported `validAmountSchema`. Consider standardizing on one approach for consistency.


```diff
export const depositOptionsSchema = z.object({
-  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
-    message: "Amount must be a valid positive number",
-  }),
+  amount: validAmountSchema,
  api: z.string().url(),
  data: z.string().optional(),
  // ...
});
```

---

`1-4`: **Fix import sorting**

The static analysis tool has flagged an issue with import sorting.


```diff
import { z } from "zod";
-
import { validAmountSchema } from "./shared.schema";
import { DEFAULT_ACCOUNT_NAME } from "./shared.constants";
+
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-4: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>
<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
Run autofix to sort these imports!

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between eb041ef6c0c213a919247d58f6433c2ed65fee2a and caad9bb0c760a87428f9df192e21600eb45b27fe.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/commands/src/accounts/show.ts` (1 hunks)
* `packages/commands/src/bitcoin/call.ts` (1 hunks)
* `types/accounts.types.ts` (1 hunks)
* `types/bitcoin.types.ts` (2 hunks)
* `utils/accounts.ts` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* utils/accounts.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>packages/commands/src/bitcoin/call.ts (9)</summary><blockquote>

<details>
<summary>types/bitcoin.types.ts (2)</summary>

* `callOptionsSchema` (94-124)
* `BtcUtxo` (6-16)

</details>
<details>
<summary>utils/accounts.ts (1)</summary>

* `getAccountData` (45-63)

</details>
<details>
<summary>types/accounts.types.ts (1)</summary>

* `EVMAccountData` (44-44)

</details>
<details>
<summary>utils/handleError.ts (1)</summary>

* `handleError` (31-45)

</details>
<details>
<summary>utils/bitcoin.helpers.ts (4)</summary>

* `SIGNET` (17-27)
* `calculateFees` (252-265)
* `makeCommitTransaction` (96-178)
* `makeRevealTransaction` (193-245)

</details>
<details>
<summary>utils/bitcoinEncode.ts (2)</summary>

* `bitcoinEncode` (57-71)
* `trimOx` (183-185)

</details>
<details>
<summary>types/bitcoin.constants.ts (2)</summary>

* `BITCOIN_LIMITS` (40-48)
* `BITCOIN_FEES` (53-57)

</details>
<details>
<summary>types/shared.constants.ts (1)</summary>

* `DEFAULT_ACCOUNT_NAME` (1-1)

</details>
<details>
<summary>utils/validateAndParseSchema.ts (1)</summary>

* `validateAndParseSchema` (28-59)

</details>

</blockquote></details>
<details>
<summary>types/bitcoin.types.ts (1)</summary><blockquote>

<details>
<summary>types/shared.constants.ts (1)</summary>

* `DEFAULT_ACCOUNT_NAME` (1-1)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>packages/commands/src/bitcoin/call.ts</summary>

[error] 1-32: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>
<details>
<summary>types/bitcoin.types.ts</summary>

[error] 1-4: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>

</details>
<details>
<summary>🪛 GitHub Check: build</summary>

<details>
<summary>packages/commands/src/bitcoin/call.ts</summary>

[failure] 1-1: 
Run autofix to sort these imports!

</details>

</details>
<details>
<summary>🪛 GitHub Actions: Lint</summary>

<details>
<summary>packages/commands/src/bitcoin/call.ts</summary>

[error] 1-1: ESLint: Run autofix to sort these imports! (simple-import-sort/imports)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>types/accounts.types.ts (1)</summary>

`35-42`: **Schema update looks good**

The rename from `privateKeyBytes` to `privateKey` propagates the new naming convention cleanly.  
No additional changes required.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T13:20:35Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/deposit.ts (3)</summary><blockquote>

`66-82`: **CLI option validation can allow ill-formed hex in `--data`**

The code currently accepts any string in the `data` option without validating if it's a valid hex string. This could lead to runtime errors when `Buffer.from(options.data, "hex")` is called.

---

`84-88`: **Fix BigInt ⇢ Number comparison – current code will not compile under `strict` TS settings**

The code compares a BigInt value returned from `ethers.parseUnits()` with `Number.MAX_SAFE_INTEGER` (a number primitive), which is not type-safe.

---

`108-108`: **`confirm()` result is ignored – user "No" still broadcasts both TXs**

The function ignores the boolean returned by `confirm()`, so transactions will be broadcast even if the user chooses not to proceed.

</blockquote></details>
<details>
<summary>types/bitcoin.types.ts (1)</summary><blockquote>

`62-80`: **Enforce mutual-exclusivity between `data` and `types/values`**

The refinement logic should be updated to prevent users from providing both raw `data` and ABI-based `types`/`values` parameters, as they are meant to be mutually exclusive.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/deposit.ts (1)</summary><blockquote>

`1-28`: **Sort imports to meet project standards.**

The imports should be sorted according to the project's standards as indicated by the static analysis tool.

```diff
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import { EVMAccountData } from "../../../../types/accounts.types";
+import { getAccountData } from "../../../../utils/accounts";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { handleError } from "../../../../utils/handleError";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
Run autofix to sort these imports!

</details>
<details>
<summary>🪛 ESLint</summary>

[error] 1-27: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>

</details>

</blockquote></details>
<details>
<summary>types/bitcoin.types.ts (1)</summary><blockquote>

`1-4`: **Sort imports to meet project standards.**

The imports should be sorted according to the project's standards as indicated by the static analysis tool.

```diff
import { z } from "zod";

-import { validAmountSchema } from "./shared.schema";
-import { DEFAULT_ACCOUNT_NAME } from "./shared.constants";
+import { DEFAULT_ACCOUNT_NAME } from "./shared.constants";
+import { validAmountSchema } from "./shared.schema";
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
Run autofix to sort these imports!                                              

</details>
<details>
<summary>🪛 ESLint</summary>

[error] 1-4: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between caad9bb0c760a87428f9df192e21600eb45b27fe and 356508c15fa50f1e0b4c87e4a1a46a5e97f1b5d5.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/commands/src/bitcoin/deposit.ts` (1 hunks)
* `types/bitcoin.types.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>types/bitcoin.types.ts (1)</summary><blockquote>

<details>
<summary>types/shared.constants.ts (1)</summary>

* `DEFAULT_ACCOUNT_NAME` (1-1)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: build</summary>

<details>
<summary>packages/commands/src/bitcoin/deposit.ts</summary>

[failure] 1-1: 
Run autofix to sort these imports!

</details>
<details>
<summary>types/bitcoin.types.ts</summary>

[failure] 89-89: 
Expected object keys to be in ascending order. 'gateway' should be before 'name'

</details>

</details>
<details>
<summary>🪛 ESLint</summary>

<details>
<summary>packages/commands/src/bitcoin/deposit.ts</summary>

[error] 1-27: Run autofix to sort these imports!

(simple-import-sort/imports)

</details>
<details>
<summary>types/bitcoin.types.ts</summary>

[error] 1-4: Run autofix to sort these imports!

(simple-import-sort/imports)

---

[error] 89-89: Expected object keys to be in ascending order. 'gateway' should be before 'name'.

(sort-keys-fix/sort-keys-fix)

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T13:24:57Z
user: login: fadeev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-11T13:34:27Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T13:35:02Z
user: login: fadeev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-11T13:36:29Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T13:38:14Z
user: login: hernan-clich
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T20:38:48Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T20:39:43Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/call.ts (1)</summary><blockquote>

`70-84`: _⚠️ Potential issue_

**User confirmation is ignored — the command proceeds even if the answer is "No"**

The result from `displayAndConfirmTransaction` is not checked, so transactions are sent regardless of user intent.



Apply this fix to respect user's confirmation choice:

```diff
-  await displayAndConfirmTransaction({
+  const userConfirmed = await displayAndConfirmTransaction({
     commitFee,
     encodedMessage: payload,
     encodingFormat: "ABI",
     gateway: options.gateway,
     network: "Signet",
     operation: "Call",
     rawInscriptionData: data.toString("hex"),
     receiver: options.receiver,
     revealFee,
     revertAddress: options.revertAddress,
     sender: address,
     totalFee,
   });
+  
+  if (!userConfirmed) {
+    console.log("Operation cancelled by user");
+    return;
+  }
```

This change requires updating the `displayAndConfirmTransaction` function in `utils/bitcoin.command.helpers.ts` to return the confirmation result:

```diff
-export const displayAndConfirmTransaction = async (info: TransactionInfo) => {
+export const displayAndConfirmTransaction = async (info: TransactionInfo): Promise<boolean> => {
   // Display logic...
-  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+  return await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
 };
```

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/deposit.ts (1)</summary><blockquote>

`55-69`: _⚠️ Potential issue_

**User confirmation is ignored — the command proceeds even if the answer is "No"**

The result from `displayAndConfirmTransaction` is not checked, so transactions are sent regardless of user intent.



Apply this fix to respect user's confirmation choice:

```diff
-  await displayAndConfirmTransaction({
+  const userConfirmed = await displayAndConfirmTransaction({
     amount: options.amount,
     commitFee,
     encodingFormat: "ABI",
     gateway: options.gateway,
     network: "Signet",
     operation: "Deposit",
     rawInscriptionData: data.toString("hex"),
     receiver: options.receiver,
     revealFee,
     revertAddress,
     sender: address,
     totalFee,
   });
+  
+  if (!userConfirmed) {
+    console.log("Operation cancelled by user");
+    return;
+  }
```

</blockquote></details>
<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (1)</summary><blockquote>

`72-86`: _⚠️ Potential issue_

**User confirmation is ignored — the command proceeds even if the answer is "No"**

The result from `displayAndConfirmTransaction` is not checked, so transactions are sent regardless of user intent.



Apply this fix to respect user's confirmation choice:

```diff
-  await displayAndConfirmTransaction({
+  const userConfirmed = await displayAndConfirmTransaction({
     amount: options.amount,
     commitFee,
     encodedMessage: payload,
     encodingFormat: "ABI",
     gateway: options.gateway,
     network: "Signet",
     operation: "DepositAndCall",
     rawInscriptionData: data.toString("hex"),
     receiver: options.receiver,
     revealFee,
     revertAddress: options.revertAddress,
     sender: address,
     totalFee,
   });
+  
+  if (!userConfirmed) {
+    console.log("Operation cancelled by user");
+    return;
+  }
```

</blockquote></details>
<details>
<summary>utils/bitcoin.command.helpers.ts (1)</summary><blockquote>

`131-173`: **Transaction creation and broadcasting lacks confirmation verification.**

The `createAndBroadcastTransactions` function only broadcasts the transactions and returns their transaction IDs, but doesn't include any mechanism to verify whether the transactions were successfully confirmed. Consider adding an optional parameter to wait for and verify transaction confirmations.



This is similar to a previous review comment by hernan-clich about the lack of confirmation verification. If this is by design, that's fine, but it's worth considering adding optional confirmation verification for better user experience.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/commands/src/bitcoin/call.ts (1)</summary><blockquote>

`38-64`: **Data encoding logic is well-implemented but has edge cases.**

The code handles both ABI-encoded data and raw hex data, with proper error handling when neither is provided.

Consider splitting the data encoding logic into a separate helper function to improve readability and testability, particularly since similar logic appears in other commands.

```diff
-  let data;
-  let payload;
-  if (
-    options.types &&
-    options.values &&
-    options.receiver &&
-    options.revertAddress
-  ) {
-    payload = new ethers.AbiCoder().encode(options.types, options.values);
-    data = Buffer.from(
-      bitcoinEncode(
-        options.receiver,
-        Buffer.from(trimOx(payload), "hex"),
-        options.revertAddress,
-        OpCode.Call,
-        EncodingFormat.EncodingFmtABI
-      ),
-      "hex"
-    );
-  } else if (options.data) {
-    data = Buffer.from(options.data, "hex");
-  } else {
-    throw new Error(
-      "Provide either data or types, values, receiver, and revert address"
-    );
-  }
+  const { data, payload } = prepareCallData(options);
```

Then implement the helper in a shared location:

```typescript
export const prepareCallData = (options: CallOptions) => {
  let data;
  let payload;
  
  if (
    options.types &&
    options.values &&
    options.receiver &&
    options.revertAddress
  ) {
    payload = new ethers.AbiCoder().encode(options.types, options.values);
    data = Buffer.from(
      bitcoinEncode(
        options.receiver,
        Buffer.from(trimOx(payload), "hex"),
        options.revertAddress,
        OpCode.Call,
        EncodingFormat.EncodingFmtABI
      ),
      "hex"
    );
  } else if (options.data) {
    data = Buffer.from(options.data, "hex");
  } else {
    throw new Error(
      "Provide either data or types, values, receiver, and revert address"
    );
  }
  
  return { data, payload };
};
```

</blockquote></details>
<details>
<summary>utils/bitcoin.command.helpers.ts (2)</summary><blockquote>

`80-88`: **Consider adding error handling for UTXO fetching.**

The `fetchUtxos` function relies on the axios request succeeding, but doesn't include any specific error handling for network failures or API errors.

```diff
 export const fetchUtxos = async (
   address: string,
   api: string
 ): Promise<BtcUtxo[]> => {
-  return (await axios.get<BtcUtxo[]>(`${api}/address/${address}/utxo`)).data;
+  try {
+    const response = await axios.get<BtcUtxo[]>(`${api}/address/${address}/utxo`);
+    return response.data;
+  } catch (error) {
+    throw handleError({
+      context: `Failed to fetch UTXOs for address ${address}`,
+      error,
+      shouldThrow: true,
+    });
+  }
 };
```

---

`117-129`: **Add error handling for transaction broadcasting.**

Similar to the `fetchUtxos` function, `broadcastBtcTransaction` doesn't include specific error handling for network failures or API errors, which could lead to confusing errors if the broadcast fails.

```diff
 export const broadcastBtcTransaction = async (
   txHex: string,
   api: string
 ): Promise<string> => {
-  const { data } = await axios.post<string>(`${api}/tx`, txHex, {
-    headers: { "Content-Type": "text/plain" },
-  });
-
-  return data;
+  try {
+    const { data } = await axios.post<string>(`${api}/tx`, txHex, {
+      headers: { "Content-Type": "text/plain" },
+    });
+    return data;
+  } catch (error) {
+    throw handleError({
+      context: "Failed to broadcast Bitcoin transaction",
+      error,
+      shouldThrow: true,
+    });
+  }
 };
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 323bb3d88b810b83d4e128a66f9af1d64bb30ed6 and 27c2b2b2546788829b5635eb2175e177e56700d1.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `packages/commands/src/bitcoin/call.ts` (1 hunks)
* `packages/commands/src/bitcoin/deposit.ts` (1 hunks)
* `packages/commands/src/bitcoin/depositAndCall.ts` (4 hunks)
* `packages/commands/src/bitcoin/encode.ts` (2 hunks)
* `packages/commands/src/bitcoin/index.ts` (1 hunks)
* `types/bitcoin.types.ts` (2 hunks)
* `types/shared.schema.ts` (2 hunks)
* `utils/bitcoin.command.helpers.ts` (1 hunks)
* `utils/bitcoin.helpers.ts` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* packages/commands/src/bitcoin/index.ts
* packages/commands/src/bitcoin/encode.ts
* utils/bitcoin.helpers.ts
* types/bitcoin.types.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (4)</summary><blockquote>

<details>
<summary>utils/bitcoin.command.helpers.ts (5)</summary>

* `setupBitcoinKeyPair` (45-78)
* `fetchUtxos` (83-88)
* `displayAndConfirmTransaction` (93-115)
* `createAndBroadcastTransactions` (134-173)
* `addCommonOptions` (178-191)

</details>
<details>
<summary>utils/bitcoinEncode.ts (2)</summary>

* `bitcoinEncode` (57-71)
* `trimOx` (183-185)

</details>
<details>
<summary>types/bitcoin.constants.ts (1)</summary>

* `BITCOIN_LIMITS` (40-48)

</details>
<details>
<summary>utils/bitcoin.helpers.ts (1)</summary>

* `calculateFees` (260-273)

</details>

</blockquote></details>
<details>
<summary>utils/bitcoin.command.helpers.ts (6)</summary><blockquote>

<details>
<summary>utils/accounts.ts (1)</summary>

* `getAccountData` (45-63)

</details>
<details>
<summary>utils/handleError.ts (1)</summary>

* `handleError` (31-45)

</details>
<details>
<summary>utils/bitcoin.helpers.ts (3)</summary>

* `SIGNET` (17-27)
* `makeCommitTransaction` (96-186)
* `makeRevealTransaction` (201-253)

</details>
<details>
<summary>types/bitcoin.types.ts (1)</summary>

* `BtcUtxo` (11-21)

</details>
<details>
<summary>types/bitcoin.constants.ts (1)</summary>

* `BITCOIN_FEES` (53-57)

</details>
<details>
<summary>types/shared.constants.ts (1)</summary>

* `DEFAULT_ACCOUNT_NAME` (1-1)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (26)</summary><blockquote>

<details>
<summary>types/shared.schema.ts (3)</summary>

`38-40`: **Excellent addition of a reusable hex string validator.**

The `hexStringSchema` is a good addition for validating hex-encoded data. It supports both 0x-prefixed and non-prefixed hex strings.

---

`61-77`: **Well-structured validation to ensure types and values consistency.**

The refinement rule properly validates that both `types` and `values` arrays exist and have the same length when either is provided. This prevents inconsistent inputs that could lead to runtime errors.

---

`78-89`: **Good mutual exclusivity check between data and types/values.**

This refinement rule correctly enforces that users can only provide either raw encoded data OR the individual ABI types and values, but not both. This avoids ambiguity when processing inputs.

</details>
<details>
<summary>packages/commands/src/bitcoin/call.ts (5)</summary>

`1-24`: **Good implementation of type safety and imports.**

The code correctly imports necessary dependencies and defines a strongly-typed `CallOptions` type using Zod schema inference.

---

`25-37`: **Well-structured setup for Bitcoin transactions.**

Good use of helper functions for key pair setup and UTXO fetching, which improves code organization and maintainability.

---

`65-69`: **Good use of predetermined constants for transaction amounts and fees.**

The code correctly uses predefined constants for the minimum commit amount and calculates fees based on data size.

---

`85-94`: **Good use of the helper function for transaction creation and broadcasting.**

Using the `createAndBroadcastTransactions` helper function improves code maintainability and consistency across commands.

---

`96-128`: **Well-structured command definition with clear options and constraints.**

The command is well-defined with appropriate options, descriptions, and conflict rules. The validation using the schema ensures type safety and consistency.

</details>
<details>
<summary>packages/commands/src/bitcoin/deposit.ts (6)</summary>

`1-22`: **Good implementation of type safety and imports.**

The code correctly imports necessary dependencies and defines a strongly-typed `DepositOptions` type using Zod schema inference.

---

`23-29`: **Well-structured setup for Bitcoin transactions.**

Good use of helper functions for key pair setup and UTXO fetching, which improves code organization and maintainability.

---

`30-49`: **Deposit data logic handles both direct and encoded options appropriately.**

The code correctly handles setting up deposit data with either receiver/revert address or raw data, with proper error handling.


I appreciate how the code defaults to the sender's address as the revert address if not provided explicitly, which is a good UX decision.

---

`50-54`: **Good use of predetermined constants for transaction amounts and fees.**

The code correctly uses predefined constants for the minimum commit amount and calculates fees based on data size.

---

`70-79`: **Good use of the helper function for transaction creation and broadcasting.**

Using the `createAndBroadcastTransactions` helper function improves code maintainability and consistency across commands.

---

`81-110`: **Well-structured command definition with clear options and constraints.**

The command is well-defined with appropriate options, descriptions, and conflict rules. The validation using the schema ensures type safety and consistency.

</details>
<details>
<summary>packages/commands/src/bitcoin/depositAndCall.ts (6)</summary>

`32-36`: **Good refactoring to use helper functions for setup.**

The refactoring to use `setupBitcoinKeyPair` and `fetchUtxos` helper functions improves code maintainability and consistency across commands.

---

`38-64`: **Enhanced flexibility with support for raw hex data.**

The changes now support both ABI-encoded data and raw hex data, with proper error handling.

---

`66-69`: **Fixed transaction amount calculation.**

Using constants for minimum commit amount and estimated reveal fee ensures consistency in fee handling.

---

`88-96`: **Good use of the helper function for transaction creation and broadcasting.**

Refactoring to use the `createAndBroadcastTransactions` helper function improves code maintainability and consistency across commands.

---

`108-125`: **Well-structured command options with improved flexibility.**

The command options now support both ABI parameters and raw data, with proper conflict rules to prevent ambiguity.

---

`137-137`: **Good use of common options helper.**

Using the `addCommonOptions` helper ensures consistency in option handling across Bitcoin commands.

</details>
<details>
<summary>utils/bitcoin.command.helpers.ts (6)</summary>

`21-24`: **Interface definition looks good.**

The `BitcoinKeyPair` interface provides a clear structure for working with Bitcoin keypairs, containing both the address and the ECPair key.

---

`26-40`: **Comprehensive transaction info interface.**

The `TransactionInfo` interface provides a thorough structure for representing Bitcoin transaction information, with appropriate optional fields for flexibility.

---

`42-78`: **Key pair setup implementation looks solid.**

The `setupBitcoinKeyPair` function properly handles both direct private key input and retrieving from stored account data. It includes appropriate error handling and correctly initializes the Bitcoin library with the ECC implementation.

---

`90-115`: **Well-designed transaction confirmation UI.**

The `displayAndConfirmTransaction` function provides a comprehensive and clear display of transaction details before asking for user confirmation, making it easy for users to understand what they're approving.

---

`175-191`: **Well-implemented command options with conflict handling.**

The `addCommonOptions` function properly sets up common Bitcoin-related command options with appropriate defaults and conflict handling between `--private-key` and `--name`.

---

`193-202`: **Good handling of amount parsing with safety checks.**

The `parseAmount` function correctly converts Bitcoin amounts to satoshis while ensuring the value doesn't exceed JavaScript's safe integer range.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T20:48:59Z
user: login: hernan-clich
body: Tested ACK ✅
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T21:05:25Z
user: login: fadeev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T07:54:11Z

Review comments:
diff_hunk: @@ -0,0 +1,160 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+
+type DepositOptions = z.infer<typeof depositOptionsSchema>;
+
+const main = async (options: DepositOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(options.privateKey, "hex"), {
+    network: SIGNET,
+  });
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  const utxos = (
+    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
+  ).data;
+
+  const revertAddress = options.revertAddress || address;
+
+  let data;
+  if (options.receiver && revertAddress) {
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(""),
+        revertAddress,
+        OpCode.Deposit,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error("Provide either --receiver or receiver encoded in --data");
+  }
+
+  const amountSatBig = ethers.parseUnits(options.amount, 8);
+  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
+    throw new Error("Amount exceeds JS safe-integer range");
+  }
+  const amountSat = Number(amountSatBig);
+
+  // Calculate total fees
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
+
+  // Display transaction information and confirm
+  console.log(`
+Network: Signet
+Amount: ${options.amount} BTC
+Gateway: ${options.gateway}
+Universal Contract: ${options.receiver}
+Revert Address: ${revertAddress}
+Operation: Deposit
+Encoding Format: ABI
+Raw Inscription Data: ${data.toString("hex")}
+Fees:
+  - Commit Fee: ${commitFee} sat
+  - Reveal Fee: ${revealFee} sat
+  - Total Fee: ${totalFee} sat (${(totalFee / 100000000).toFixed(8)} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+
+  // Create and broadcast commit transaction
path: packages/commands/src/bitcoin/deposit.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`confirm()` result is ignored – user “No” still broadcasts both TXs**  

`await confirm()` returns a boolean indicating the user’s choice, but the value is discarded.  
If the user replies *No*, the script merrily proceeds to send real transactions.

```diff
-await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+const ok = await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+if (!ok) {
+  console.log("Aborted by user.");
+  return;
+}
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const ok = await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
  if (!ok) {
    console.log("Aborted by user.");
    return;
  }

  // Create and broadcast commit transaction
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T14:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,160 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+
+type DepositOptions = z.infer<typeof depositOptionsSchema>;
+
+const main = async (options: DepositOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(options.privateKey, "hex"), {
+    network: SIGNET,
+  });
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  const utxos = (
+    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
+  ).data;
+
+  const revertAddress = options.revertAddress || address;
+
+  let data;
+  if (options.receiver && revertAddress) {
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(""),
+        revertAddress,
+        OpCode.Deposit,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error("Provide either --receiver or receiver encoded in --data");
+  }
path: packages/commands/src/bitcoin/deposit.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**CLI option validation can allow ill-formed hex in `--data`**  

The schema only marks `data` as an optional string. Supplying odd-length or non-hex characters will make `Buffer.from(str,"hex")` throw at runtime.

Add a `refine` check (example):

```diff
-data: z.string().optional(),
+data: z
+  .string()
+  .regex(/^(0x)?[0-9a-fA-F]*$/, { message: "data must be hex encoded" })
+  .optional(),
```



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T14:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,160 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+
+type DepositOptions = z.infer<typeof depositOptionsSchema>;
+
+const main = async (options: DepositOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(options.privateKey, "hex"), {
+    network: SIGNET,
+  });
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  const utxos = (
+    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
+  ).data;
+
+  const revertAddress = options.revertAddress || address;
+
+  let data;
+  if (options.receiver && revertAddress) {
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(""),
+        revertAddress,
+        OpCode.Deposit,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error("Provide either --receiver or receiver encoded in --data");
+  }
+
+  const amountSatBig = ethers.parseUnits(options.amount, 8);
+  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
+    throw new Error("Amount exceeds JS safe-integer range");
+  }
+  const amountSat = Number(amountSatBig);
path: packages/commands/src/bitcoin/deposit.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix BigInt ⇢ Number comparison – current code will not compile under `strict` TS settings**  

`ethers.parseUnits()` returns a `bigint`, yet the code compares it to `Number.MAX_SAFE_INTEGER` (a `number`).  
TypeScript forbids mixing the two primitive numeric types.

```diff
-const amountSatBig = ethers.parseUnits(options.amount, 8);
-if (amountSatBig > Number.MAX_SAFE_INTEGER) {
+const amountSatBig = ethers.parseUnits(options.amount, 8n);
+if (amountSatBig > BigInt(Number.MAX_SAFE_INTEGER)) {
```

…then cast safely:

```diff
-const amountSat = Number(amountSatBig);
+const amountSat = Number(amountSatBig); // safe because of the range-check above
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const amountSatBig = ethers.parseUnits(options.amount, 8n);
  if (amountSatBig > BigInt(Number.MAX_SAFE_INTEGER)) {
    throw new Error("Amount exceeds JS safe-integer range");
  }
  const amountSat = Number(amountSatBig); // safe because of the range-check above
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T14:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,45 +46,64 @@ const main = async (options: DepositAndCallOptions) => {
     pubkey: key.publicKey,
   });
 
-  // Get available UTXOs
   const utxos = (
     await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
   ).data;
 
-  // Encode contract call data for inscription
-  const encodedPayload = new ethers.AbiCoder().encode(
-    options.types,
-    options.values
-  );
-  const inscriptionData = Buffer.from(
-    bitcoinEncode(
-      options.receiver,
-      Buffer.from(trimOx(encodedPayload), "hex"),
-      options.revertAddress,
-      OpCode.DepositAndCall,
-      EncodingFormat.EncodingFmtABI
-    ),
-    "hex"
-  );
+  let data;
+  let payload;
+  if (
+    options.types &&
+    options.values &&
+    options.receiver &&
+    options.revertAddress
+  ) {
+    // Encode contract call data for inscription
+    payload = new ethers.AbiCoder().encode(options.types, options.values);
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(trimOx(payload), "hex"),
+        options.revertAddress,
+        OpCode.DepositAndCall,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error(
+      "Provide either data or types, values, receiver, and revert address"
+    );
+  }
path: packages/commands/src/bitcoin/depositAndCall.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Code duplication across deposit / call / deposit-and-call**  

The blocks that (a) build `payload` & `data`, (b) display the summary, and (c) handle confirm logic are nearly identical across three commands. Extracting helpers (e.g., `buildInscriptionData()`, `printSummary()`, `askForConfirmation()`) would:

• reduce maintenance cost  
• ensure behaviour stays consistent  
• improve testability

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 5aabf43 to 034a502
created_at: 2025-05-08T14:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,23 +44,79 @@ export interface BtcTxById {
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
       message: "Amount must be a valid positive number",
     }),
     api: z.string().url(),
+    data: z.string().optional(),
     gateway: z.string(),
     privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
path: types/bitcoin.types.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enforce mutual-exclusivity between `--data` and `--types/--values`.**

`depositAndCallOptionsSchema` currently allows users to pass *both* raw `data` **and** ABI‐based `types` / `values`, which contradicts the CLI help text (“use `--data` *instead of* types + values”).  This will silently accept ambiguous input and may lead to the wrong encoding path being taken.

You can tighten the constraint inside the same refine block:

```diff
   // If both are undefined/not provided, that's valid
-  return true;
+  // Reject when data is provided together with types/values
+  if (data.data && (data.types || data.values)) return false;
+  // Accept all other cases
+  return true;
 },
 {
   message:
-    "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+    "Provide *either* --data *or* matching --types/--values (not both); when using ABI mode, 'types' & 'values' must have identical length",
   path: ["values"],
 }
```



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 5aabf43
created_at: 2025-05-08T14:52:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -112,20 +114,33 @@ export const makeCommitTransaction = async (
   for (const u of utxos) {
     inTotal += u.value;
     picks.push(u);
-    if (inTotal >= amountSat + feeSat) break;
+    if (inTotal >= effectiveAmount + feeSat) break;
   }
-  if (inTotal < amountSat + feeSat) throw new Error("Not enough funds");
-  const changeSat = inTotal - amountSat - feeSat;
+  if (inTotal < effectiveAmount + feeSat) throw new Error("Not enough funds");
+  const changeSat = inTotal - effectiveAmount - feeSat;
 
   /* leaf script */
-  const leafScript = bitcoin.script.compile([
+  const scriptItems = [
     key.publicKey.slice(1, 33),
     bitcoin.opcodes.OP_CHECKSIG,
     bitcoin.opcodes.OP_FALSE,
     bitcoin.opcodes.OP_IF,
-    inscriptionData,
-    bitcoin.opcodes.OP_ENDIF,
-  ]);
+  ];
+
+  // Add inscription data in chunks if it exceeds 520 bytes (max script element size)
+  const MAX_SCRIPT_ELEMENT_SIZE = 520;
+  if (inscriptionData.length > MAX_SCRIPT_ELEMENT_SIZE) {
+    for (let i = 0; i < inscriptionData.length; i += MAX_SCRIPT_ELEMENT_SIZE) {
+      const end = Math.min(i + MAX_SCRIPT_ELEMENT_SIZE, inscriptionData.length);
+      scriptItems.push(inscriptionData.slice(i, end));
+    }
+  } else {
+    scriptItems.push(inscriptionData);
+  }
+
+  scriptItems.push(bitcoin.opcodes.OP_ENDIF);
+
path: utils/bitcoin.helpers.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Reveal script is currently unspendable – empty 64-byte sig always fails `OP_CHECKSIG`.**

The chunking code is fine, but the surrounding script still has the pattern:

```
<pubKey> OP_CHECKSIG OP_FALSE OP_IF … OP_ENDIF
```

`makeRevealTransaction` pushes a 64-byte zero signature; `OP_CHECKSIG` will evaluate to 0, leaving **false** on the stack. After `OP_FALSE OP_IF` the final stack top is 0 ⇒ script fails.  

Fix options:

1. Require a real Schnorr signature in the witness (preferred for security).
2. Keep the dummy sig but force stack truthiness:

```diff
-  key.publicKey.slice(1, 33),
-  bitcoin.opcodes.OP_CHECKSIG,
-  bitcoin.opcodes.OP_FALSE,
+  key.publicKey.slice(1, 33),
+  bitcoin.opcodes.OP_CHECKSIG,
+  bitcoin.opcodes.OP_TRUE,   // ensure non-zero top-of-stack
+  bitcoin.opcodes.OP_FALSE,
```

and adjust the witness builder accordingly.

Without this fix every reveal transaction will be invalid.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const scriptItems = [
    key.publicKey.slice(1, 33),
    bitcoin.opcodes.OP_CHECKSIG,
    bitcoin.opcodes.OP_TRUE,   // ensure non-zero top-of-stack
    bitcoin.opcodes.OP_FALSE,
    bitcoin.opcodes.OP_IF,
  ];

  // Add inscription data in chunks if it exceeds 520 bytes (max script element size)
  const MAX_SCRIPT_ELEMENT_SIZE = 520;
  if (inscriptionData.length > MAX_SCRIPT_ELEMENT_SIZE) {
    for (let i = 0; i < inscriptionData.length; i += MAX_SCRIPT_ELEMENT_SIZE) {
      const end = Math.min(i + MAX_SCRIPT_ELEMENT_SIZE, inscriptionData.length);
      scriptItems.push(inscriptionData.slice(i, end));
    }
  } else {
    scriptItems.push(inscriptionData);
  }

  scriptItems.push(bitcoin.opcodes.OP_ENDIF);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commits 5aabf43 to 27c2b2b

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-08T14:52:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import {
+  BITCOIN_FEES,
+  BITCOIN_LIMITS,
+} from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { callOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+  trimOx,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+import { handleError } from "../../../../utils/handleError";
+import { EVMAccountData } from "../../../../types/accounts.types";
+import { DEFAULT_ACCOUNT_NAME } from "../../../../types/shared.constants";
+import { getAccountData } from "../../../../utils/accounts";
+
+type CallOptions = z.infer<typeof callOptionsSchema>;
+
+/**
+ * Main function that executes the call operation.
+ * Creates and broadcasts both commit and reveal transactions to perform a cross-chain call.
+ *
+ * @param options - Command options including amounts, addresses, and contract parameters
+ */
+const main = async (options: CallOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  const privateKey =
+    options.privateKey ||
+    getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+
+  if (!privateKey) {
+    const errorMessage = handleError({
+      context: "Failed to retrieve private key",
+      error: new Error("Private key not found"),
+      shouldThrow: false,
+    });
+
+    throw new Error(errorMessage);
+  }
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(privateKey, "hex"), {
+    network: SIGNET,
+  });
+
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  const utxos = (
+    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
+  ).data;
+
+  let data;
+  let payload;
+  if (
+    options.types &&
+    options.values &&
+    options.receiver &&
+    options.revertAddress
+  ) {
+    // Encode contract call data for inscription
+    payload = new ethers.AbiCoder().encode(options.types, options.values);
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(trimOx(payload), "hex"),
+        options.revertAddress,
+        OpCode.Call,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error(
+      "Provide either data or types, values, receiver, and revert address"
+    );
+  }
+
+  const notApplicable = "encoded in raw inscription data";
+
+  // Calculate total fees
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
+
+  // Display transaction information and confirm
+  console.log(`
+Network: Signet
+Gateway: ${options.gateway}
+Sender: ${address}
+Universal Contract: ${options.receiver || notApplicable}
+Revert Address: ${options.revertAddress || notApplicable}
+Operation: Call
+Encoded Message: ${payload || notApplicable}
+Encoding Format: ABI
+Raw Inscription Data: ${data.toString("hex")}
+Fees:
+  - Commit Fee: ${commitFee} sat
+  - Reveal Fee: ${revealFee} sat
+  - Total Fee: ${totalFee} sat (${(totalFee / 100000000).toFixed(8)} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+
path: packages/commands/src/bitcoin/call.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**User confirmation is ignored — the command proceeds even if the answer is “No”**

`confirm()` returns a boolean, but the result is not checked, so the transactions are sent regardless of user intent.

```diff
-  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+  const proceed = await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+  if (!proceed) {
+    console.log("Operation aborted by user.");
+    return;
+  }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const proceed = await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
  if (!proceed) {
    console.log("Operation aborted by user.");
    return;
  }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-11T13:20:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import {
+  BITCOIN_FEES,
+  BITCOIN_LIMITS,
+} from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { callOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+  trimOx,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+import { handleError } from "../../../../utils/handleError";
+import { EVMAccountData } from "../../../../types/accounts.types";
+import { DEFAULT_ACCOUNT_NAME } from "../../../../types/shared.constants";
+import { getAccountData } from "../../../../utils/accounts";
+
+type CallOptions = z.infer<typeof callOptionsSchema>;
+
+/**
+ * Main function that executes the call operation.
+ * Creates and broadcasts both commit and reveal transactions to perform a cross-chain call.
+ *
+ * @param options - Command options including amounts, addresses, and contract parameters
+ */
+const main = async (options: CallOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  const privateKey =
+    options.privateKey ||
+    getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+
path: packages/commands/src/bitcoin/call.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Use `BitcoinAccountData` instead of `EVMAccountData` when fetching a Bitcoin account**

`getAccountData` is called with the `"bitcoin"` type, yet the generic is `EVMAccountData`.  
Types still appear to work because both interfaces carry a `privateKey`, but the mismatch is conceptually wrong and may hide future schema divergences.

```diff
-  options.privateKey ||
-  getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+  options.privateKey ||
+  getAccountData<BitcoinAccountData>("bitcoin", options.name)?.privateKey;
```
Also update the import at the top:

```diff
-import { EVMAccountData } from "../../../../types/accounts.types";
+import { BitcoinAccountData } from "../../../../types/accounts.types";
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
// At the top of packages/commands/src/bitcoin/call.ts
-import { EVMAccountData } from "../../../../types/accounts.types";
+import { BitcoinAccountData } from "../../../../types/accounts.types";

  // …

  const privateKey =
    options.privateKey ||
-   getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+   getAccountData<BitcoinAccountData>("bitcoin", options.name)?.privateKey;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 5aabf43 to 42bf1f9
created_at: 2025-05-11T13:20:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,178 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import { BITCOIN_FEES } from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { depositOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+import { handleError } from "../../../../utils/handleError";
+import { getAccountData } from "../../../../utils/accounts";
+import { EVMAccountData } from "../../../../types/accounts.types";
+
+type DepositOptions = z.infer<typeof depositOptionsSchema>;
+
+const main = async (options: DepositOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  const privateKey =
+    options.privateKey ||
+    getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+
+  if (!privateKey) {
+    const errorMessage = handleError({
+      context: "Failed to retrieve private key",
+      error: new Error("Private key not found"),
+      shouldThrow: false,
+    });
+
+    throw new Error(errorMessage);
+  }
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(privateKey, "hex"), {
+    network: SIGNET,
+  });
+
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  const utxos = (
+    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
+  ).data;
+
+  const revertAddress = options.revertAddress || address;
+
+  let data;
+  if (options.receiver && revertAddress) {
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(""),
+        revertAddress,
+        OpCode.Deposit,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error("Provide either --receiver or receiver encoded in --data");
+  }
+
+  const amountSatBig = ethers.parseUnits(options.amount, 8);
+  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
+    throw new Error("Amount exceeds JS safe-integer range");
+  }
+  const amountSat = Number(amountSatBig);
+
+  // Calculate total fees
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
+
+  // Display transaction information and confirm
+  console.log(`
+Network: Signet
+Amount: ${options.amount} BTC
+Gateway: ${options.gateway}
+Universal Contract: ${options.receiver}
+Revert Address: ${revertAddress}
+Operation: Deposit
+Encoding Format: ABI
+Raw Inscription Data: ${data.toString("hex")}
+Fees:
+  - Commit Fee: ${commitFee} sat
+  - Reveal Fee: ${revealFee} sat
+  - Total Fee: ${totalFee} sat (${(totalFee / 100000000).toFixed(8)} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+
+  // Create and broadcast commit transaction
+  const commit = await makeCommitTransaction(
+    key,
+    utxos,
+    address!,
+    data,
+    options.api,
+    amountSat
+  );
path: packages/commands/src/bitcoin/deposit.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify the address value before using non-null assertion**

Line 115 uses a non-null assertion (`address!`) but it would be safer to verify this value.

```diff
  // Create and broadcast commit transaction
  const commit = await makeCommitTransaction(
    key,
    utxos,
-   address!,
+   address || '',  // Consider better error handling if address is undefined
    data,
    options.api,
    amountSat
  );
```

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if address can ever be null in this context
grep -r "p2wpkh" --include="*.ts" . | grep -B 3 -A 3 "address"
```

Length of output: 1096

---

🏁 Script executed:

```shell
#!/bin/bash
# Display context around the p2wpkh payment and address usage in deposit.ts
rg -n "bitcoin.payments.p2wpkh" packages/commands/src/bitcoin/deposit.ts -A 10 -B 10
```

Length of output: 616

---



</details>

**Add a runtime check for `address` instead of using `!`**  

To avoid passing an undefined address into `makeCommitTransaction`, validate that `bitcoin.payments.p2wpkh` actually produced one and surface a clear error early.  

• File: packages/commands/src/bitcoin/deposit.ts  
• Around line 115 (where `address!` is used)  

```diff
-  const { address } = bitcoin.payments.p2wpkh({
+  const { address } = bitcoin.payments.p2wpkh({
     network: SIGNET,
     pubkey: key.publicKey,
   });

+  if (!address) {
+    throw new Error("Failed to derive SegWit address from public key");
+  }

  const utxos = (
    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
  ).data;

  // Create and broadcast commit transaction
-  const commit = await makeCommitTransaction(
-    key,
-    utxos,
-    address!,
+  const commit = await makeCommitTransaction(
+    key,
+    utxos,
+    address,
     data,
     options.api,
     amountSat
   );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
  const { address } = bitcoin.payments.p2wpkh({
    network: SIGNET,
    pubkey: key.publicKey,
  });

  if (!address) {
    throw new Error("Failed to derive SegWit address from public key");
  }

  const utxos = (
    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
  ).data;

  // Create and broadcast commit transaction
  const commit = await makeCommitTransaction(
    key,
    utxos,
    address,
    data,
    options.api,
    amountSat
  );
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-11T13:24:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,21 +47,79 @@
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: validAmountSchema,
     api: z.string().url(),
+    data: z.string().optional(),
     gateway: z.string(),
     privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
-  .refine((data) => data.types.length === data.values.length, {
-    message: "The 'types' and 'values' arrays must have the same length",
-    path: ["values"],
-  });
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
+
+export const depositOptionsSchema = z.object({
+  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
+    message: "Amount must be a valid positive number",
+  }),
+  api: z.string().url(),
+  data: z.string().optional(),
+  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+  gateway: z.string(),
+  privateKey: z.string().optional(),
+  receiver: z.string().optional(),
+  revertAddress: z.string().optional(),
+});
path: types/bitcoin.types.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Sort object keys alphabetically and enforce mutual exclusivity in deposit schema**

The object keys should be in alphabetical order as indicated by the static analysis warning, and mutual exclusivity between `data` and `receiver`/`revertAddress` should be enforced.

```diff
export const depositOptionsSchema = z.object({
  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
    message: "Amount must be a valid positive number",
  }),
  api: z.string().url(),
  data: z.string().optional(),
+  gateway: z.string(),
  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
-  gateway: z.string(),
  privateKey: z.string().optional(),
  receiver: z.string().optional(),
  revertAddress: z.string().optional(),
+}).refine(
+  (data) => {
+    // Prevent providing both data and receiver/revertAddress
+    if (data.data && (data.receiver || data.revertAddress)) {
+      return false;
+    }
+    return true;
+  },
+  {
+    message: "Provide either --data or --receiver/--revert-address (not both)",
+    path: ["data"],
+  }
});
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
export const depositOptionsSchema = z.object({
  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
    message: "Amount must be a valid positive number",
  }),
  api: z.string().url(),
  data: z.string().optional(),
  gateway: z.string(),
  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
  privateKey: z.string().optional(),
  receiver: z.string().optional(),
  revertAddress: z.string().optional(),
}).refine(
  (data) => {
    // Prevent providing both data and receiver/revertAddress
    if (data.data && (data.receiver || data.revertAddress)) {
      return false;
    }
    return true;
  },
  {
    message: "Provide either --data or --receiver/--revert-address (not both)",
    path: ["data"],
  }
);
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 89-89: 
Expected object keys to be in ascending order. 'gateway' should be before 'name'

</details>
<details>
<summary>🪛 ESLint</summary>

[error] 89-89: Expected object keys to be in ascending order. 'gateway' should be before 'name'.

(sort-keys-fix/sort-keys-fix)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-11T13:24:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,21 +47,79 @@
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: validAmountSchema,
     api: z.string().url(),
+    data: z.string().optional(),
     gateway: z.string(),
     privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
-  .refine((data) => data.types.length === data.values.length, {
-    message: "The 'types' and 'values' arrays must have the same length",
-    path: ["values"],
-  });
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
+
+export const depositOptionsSchema = z.object({
+  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
+    message: "Amount must be a valid positive number",
+  }),
+  api: z.string().url(),
+  data: z.string().optional(),
+  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+  gateway: z.string(),
+  privateKey: z.string().optional(),
+  receiver: z.string().optional(),
+  revertAddress: z.string().optional(),
+});
+
+export const callOptionsSchema = z
+  .object({
+    api: z.string().url(),
+    data: z.string().optional(),
+    gateway: z.string(),
+    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+    privateKey: z.string().optional(),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
+  })
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
path: types/bitcoin.types.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enforce mutual-exclusivity between `data` and `types/values` in call schema**

Like in the `depositAndCallOptionsSchema`, mutual exclusivity should be enforced between using raw `data` or ABI-based `types`/`values` parameters in the `callOptionsSchema`.

```diff
export const callOptionsSchema = z
  .object({
    api: z.string().url(),
    data: z.string().optional(),
    gateway: z.string(),
    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
    privateKey: z.string().optional(),
    receiver: z.string().optional(),
    revertAddress: z.string().optional(),
    types: z.array(z.string()).optional(),
    values: z.array(z.string()).optional(),
  })
  .refine(
    (data) => {
      // Only check length equality if both arrays exist
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      // If one exists and the other doesn't, that's invalid
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      // If both are undefined/not provided, that's valid
      return true;
    },
    {
      message:
        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
      path: ["values"],
    }
+  )
+  .refine(
+    (data) => {
+      // Prevent providing both data and types/values
+      if (data.data && (data.types || data.values)) {
+        return false;
+      }
+      return true;
+    },
+    {
+      message: "Provide either --data or --types/--values (not both)",
+      path: ["data"],
+    }
  );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
export const callOptionsSchema = z
  .object({
    api: z.string().url(),
    data: z.string().optional(),
    gateway: z.string(),
    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
    privateKey: z.string().optional(),
    receiver: z.string().optional(),
    revertAddress: z.string().optional(),
    types: z.array(z.string()).optional(),
    values: z.array(z.string()).optional(),
  })
  .refine(
    (data) => {
      // Only check length equality if both arrays exist
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      // If one exists and the other doesn't, that's invalid
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      // If both are undefined/not provided, that's valid
      return true;
    },
    {
      message:
        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
      path: ["values"],
    }
  )
  .refine(
    (data) => {
      // Prevent providing both raw `data` and ABI-based `types`/`values`
      if (data.data && (data.types || data.values)) {
        return false;
      }
      return true;
    },
    {
      message: "Provide either --data or --types/--values (not both)",
      path: ["data"],
    }
  );
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit 5aabf43
created_at: 2025-05-11T13:24:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,21 +47,79 @@
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: validAmountSchema,
     api: z.string().url(),
+    data: z.string().optional(),
     gateway: z.string(),
     privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
-  .refine((data) => data.types.length === data.values.length, {
-    message: "The 'types' and 'values' arrays must have the same length",
-    path: ["values"],
-  });
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
+
+export const depositOptionsSchema = z.object({
+  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
+    message: "Amount must be a valid positive number",
+  }),
+  api: z.string().url(),
+  data: z.string().optional(),
+  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+  gateway: z.string(),
+  privateKey: z.string().optional(),
+  receiver: z.string().optional(),
+  revertAddress: z.string().optional(),
+});
+
+export const callOptionsSchema = z
+  .object({
+    api: z.string().url(),
+    data: z.string().optional(),
+    gateway: z.string(),
+    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+    privateKey: z.string().optional(),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
+  })
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
path: types/bitcoin.types.ts
user: login: fadeev
body: https://github.com/zeta-chain/toolkit/pull/325/commits/5aabf4399c3292eec0a1ab6a09ba051956f0c84c
created_at: 2025-05-11T13:34:27Z
author_association: MEMBER
diff_hunk: @@ -46,21 +47,79 @@
   weight: number;
 }
 
-/**
- * Schema for the deposit-and-call command options
- */
 export const depositAndCallOptionsSchema = z
   .object({
     amount: validAmountSchema,
     api: z.string().url(),
+    data: z.string().optional(),
     gateway: z.string(),
     privateKey: z.string().min(1, "Private key is required"),
-    receiver: z.string().min(1, "Receiver address is required"),
-    revertAddress: z.string(),
-    types: z.array(z.string()),
-    values: z.array(z.string()),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
   })
-  .refine((data) => data.types.length === data.values.length, {
-    message: "The 'types' and 'values' arrays must have the same length",
-    path: ["values"],
-  });
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
+
+export const depositOptionsSchema = z.object({
+  amount: z.string().refine((val) => !isNaN(Number(val)) && Number(val) > 0, {
+    message: "Amount must be a valid positive number",
+  }),
+  api: z.string().url(),
+  data: z.string().optional(),
+  name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+  gateway: z.string(),
+  privateKey: z.string().optional(),
+  receiver: z.string().optional(),
+  revertAddress: z.string().optional(),
+});
+
+export const callOptionsSchema = z
+  .object({
+    api: z.string().url(),
+    data: z.string().optional(),
+    gateway: z.string(),
+    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
+    privateKey: z.string().optional(),
+    receiver: z.string().optional(),
+    revertAddress: z.string().optional(),
+    types: z.array(z.string()).optional(),
+    values: z.array(z.string()).optional(),
+  })
+  .refine(
+    (data) => {
+      // Only check length equality if both arrays exist
+      if (data.types && data.values) {
+        return data.types.length === data.values.length;
+      }
+      // If one exists and the other doesn't, that's invalid
+      if ((data.types && !data.values) || (!data.types && data.values)) {
+        return false;
+      }
+      // If both are undefined/not provided, that's valid
+      return true;
+    },
+    {
+      message:
+        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
+      path: ["values"],
+    }
+  );
path: types/bitcoin.types.ts
user: login: coderabbitai[bot]
body: @fadeev Thank you for sharing the commit link. I can see you've addressed the suggestion to enforce mutual-exclusivity between `data` and `types/values` in the `callOptionsSchema`. Great job implementing this validation rule consistently!

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Enforce%20mutual%20exclusivity%20between%20raw%20%60data%60%20and%20ABI%20%60types%60%2F%60values%60%20in%20call%20schema%20to%20prevent%20conflicts%20and%20ensure%20data%20integrity.%20Cleaner%2C%20safer%20validation!%20%F0%9F%94%92%E2%9C%A8%20Try%20%40coderabbitai!&url=https%3A%2F%2Fgithub.com%2Fzeta-chain%2Ftoolkit%2Fpull%2F325%23discussion_r2083524002)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@fadeev Thank you for sharing the commit link. I can see you've addressed the suggestion to enforce mutual-exclusivity between `data` and `types/values` in the `callOptionsSchema`. Great job implementing this validation rule consistently!

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Enforce%20mutual%20exclusivity%20between%20raw%20%60data%60%20and%20ABI%20%60types%60%2F%60values%60%20in%20call%20schema%20to%20prevent%20conflicts%20and%20ensure%20data%20integrity.%20Cleaner%2C%20safer%20validation!%20%F0%9F%94%92%E2%9C%A8%20Try%20%40coderabbitai!&url=https%3A%2F%2Fgithub.com%2Fzeta-chain%2Ftoolkit%2Fpull%2F325%23discussion_r2083524002)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-11T13:35:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,207 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import {
+  BITCOIN_FEES,
+  BITCOIN_LIMITS,
+} from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { callOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+  trimOx,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+import { handleError } from "../../../../utils/handleError";
+import { EVMAccountData } from "../../../../types/accounts.types";
+import { DEFAULT_ACCOUNT_NAME } from "../../../../types/shared.constants";
+import { getAccountData } from "../../../../utils/accounts";
+
+type CallOptions = z.infer<typeof callOptionsSchema>;
+
+/**
+ * Main function that executes the call operation.
+ * Creates and broadcasts both commit and reveal transactions to perform a cross-chain call.
+ *
+ * @param options - Command options including amounts, addresses, and contract parameters
+ */
+const main = async (options: CallOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  const privateKey =
+    options.privateKey ||
+    getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+
path: packages/commands/src/bitcoin/call.ts
user: login: fadeev
body: 42bf1f9
created_at: 2025-05-11T13:36:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,207 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+import { z } from "zod";
+
+import {
+  BITCOIN_FEES,
+  BITCOIN_LIMITS,
+} from "../../../../types/bitcoin.constants";
+import type { BtcUtxo } from "../../../../types/bitcoin.types";
+import { callOptionsSchema } from "../../../../types/bitcoin.types";
+import {
+  calculateFees,
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "../../../../utils/bitcoin.helpers";
+import {
+  bitcoinEncode,
+  EncodingFormat,
+  OpCode,
+  trimOx,
+} from "../../../../utils/bitcoinEncode";
+import { validateAndParseSchema } from "../../../../utils/validateAndParseSchema";
+import { handleError } from "../../../../utils/handleError";
+import { EVMAccountData } from "../../../../types/accounts.types";
+import { DEFAULT_ACCOUNT_NAME } from "../../../../types/shared.constants";
+import { getAccountData } from "../../../../utils/accounts";
+
+type CallOptions = z.infer<typeof callOptionsSchema>;
+
+/**
+ * Main function that executes the call operation.
+ * Creates and broadcasts both commit and reveal transactions to perform a cross-chain call.
+ *
+ * @param options - Command options including amounts, addresses, and contract parameters
+ */
+const main = async (options: CallOptions) => {
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  const privateKey =
+    options.privateKey ||
+    getAccountData<EVMAccountData>("bitcoin", options.name)?.privateKey;
+
path: packages/commands/src/bitcoin/call.ts
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@fadeev Thank you for making the fix! I can see from commit 42bf1f9 that you've updated the code to use the correct `BitcoinAccountData` type instead of `EVMAccountData`. This ensures type consistency and will help prevent potential issues if the schemas diverge in the future.



🐰 *happy rabbit noises*

---

🏁 Script executed:
```
#!/bin/bash
# Check the commit referenced by the user
git show 42bf1f9 --name-status
```

Length of output: 349

---

🏁 Script executed:
```
#!/bin/bash
# Get the content of the specific commit to see details of changes
git show 42bf1f9 -p
```

Length of output: 3131

---



</details>

@fadeev Thank you for making the fix! I can see from commit 42bf1f9 that you've updated the code to use the correct `BitcoinAccountData` type instead of `EVMAccountData`. I notice you also applied the same fix consistently across other Bitcoin-related command files. This ensures type consistency and will help prevent potential issues if the schemas diverge in the future.



🐰 *happy rabbit noises*

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets for understanding `packages/commands/src/bitcoin/call.ts`

---

#### File: `types/shared.constants.ts` (line 1)
```ts
export const DEFAULT_ACCOUNT_NAME = "default";
```
- Provides the default account name used in the call command options.

---

#### File: `types/bitcoin.constants.ts` (lines 40-48, 53-57)
```ts
export const BITCOIN_LIMITS = {
  DUST_THRESHOLD: {
    P2TR: 330,
    P2WPKH: 294,
  },
  // Minimum amount for commit output
  ESTIMATED_REVEAL_FEE: 5000,
  MIN_COMMIT_AMOUNT: 546, // Estimated fee for reveal transaction
};

export const BITCOIN_FEES = {
  DEFAULT_COMMIT_FEE_SAT: 15000,
  DEFAULT_REVEAL_FEE_RATE: 10,
  MIN_TOTAL_FEE: 20000, // Minimum total fee for both transactions
};
```
- Constants defining Bitcoin fee and limit parameters used in fee calculation and transaction construction.

---

#### File: `types/bitcoin.types.ts` (lines 6-16, 109-152)
```ts
export interface BtcUtxo {
  status: {
    block_hash: string;
    block_height: number;
    block_time: number;
    confirmed: boolean;
  };
  txid: string;
  value: number;
  vout: number;
}

export const callOptionsSchema = z
  .object({
    api: z.string().url(),
    data: z.string().optional(),
    gateway: z.string(),
    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
    privateKey: z.string().optional(),
    receiver: z.string().optional(),
    revertAddress: z.string().optional(),
    types: z.array(z.string()).optional(),
    values: z.array(z.string()).optional(),
  })
  .refine(
    (data) => {
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      return true;
    },
    {
      message:
        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
      path: ["values"],
    }
  )
  .refine(
    (data) => {
      if (data.data && (data.types || data.values)) {
        return false;
      }
      return true;
    },
    {
      message: "Provide either --data or --types/--values (not both)",
      path: ["data"],
    }
  );
```
- `BtcUtxo` type describes the structure of a Bitcoin UTXO used in transaction inputs.
- `callOptionsSchema` is the Zod schema validating the command options, enforcing rules on mutually exclusive fields and array length equality.

---

#### File: `utils/handleError.ts` (lines 31-45)
```ts
export const handleError = (options: ErrorHandlingOptions): string => {
  const { error, context, shouldThrow } = options;
  const errorMessage = getErrorMessage(error);
  const formattedMessage = context
    ? `${context}: ${errorMessage}`
    : errorMessage;

  console.error(formattedMessage);

  if (shouldThrow) {
    throw new Error(formattedMessage);
  }

  return formattedMessage;
};
```
- Utility to format and optionally throw errors, used in `main` to handle missing private key.

---

#### File: `utils/accounts.ts` (lines 45-63)
```ts
export const getAccountData = <
  T extends EVMAccountData | SolanaAccountData | BitcoinAccountData
>(
  accountType: (typeof AvailableAccountTypes)[number],
  accountName: string
): T | undefined => {
  const keyPath = getAccountKeyPath(accountType, accountName);

  if (!safeExists(keyPath)) {
    return undefined;
  }

  try {
    const keyData = parseJson(safeReadFile(keyPath), accountDataSchema);
    return keyData as T;
  } catch {
    return undefined;
  }
};
```
- Retrieves stored account data (including private key) by account type and name, used to get Bitcoin private key if not provided.

---

#### File: `utils/bitcoinEncode.ts` (lines 57-71, 183-185)
```ts
export const bitcoinEncode = (
  receiver: Address,
  payload: Buffer,
  revertAddress: BtcAddress,
  opCode: OpCode = OpCode.DepositAndCall,
  encodingFormat: EncodingFormat = EncodingFormat.EncodingFmtABI
): string => {
  // Create memo header
  const header = new Header(encodingFormat, opCode);

  // Create memo fields
  const fields = new FieldsV0(receiver, payload, revertAddress);

  return bytesToHex(encodeToBytes(header, fields));
};

export const trimOx = (hexString: string): string => {
  return hexString.startsWith("0x") ? hexString.slice(2) : hexString;
};
```
- `bitcoinEncode` encodes contract call data into a hex string inscription format.
- `trimOx` removes "0x" prefix from hex strings, used before encoding.

---

#### File: `utils/validateAndParseSchema.ts` (lines 28-59)
```ts
export const validateAndParseSchema = <T, U = T>(
  args: unknown,
  schema: z.ZodType<T, z.ZodTypeDef, U>,
  options?: {
    exitOnError?: boolean;
    shouldLogError?: boolean;
  }
): T => {
  const mergedOptions = {
    exitOnError: false,
    shouldLogError: true,
    ...options,
  };

  const result = schema.safeParse(args);

  if (!result.success) {
    const errorMessage = formatZodError(result.error);
    if (mergedOptions.shouldLogError) {
      console.error(`\x1b[31m${errorMessage}\x1b[0m`);
    }

    if (mergedOptions.exitOnError) {
      process.exit(1);
    } else {
      throw new Error(errorMessage);
    }
  }

  return result.data;
};
```
- Validates and parses command options against the Zod schema, throwing or exiting on validation errors.

---

#### File: `utils/bitcoin.helpers.ts` (lines 17-27, 96-178, 193-245, 252-265)
```ts
export const SIGNET = {
  bech32: BITCOIN_NETWORKS.SIGNET.BECH32,
  bip32: {
    private: BITCOIN_NETWORKS.SIGNET.BIP32.PRIVATE,
    public: BITCOIN_NETWORKS.SIGNET.BIP32.PUBLIC,
  },
  messagePrefix: BITCOIN_NETWORKS.SIGNET.MESSAGE_PREFIX,
  pubKeyHash: BITCOIN_NETWORKS.SIGNET.PUBKEY_HASH,
  scriptHash: BITCOIN_NETWORKS.SIGNET.SCRIPT_HASH,
  wif: BITCOIN_NETWORKS.SIGNET.WIF,
};

/**
 * Constructs a commit transaction with inscription data.
 * - Selects UTXOs to cover amount + fee.
 * - Builds Taproot leaf script embedding inscription data.
 * - Creates P2TR output with leaf script.
 * - Signs inputs and finalizes PSBT.
 * 
 * @param key - bitcoinjs-lib signer (private key)
 * @param utxos - available UTXOs for inputs
 * @param changeAddress - address for change output
 * @param inscriptionData - data to embed in commit output script
 * @param api - Bitcoin API URL for fetching full tx data
 * @param amountSat - satoshis to commit
 * @param feeSat - fee in satoshis (default commit fee)
 * @returns object with controlBlock, internalKey, leafScript, and txHex
 * @throws if insufficient funds or taproot build fails
 */
export const makeCommitTransaction = async (
  key: bitcoin.Signer,
  utxos: BtcUtxo[],
  changeAddress: string,
  inscriptionData: Buffer,
  api: string,
  amountSat: number,
  feeSat = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT
) => {
  // Implementation details omitted for brevity
};

export const makeRevealTransaction = (
  commitTxId: string,
  commitVout: number,
  commitValue: number,
  to: string,
  feeRate: number,
  commitData: { controlBlock: Buffer; internalKey: Buffer; leafScript: Buffer },
  key: bitcoin.Signer
) => {
  // Constructs reveal transaction spending commit output
  // Calculates fee based on feeRate and transaction size
  // Ensures output value is above dust threshold
  // Signs and finalizes PSBT, returns hex
};

export const calculateFees = (data: Buffer) => {
  const commitFee = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT;
  const revealFee = Math.ceil(
    (BITCOIN_TX.TX_OVERHEAD +
      36 +
      1 +
      43 +
      Math.ceil(data.length / 4) +
      BITCOIN_TX.P2WPKH_OUTPUT_VBYTES) *
      BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE
  );
  const totalFee = commitFee + revealFee;
  return { commitFee, revealFee, totalFee };
};
```
- `SIGNET` defines Bitcoin Signet network parameters.
- `makeCommitTransaction` creates the commit transaction embedding the inscription data.
- `makeRevealTransaction` creates the reveal transaction spending the commit output.
- `calculateFees` computes commit and reveal fees based on data size and fee rates.

---

These snippets provide the necessary context for understanding the command options, Bitcoin network parameters, fee calculations, transaction construction, error handling, and data encoding used in `call.ts`.

</details>

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20caught%20a%20crucial%20type%20mismatch%3A%20using%20BitcoinAccountData%20instead%20of%20EVMAccountData%20for%20Bitcoin%20accounts%20prevents%20future%20bugs%20and%20keeps%20code%20semantically%20correct!%20%F0%9F%94%92%F0%9F%90%87%20Try%20it%20now!&url=https%3A%2F%2Fgithub.com%2Fzeta-chain%2Ftoolkit%2Fpull%2F325%23discussion_r2083522948)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

@fadeev Thank you for making the fix! I can see from commit 42bf1f9 that you've updated the code to use the correct `BitcoinAccountData` type instead of `EVMAccountData`. This ensures type consistency and will help prevent potential issues if the schemas diverge in the future.



🐰 *happy rabbit noises*

---

🏁 Script executed:
```
#!/bin/bash
# Check the commit referenced by the user
git show 42bf1f9 --name-status
```

Length of output: 349

---

🏁 Script executed:
```
#!/bin/bash
# Get the content of the specific commit to see details of changes
git show 42bf1f9 -p
```

Length of output: 3131

---



</details>

@fadeev Thank you for making the fix! I can see from commit 42bf1f9 that you've updated the code to use the correct `BitcoinAccountData` type instead of `EVMAccountData`. I notice you also applied the same fix consistently across other Bitcoin-related command files. This ensures type consistency and will help prevent potential issues if the schemas diverge in the future.



🐰 *happy rabbit noises*

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets for understanding `packages/commands/src/bitcoin/call.ts`

---

#### File: `types/shared.constants.ts` (line 1)
```ts
export const DEFAULT_ACCOUNT_NAME = "default";
```
- Provides the default account name used in the call command options.

---

#### File: `types/bitcoin.constants.ts` (lines 40-48, 53-57)
```ts
export const BITCOIN_LIMITS = {
  DUST_THRESHOLD: {
    P2TR: 330,
    P2WPKH: 294,
  },
  // Minimum amount for commit output
  ESTIMATED_REVEAL_FEE: 5000,
  MIN_COMMIT_AMOUNT: 546, // Estimated fee for reveal transaction
};

export const BITCOIN_FEES = {
  DEFAULT_COMMIT_FEE_SAT: 15000,
  DEFAULT_REVEAL_FEE_RATE: 10,
  MIN_TOTAL_FEE: 20000, // Minimum total fee for both transactions
};
```
- Constants defining Bitcoin fee and limit parameters used in fee calculation and transaction construction.

---

#### File: `types/bitcoin.types.ts` (lines 6-16, 109-152)
```ts
export interface BtcUtxo {
  status: {
    block_hash: string;
    block_height: number;
    block_time: number;
    confirmed: boolean;
  };
  txid: string;
  value: number;
  vout: number;
}

export const callOptionsSchema = z
  .object({
    api: z.string().url(),
    data: z.string().optional(),
    gateway: z.string(),
    name: z.string().optional().default(DEFAULT_ACCOUNT_NAME),
    privateKey: z.string().optional(),
    receiver: z.string().optional(),
    revertAddress: z.string().optional(),
    types: z.array(z.string()).optional(),
    values: z.array(z.string()).optional(),
  })
  .refine(
    (data) => {
      if (data.types && data.values) {
        return data.types.length === data.values.length;
      }
      if ((data.types && !data.values) || (!data.types && data.values)) {
        return false;
      }
      return true;
    },
    {
      message:
        "If provided, the 'types' and 'values' arrays must both exist and have the same length",
      path: ["values"],
    }
  )
  .refine(
    (data) => {
      if (data.data && (data.types || data.values)) {
        return false;
      }
      return true;
    },
    {
      message: "Provide either --data or --types/--values (not both)",
      path: ["data"],
    }
  );
```
- `BtcUtxo` type describes the structure of a Bitcoin UTXO used in transaction inputs.
- `callOptionsSchema` is the Zod schema validating the command options, enforcing rules on mutually exclusive fields and array length equality.

---

#### File: `utils/handleError.ts` (lines 31-45)
```ts
export const handleError = (options: ErrorHandlingOptions): string => {
  const { error, context, shouldThrow } = options;
  const errorMessage = getErrorMessage(error);
  const formattedMessage = context
    ? `${context}: ${errorMessage}`
    : errorMessage;

  console.error(formattedMessage);

  if (shouldThrow) {
    throw new Error(formattedMessage);
  }

  return formattedMessage;
};
```
- Utility to format and optionally throw errors, used in `main` to handle missing private key.

---

#### File: `utils/accounts.ts` (lines 45-63)
```ts
export const getAccountData = <
  T extends EVMAccountData | SolanaAccountData | BitcoinAccountData
>(
  accountType: (typeof AvailableAccountTypes)[number],
  accountName: string
): T | undefined => {
  const keyPath = getAccountKeyPath(accountType, accountName);

  if (!safeExists(keyPath)) {
    return undefined;
  }

  try {
    const keyData = parseJson(safeReadFile(keyPath), accountDataSchema);
    return keyData as T;
  } catch {
    return undefined;
  }
};
```
- Retrieves stored account data (including private key) by account type and name, used to get Bitcoin private key if not provided.

---

#### File: `utils/bitcoinEncode.ts` (lines 57-71, 183-185)
```ts
export const bitcoinEncode = (
  receiver: Address,
  payload: Buffer,
  revertAddress: BtcAddress,
  opCode: OpCode = OpCode.DepositAndCall,
  encodingFormat: EncodingFormat = EncodingFormat.EncodingFmtABI
): string => {
  // Create memo header
  const header = new Header(encodingFormat, opCode);

  // Create memo fields
  const fields = new FieldsV0(receiver, payload, revertAddress);

  return bytesToHex(encodeToBytes(header, fields));
};

export const trimOx = (hexString: string): string => {
  return hexString.startsWith("0x") ? hexString.slice(2) : hexString;
};
```
- `bitcoinEncode` encodes contract call data into a hex string inscription format.
- `trimOx` removes "0x" prefix from hex strings, used before encoding.

---

#### File: `utils/validateAndParseSchema.ts` (lines 28-59)
```ts
export const validateAndParseSchema = <T, U = T>(
  args: unknown,
  schema: z.ZodType<T, z.ZodTypeDef, U>,
  options?: {
    exitOnError?: boolean;
    shouldLogError?: boolean;
  }
): T => {
  const mergedOptions = {
    exitOnError: false,
    shouldLogError: true,
    ...options,
  };

  const result = schema.safeParse(args);

  if (!result.success) {
    const errorMessage = formatZodError(result.error);
    if (mergedOptions.shouldLogError) {
      console.error(`\x1b[31m${errorMessage}\x1b[0m`);
    }

    if (mergedOptions.exitOnError) {
      process.exit(1);
    } else {
      throw new Error(errorMessage);
    }
  }

  return result.data;
};
```
- Validates and parses command options against the Zod schema, throwing or exiting on validation errors.

---

#### File: `utils/bitcoin.helpers.ts` (lines 17-27, 96-178, 193-245, 252-265)
```ts
export const SIGNET = {
  bech32: BITCOIN_NETWORKS.SIGNET.BECH32,
  bip32: {
    private: BITCOIN_NETWORKS.SIGNET.BIP32.PRIVATE,
    public: BITCOIN_NETWORKS.SIGNET.BIP32.PUBLIC,
  },
  messagePrefix: BITCOIN_NETWORKS.SIGNET.MESSAGE_PREFIX,
  pubKeyHash: BITCOIN_NETWORKS.SIGNET.PUBKEY_HASH,
  scriptHash: BITCOIN_NETWORKS.SIGNET.SCRIPT_HASH,
  wif: BITCOIN_NETWORKS.SIGNET.WIF,
};

/**
 * Constructs a commit transaction with inscription data.
 * - Selects UTXOs to cover amount + fee.
 * - Builds Taproot leaf script embedding inscription data.
 * - Creates P2TR output with leaf script.
 * - Signs inputs and finalizes PSBT.
 * 
 * @param key - bitcoinjs-lib signer (private key)
 * @param utxos - available UTXOs for inputs
 * @param changeAddress - address for change output
 * @param inscriptionData - data to embed in commit output script
 * @param api - Bitcoin API URL for fetching full tx data
 * @param amountSat - satoshis to commit
 * @param feeSat - fee in satoshis (default commit fee)
 * @returns object with controlBlock, internalKey, leafScript, and txHex
 * @throws if insufficient funds or taproot build fails
 */
export const makeCommitTransaction = async (
  key: bitcoin.Signer,
  utxos: BtcUtxo[],
  changeAddress: string,
  inscriptionData: Buffer,
  api: string,
  amountSat: number,
  feeSat = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT
) => {
  // Implementation details omitted for brevity
};

export const makeRevealTransaction = (
  commitTxId: string,
  commitVout: number,
  commitValue: number,
  to: string,
  feeRate: number,
  commitData: { controlBlock: Buffer; internalKey: Buffer; leafScript: Buffer },
  key: bitcoin.Signer
) => {
  // Constructs reveal transaction spending commit output
  // Calculates fee based on feeRate and transaction size
  // Ensures output value is above dust threshold
  // Signs and finalizes PSBT, returns hex
};

export const calculateFees = (data: Buffer) => {
  const commitFee = BITCOIN_FEES.DEFAULT_COMMIT_FEE_SAT;
  const revealFee = Math.ceil(
    (BITCOIN_TX.TX_OVERHEAD +
      36 +
      1 +
      43 +
      Math.ceil(data.length / 4) +
      BITCOIN_TX.P2WPKH_OUTPUT_VBYTES) *
      BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE
  );
  const totalFee = commitFee + revealFee;
  return { commitFee, revealFee, totalFee };
};
```
- `SIGNET` defines Bitcoin Signet network parameters.
- `makeCommitTransaction` creates the commit transaction embedding the inscription data.
- `makeRevealTransaction` creates the reveal transaction spending the commit output.
- `calculateFees` computes commit and reveal fees based on data size and fee rates.

---

These snippets provide the necessary context for understanding the command options, Bitcoin network parameters, fee calculations, transaction construction, error handling, and data encoding used in `call.ts`.

</details>

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20caught%20a%20crucial%20type%20mismatch%3A%20using%20BitcoinAccountData%20instead%20of%20EVMAccountData%20for%20Bitcoin%20accounts%20prevents%20future%20bugs%20and%20keeps%20code%20semantically%20correct!%20%F0%9F%94%92%F0%9F%90%87%20Try%20it%20now!&url=https%3A%2F%2Fgithub.com%2Fzeta-chain%2Ftoolkit%2Fpull%2F325%23discussion_r2083522948)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-11T13:38:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,6 +42,9 @@ export const BITCOIN_LIMITS = {
     P2TR: 330,
     P2WPKH: 294,
   },
+  // Minimum amount for commit output
+  ESTIMATED_REVEAL_FEE: 5000,
path: types/bitcoin.constants.ts
user: login: hernan-clich
body: Thanks for keeping all these constants here in the same place.
created_at: 2025-05-14T19:19:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,181 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory, { ECPairInterface } from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+
+import { BitcoinAccountData } from "../types/accounts.types";
+import { BITCOIN_FEES } from "../types/bitcoin.constants";
+import type { BtcUtxo } from "../types/bitcoin.types";
+import { DEFAULT_ACCOUNT_NAME } from "../types/shared.constants";
+import { getAccountData } from "./accounts";
+import {
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "./bitcoin.helpers";
+import { handleError } from "./handleError";
+
+export interface BitcoinKeyPair {
+  address: string;
+  key: ECPairInterface;
+}
+
+export interface TransactionInfo {
+  amount?: string;
+  commitFee: number;
+  encodedMessage?: string;
+  encodingFormat: string;
+  gateway: string;
+  network: string;
+  operation: string;
+  rawInscriptionData: string;
+  receiver?: string;
+  revealFee: number;
+  revertAddress?: string;
+  sender: string;
+  totalFee: number;
+}
+
+export const setupBitcoinKeyPair = (
+  privateKey: string | undefined,
+  name: string
+): BitcoinKeyPair => {
+  const keyPrivateKey =
+    privateKey ||
+    getAccountData<BitcoinAccountData>("bitcoin", name)?.privateKey;
+
+  if (!keyPrivateKey) {
+    const errorMessage = handleError({
+      context: "Failed to retrieve private key",
+      error: new Error("Private key not found"),
+      shouldThrow: false,
+    });
+
+    throw new Error(errorMessage);
+  }
+
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(keyPrivateKey, "hex"), {
+    network: SIGNET,
+  });
+
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  return { address: address!, key };
+};
+
+export const fetchUtxos = async (
+  address: string,
+  api: string
+): Promise<BtcUtxo[]> => {
+  return (await axios.get<BtcUtxo[]>(`${api}/address/${address}/utxo`)).data;
+};
+
+export const displayAndConfirmTransaction = async (info: TransactionInfo) => {
+  const notApplicable = "encoded in raw inscription data";
+
+  console.log(`
+Network: ${info.network}
+${info.amount ? `Amount: ${info.amount} BTC` : ""}
+Gateway: ${info.gateway}
+Sender: ${info.sender}
+Universal Contract: ${info.receiver || notApplicable}
+Revert Address: ${info.revertAddress || notApplicable}
+Operation: ${info.operation}
+${info.encodedMessage ? `Encoded Message: ${info.encodedMessage}` : ""}
+Encoding Format: ${info.encodingFormat}
+Raw Inscription Data: ${info.rawInscriptionData}
+Fees:
+  - Commit Fee: ${info.commitFee} sat
+  - Reveal Fee: ${info.revealFee} sat
+  - Total Fee: ${info.totalFee} sat (${(info.totalFee / 100000000).toFixed(
+    8
+  )} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+};
+
+export const broadcastTransaction = async (
+  txHex: string,
+  api: string
+): Promise<string> => {
+  return (
+    await axios.post<string>(`${api}/tx`, txHex, {
+      headers: { "Content-Type": "text/plain" },
+    })
+  ).data;
+};
+
+export const createAndBroadcastTransactions = async (
+  key: ECPairInterface,
+  utxos: BtcUtxo[],
+  address: string,
+  data: Buffer,
+  api: string,
+  amount: number,
+  gateway: string
+) => {
+  // Create and broadcast commit transaction
+  const commit = await makeCommitTransaction(
+    key,
+    utxos,
+    address,
+    data,
+    api,
+    amount
+  );
+
+  const commitTxid = await broadcastTransaction(commit.txHex, api);
+  console.log("Commit TXID:", commitTxid);
+
+  // Create and broadcast reveal transaction
+  const revealHex = makeRevealTransaction(
+    commitTxid,
+    0,
+    amount,
+    gateway,
+    BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE,
+    {
+      controlBlock: commit.controlBlock,
+      internalKey: commit.internalKey,
+      leafScript: commit.leafScript,
+    },
+    key
+  );
+  const revealTxid = await broadcastTransaction(revealHex, api);
+
+  return { commitTxid, revealTxid };
path: utils/bitcoin.command.helpers.ts
user: login: hernan-clich
body: This only broadcasts the txs and returns their tx ids, but it doesn't include any mechanism to verify whether either tx was successfully confirmed. If this is by design then I'm fine with that.
created_at: 2025-05-14T20:21:23Z
author_association: MEMBER
diff_hunk: @@ -112,20 +114,33 @@ export const makeCommitTransaction = async (
   for (const u of utxos) {
     inTotal += u.value;
     picks.push(u);
-    if (inTotal >= amountSat + feeSat) break;
+    if (inTotal >= effectiveAmount + feeSat) break;
   }
-  if (inTotal < amountSat + feeSat) throw new Error("Not enough funds");
-  const changeSat = inTotal - amountSat - feeSat;
+  if (inTotal < effectiveAmount + feeSat) throw new Error("Not enough funds");
+  const changeSat = inTotal - effectiveAmount - feeSat;
 
   /* leaf script */
-  const leafScript = bitcoin.script.compile([
+  const scriptItems = [
     key.publicKey.slice(1, 33),
     bitcoin.opcodes.OP_CHECKSIG,
     bitcoin.opcodes.OP_FALSE,
     bitcoin.opcodes.OP_IF,
-    inscriptionData,
-    bitcoin.opcodes.OP_ENDIF,
-  ]);
+  ];
+
+  // Add inscription data in chunks if it exceeds 520 bytes (max script element size)
+  const MAX_SCRIPT_ELEMENT_SIZE = 520;
+  if (inscriptionData.length > MAX_SCRIPT_ELEMENT_SIZE) {
+    for (let i = 0; i < inscriptionData.length; i += MAX_SCRIPT_ELEMENT_SIZE) {
+      const end = Math.min(i + MAX_SCRIPT_ELEMENT_SIZE, inscriptionData.length);
+      scriptItems.push(inscriptionData.slice(i, end));
+    }
+  } else {
+    scriptItems.push(inscriptionData);
+  }
+
+  scriptItems.push(bitcoin.opcodes.OP_ENDIF);
+
path: utils/bitcoin.helpers.ts
user: login: hernan-clich
body: Will this simply work as expected after adding `OP_TRUE` between `OP_CHECKSIG` and `OP_FALSE`? Or is there any additional measure required? I'm asking because you said "and adjust the witness builder accordingly". What did you mean by that?
created_at: 2025-05-14T20:31:51Z
author_association: MEMBER
diff_hunk: @@ -112,20 +114,33 @@ export const makeCommitTransaction = async (
   for (const u of utxos) {
     inTotal += u.value;
     picks.push(u);
-    if (inTotal >= amountSat + feeSat) break;
+    if (inTotal >= effectiveAmount + feeSat) break;
   }
-  if (inTotal < amountSat + feeSat) throw new Error("Not enough funds");
-  const changeSat = inTotal - amountSat - feeSat;
+  if (inTotal < effectiveAmount + feeSat) throw new Error("Not enough funds");
+  const changeSat = inTotal - effectiveAmount - feeSat;
 
   /* leaf script */
-  const leafScript = bitcoin.script.compile([
+  const scriptItems = [
     key.publicKey.slice(1, 33),
     bitcoin.opcodes.OP_CHECKSIG,
     bitcoin.opcodes.OP_FALSE,
     bitcoin.opcodes.OP_IF,
-    inscriptionData,
-    bitcoin.opcodes.OP_ENDIF,
-  ]);
+  ];
+
+  // Add inscription data in chunks if it exceeds 520 bytes (max script element size)
+  const MAX_SCRIPT_ELEMENT_SIZE = 520;
+  if (inscriptionData.length > MAX_SCRIPT_ELEMENT_SIZE) {
+    for (let i = 0; i < inscriptionData.length; i += MAX_SCRIPT_ELEMENT_SIZE) {
+      const end = Math.min(i + MAX_SCRIPT_ELEMENT_SIZE, inscriptionData.length);
+      scriptItems.push(inscriptionData.slice(i, end));
+    }
+  } else {
+    scriptItems.push(inscriptionData);
+  }
+
+  scriptItems.push(bitcoin.opcodes.OP_ENDIF);
+
path: utils/bitcoin.helpers.ts
user: login: coderabbitai[bot]
body: @hernan-clich Adding `OP_TRUE` between `OP_CHECKSIG` and `OP_FALSE` will be sufficient on its own - you don't need to adjust the witness builder.

Let me clarify the execution flow:

1. With the current script: 
   - `OP_CHECKSIG` with empty signature returns 0 (false)
   - After `OP_FALSE`, we have [0,0] on stack
   - `OP_IF` sees 0, skips the body
   - Script ends with [0] on stack, which is falsy → fails

2. With proposed change:
   - `OP_CHECKSIG` returns 0 (false)
   - `OP_TRUE` pushes 1
   - `OP_FALSE` pushes 0
   - `OP_IF` sees 0, skips the body
   - Script ends with [0,1] on stack, which is truthy → passes

The mention of "adjust the witness builder" was referring to the alternative approach (requiring a real signature) which would need more complex changes. The simple `OP_TRUE` addition works fine with the existing witness builder and empty signature.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@hernan-clich Adding `OP_TRUE` between `OP_CHECKSIG` and `OP_FALSE` will be sufficient on its own - you don't need to adjust the witness builder.

Let me clarify the execution flow:

1. With the current script: 
   - `OP_CHECKSIG` with empty signature returns 0 (false)
   - After `OP_FALSE`, we have [0,0] on stack
   - `OP_IF` sees 0, skips the body
   - Script ends with [0] on stack, which is falsy → fails

2. With proposed change:
   - `OP_CHECKSIG` returns 0 (false)
   - `OP_TRUE` pushes 1
   - `OP_FALSE` pushes 0
   - `OP_IF` sees 0, skips the body
   - Script ends with [0,1] on stack, which is truthy → passes

The mention of "adjust the witness builder" was referring to the alternative approach (requiring a real signature) which would need more complex changes. The simple `OP_TRUE` addition works fine with the existing witness builder and empty signature.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-14T20:39:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,99 +29,71 @@ type DepositAndCallOptions = z.infer<typeof depositAndCallOptionsSchema>;
  * @param options - Command options including amounts, addresses, and contract parameters
  */
 const main = async (options: DepositAndCallOptions) => {
-  // Initialize Bitcoin library with ECC implementation
-  bitcoin.initEccLib(ecc);
-
-  // Set up Bitcoin key pair
-  const ECPair = ECPairFactory(ecc);
-  const key = ECPair.fromPrivateKey(Buffer.from(options.privateKey, "hex"), {
-    network: SIGNET,
-  });
-  const { address } = bitcoin.payments.p2wpkh({
-    network: SIGNET,
-    pubkey: key.publicKey,
-  });
-
-  // Get available UTXOs
-  const utxos = (
-    await axios.get<BtcUtxo[]>(`${options.api}/address/${address}/utxo`)
-  ).data;
-
-  // Encode contract call data for inscription
-  const encodedPayload = new ethers.AbiCoder().encode(
-    options.types,
-    options.values
-  );
-  const inscriptionData = Buffer.from(
-    bitcoinEncode(
-      options.receiver,
-      Buffer.from(trimOx(encodedPayload), "hex"),
-      options.revertAddress,
-      OpCode.DepositAndCall,
-      EncodingFormat.EncodingFmtABI
-    ),
-    "hex"
+  const { key, address } = setupBitcoinKeyPair(
+    options.privateKey,
+    options.name
   );
+  const utxos = await fetchUtxos(address, options.api);
 
-  // Convert BTC amount to satoshis
-  const amountSatBig = ethers.parseUnits(options.amount, 8);
-  if (amountSatBig > Number.MAX_SAFE_INTEGER) {
-    throw new Error("Amount exceeds JS safe-integer range");
+  let data;
+  let payload;
+  if (
+    options.types &&
+    options.values &&
+    options.receiver &&
+    options.revertAddress
+  ) {
+    // Encode contract call data for inscription
+    payload = new ethers.AbiCoder().encode(options.types, options.values);
+    data = Buffer.from(
+      bitcoinEncode(
+        options.receiver,
+        Buffer.from(trimOx(payload), "hex"),
+        options.revertAddress,
+        OpCode.DepositAndCall,
+        EncodingFormat.EncodingFmtABI
+      ),
+      "hex"
+    );
+  } else if (options.data) {
+    data = Buffer.from(options.data, "hex");
+  } else {
+    throw new Error(
+      "Provide either data or types, values, receiver, and revert address"
+    );
   }
-  const amountSat = Number(amountSatBig);
+
+  const amount =
+    BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE;
+
+  const { commitFee, revealFee, totalFee } = calculateFees(data);
 
   // Display transaction information and confirm
-  console.log(`
-Network: Signet
-Amount: ${options.amount} BTC
-Gateway: ${options.gateway}
-Universal Contract: ${options.receiver}
-Revert Address: ${options.revertAddress}
-Operation: DepositAndCall
-Encoded Message: ${encodedPayload}
-Encoding Format: ABI
-Raw Inscription Data: ${inscriptionData.toString("hex")}
-`);
-  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+  await displayAndConfirmTransaction({
+    amount: options.amount,
+    commitFee,
+    encodedMessage: payload,
+    encodingFormat: "ABI",
+    gateway: options.gateway,
+    network: "Signet",
+    operation: "DepositAndCall",
+    rawInscriptionData: data.toString("hex"),
+    receiver: options.receiver,
+    revealFee,
+    revertAddress: options.revertAddress,
+    sender: address,
+    totalFee,
+  });
path: packages/commands/src/bitcoin/depositAndCall.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Discrepancy between user-provided amount and internal amount calculation**

The user must provide an amount via `--amount`, but this value is only used for display purposes and not for the actual transaction. The code internally uses `BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE` regardless of user input.


This could be confusing for users who expect their specified amount to be used. Consider either:
1. Using the user's amount for the transaction (with validation)
2. Clarifying in the documentation that the amount is a display value only
3. Making the amount parameter optional if it's not actually used

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if this behavior is documented anywhere
rg -n "amount.*display" --include="*.md" --include="*.ts"
```

Length of output: 501

---

I’ll inspect the `depositAndCall.ts` implementation to confirm exactly how `options.amount` is (not) used in the actual transaction logic.

---

🏁 Script executed:

```shell
#!/bin/bash
set -e

# Locate the file
FILE=$(find . -type f -name depositAndCall.ts | head -n1)
echo "Inspecting: $FILE"

# Show the relevant portion around the amount usage and transaction construction
sed -n '1,200p; /displayAndConfirmTransaction/!d' "$FILE"
```

Length of output: 4220

---



</details>

Here’s what I found:

- In `depositAndCall.ts`, you compute  
  ```ts
  const amount = BITCOIN_LIMITS.MIN_COMMIT_AMOUNT + BITCOIN_LIMITS.ESTIMATED_REVEAL_FEE
  ```
  then  
  1. pass `options.amount` to `displayAndConfirmTransaction` for display  
  2. use the internal `amount` when calling  
     ```ts
     createAndBroadcastTransactions(
       …,
       amount,
       …
     )
     ```
- There’s no documentation noting that `--amount` is only for display; nor is `options.amount` ever used to build the transaction.

**Recommended fixes:**
- Either wire up `options.amount` into the actual transaction (validating it ≥ required minimum plus fees), or
- Document that `--amount` purely affects display/confirmation, or
- Drop/optional-ify `--amount` if it’s not really used.

Tagging as **fix required** since the discrepancy still needs resolving.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-14T20:48:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,181 @@
+import confirm from "@inquirer/confirm";
+import axios from "axios";
+import * as bitcoin from "bitcoinjs-lib";
+import { Command, Option } from "commander";
+import ECPairFactory, { ECPairInterface } from "ecpair";
+import { ethers } from "ethers";
+import * as ecc from "tiny-secp256k1";
+
+import { BitcoinAccountData } from "../types/accounts.types";
+import { BITCOIN_FEES } from "../types/bitcoin.constants";
+import type { BtcUtxo } from "../types/bitcoin.types";
+import { DEFAULT_ACCOUNT_NAME } from "../types/shared.constants";
+import { getAccountData } from "./accounts";
+import {
+  makeCommitTransaction,
+  makeRevealTransaction,
+  SIGNET,
+} from "./bitcoin.helpers";
+import { handleError } from "./handleError";
+
+export interface BitcoinKeyPair {
+  address: string;
+  key: ECPairInterface;
+}
+
+export interface TransactionInfo {
+  amount?: string;
+  commitFee: number;
+  encodedMessage?: string;
+  encodingFormat: string;
+  gateway: string;
+  network: string;
+  operation: string;
+  rawInscriptionData: string;
+  receiver?: string;
+  revealFee: number;
+  revertAddress?: string;
+  sender: string;
+  totalFee: number;
+}
+
+export const setupBitcoinKeyPair = (
+  privateKey: string | undefined,
+  name: string
+): BitcoinKeyPair => {
+  const keyPrivateKey =
+    privateKey ||
+    getAccountData<BitcoinAccountData>("bitcoin", name)?.privateKey;
+
+  if (!keyPrivateKey) {
+    const errorMessage = handleError({
+      context: "Failed to retrieve private key",
+      error: new Error("Private key not found"),
+      shouldThrow: false,
+    });
+
+    throw new Error(errorMessage);
+  }
+
+  // Initialize Bitcoin library with ECC implementation
+  bitcoin.initEccLib(ecc);
+
+  // Set up Bitcoin key pair
+  const ECPair = ECPairFactory(ecc);
+  const key = ECPair.fromPrivateKey(Buffer.from(keyPrivateKey, "hex"), {
+    network: SIGNET,
+  });
+
+  const { address } = bitcoin.payments.p2wpkh({
+    network: SIGNET,
+    pubkey: key.publicKey,
+  });
+
+  return { address: address!, key };
+};
+
+export const fetchUtxos = async (
+  address: string,
+  api: string
+): Promise<BtcUtxo[]> => {
+  return (await axios.get<BtcUtxo[]>(`${api}/address/${address}/utxo`)).data;
+};
+
+export const displayAndConfirmTransaction = async (info: TransactionInfo) => {
+  const notApplicable = "encoded in raw inscription data";
+
+  console.log(`
+Network: ${info.network}
+${info.amount ? `Amount: ${info.amount} BTC` : ""}
+Gateway: ${info.gateway}
+Sender: ${info.sender}
+Universal Contract: ${info.receiver || notApplicable}
+Revert Address: ${info.revertAddress || notApplicable}
+Operation: ${info.operation}
+${info.encodedMessage ? `Encoded Message: ${info.encodedMessage}` : ""}
+Encoding Format: ${info.encodingFormat}
+Raw Inscription Data: ${info.rawInscriptionData}
+Fees:
+  - Commit Fee: ${info.commitFee} sat
+  - Reveal Fee: ${info.revealFee} sat
+  - Total Fee: ${info.totalFee} sat (${(info.totalFee / 100000000).toFixed(
+    8
+  )} BTC)
+`);
+  await confirm({ message: "Proceed?" }, { clearPromptOnDone: true });
+};
+
+export const broadcastTransaction = async (
+  txHex: string,
+  api: string
+): Promise<string> => {
+  return (
+    await axios.post<string>(`${api}/tx`, txHex, {
+      headers: { "Content-Type": "text/plain" },
+    })
+  ).data;
+};
+
+export const createAndBroadcastTransactions = async (
+  key: ECPairInterface,
+  utxos: BtcUtxo[],
+  address: string,
+  data: Buffer,
+  api: string,
+  amount: number,
+  gateway: string
+) => {
+  // Create and broadcast commit transaction
+  const commit = await makeCommitTransaction(
+    key,
+    utxos,
+    address,
+    data,
+    api,
+    amount
+  );
+
+  const commitTxid = await broadcastTransaction(commit.txHex, api);
+  console.log("Commit TXID:", commitTxid);
+
+  // Create and broadcast reveal transaction
+  const revealHex = makeRevealTransaction(
+    commitTxid,
+    0,
+    amount,
+    gateway,
+    BITCOIN_FEES.DEFAULT_REVEAL_FEE_RATE,
+    {
+      controlBlock: commit.controlBlock,
+      internalKey: commit.internalKey,
+      leafScript: commit.leafScript,
+    },
+    key
+  );
+  const revealTxid = await broadcastTransaction(revealHex, api);
+
+  return { commitTxid, revealTxid };
path: utils/bitcoin.command.helpers.ts
user: login: fadeev
body: We can look into this. Perhaps adding some additional logic to verify that the tx has been broadcasted. We can't wait for N confirmations, because that would take a long time. I think users should use CCTX tracking command for that.
created_at: 2025-05-15T07:54:10Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-08T06:57:34Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

The changes introduce new Bitcoin CLI commands for deposit and contract call operations on ZetaChain, enhance input validation and flexibility for inscription data, implement detailed fee calculations, and update transaction construction logic to enforce minimum amounts and chunking of inscription data. Additional constants and schemas support these new features, and a new test ensures encoding robustness with empty payloads. Variable and schema property names related to Bitcoin private keys were standardized.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `packages/commands/src/bitcoin/call.ts` | Added new `callCommand` CLI command to call contracts on ZetaChain via Bitcoin Signet, supporting ABI or raw data inscription, UTXO management, fee calculation, and user confirmation. |
| `packages/commands/src/bitcoin/deposit.ts` | Added new `depositCommand` CLI command to deposit BTC to ZetaChain addresses using Signet, supporting receiver or raw data inscription, fee calculation, UTXO management, and user confirmation. |
| `packages/commands/src/bitcoin/depositAndCall.ts` | Enhanced `depositAndCallCommand` to allow mutually exclusive ABI or raw data input, made several options optional, added dynamic fee calculation and detailed transaction info display, and added mutually exclusive authentication options (`--private-key` vs `--name`). |
| `packages/commands/src/bitcoin/encode.ts` | Replaced manual options interface with Zod schema, made `types` and `values` optional with default empty arrays, and handled empty payloads in encoding logic. |
| `packages/commands/src/bitcoin/index.ts` | Registered new `depositCommand` and `callCommand` subcommands within the main Bitcoin command group. |
| `test/bitcoinEncode.test.ts` | Added a test case verifying encoding with an empty payload, ensuring output contains the receiver address and is non-empty. |
| `types/bitcoin.constants.ts` | Introduced new constants for estimated reveal fee, minimum commit amount, and minimum total fee within exported objects. |
| `types/bitcoin.types.ts` | Updated and relaxed validation schemas: made several fields optional, added new schemas for deposit and call commands, and refined array length checks for ABI parameters. |
| `utils/bitcoin.helpers.ts` | Updated commit/reveal transaction creation to enforce minimum amounts, chunk inscription data, and clarify fee/dust checks. Added `calculateFees` utility for commit/reveal/total fee calculation. |
| `packages/commands/src/accounts/show.ts` | Changed property used for private key bytes from `privateKeyBytes` to `privateKey` in Bitcoin account details. |
| `types/accounts.types.ts` | Renamed `privateKeyBytes` property to `privateKey` in Bitcoin account data schema. |
| `utils/accounts.ts` | Renamed variable and returned property from `privateKeyBytes` to `privateKey` in Bitcoin account creation logic. |
| `test/addressResolver.test.ts` | Updated test mocks to rename `privateKeyBytes` to `privateKey` in Bitcoin account data for address resolution tests. |
| `types/shared.schema.ts` | Added `hexStringSchema` and two refinement rules to enforce exclusivity and length equality between ABI parameters and raw data inputs. |
| `utils/bitcoin.command.helpers.ts` | Added helper functions for Bitcoin key setup, UTXO fetching, transaction info display and confirmation, transaction broadcasting, and common CLI options; includes amount parsing and error handling. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant CLI_Command
    participant BitcoinJS
    participant BTC_API

    User->>CLI_Command: Provide options (privateKey, amount, gateway, etc.)
    CLI_Command->>BitcoinJS: Initialize ECC, derive key/address
    CLI_Command->>BTC_API: Fetch UTXOs
    CLI_Command->>CLI_Command: Encode inscription data (ABI or raw)
    CLI_Command->>CLI_Command: Calculate commit/reveal fees
    CLI_Command->>User: Display transaction info, request confirmation
    User-->>CLI_Command: Confirm
    CLI_Command->>BitcoinJS: Create commit transaction
    CLI_Command->>BTC_API: Broadcast commit transaction
    CLI_Command->>BitcoinJS: Create reveal transaction (after commit)
    CLI_Command->>BTC_API: Broadcast reveal transaction
    CLI_Command->>User: Output commit/reveal transaction IDs
```

```mermaid
sequenceDiagram
    participant CLI_Command
    participant Zod_Schema

    CLI_Command->>Zod_Schema: Validate input options
    alt ABI input
        CLI_Command->>CLI_Command: Encode ABI types/values to data
    else Raw data input
        CLI_Command->>CLI_Command: Use raw hex data
    end
    CLI_Command->>CLI_Command: Proceed with transaction creation
```

</details>

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!ANNOUNCEMENT]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!ANNOUNCEMENT]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 27c2b2b2546788829b5635eb2175e177e56700d1 and 8d1f6f65958b003e9d0e2b011d4df05e91c01064.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `utils/bitcoin.command.helpers.ts` (1 hunks)
* `utils/bitcoin.helpers.ts` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* utils/bitcoin.command.helpers.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>utils/bitcoin.helpers.ts (1)</summary><blockquote>

<details>
<summary>types/bitcoin.constants.ts (3)</summary>

* `BITCOIN_LIMITS` (40-48)
* `BITCOIN_FEES` (53-57)
* `BITCOIN_TX` (28-35)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>utils/bitcoin.helpers.ts (6)</summary>

`105-108`: **Smart adjustment to ensure sufficient transaction value**

This change ensures that the effective amount is always enough to cover both the minimum commit amount and the estimated reveal fee, which is a robust approach to prevent insufficient funds in subsequent transactions.

---

`117-120`: **Consistent application of the effective amount**

Good update to use the new effectiveAmount in the UTXO selection logic and error checking to ensure enough funds are available for both transactions.

---

`129-139`: **Excellent chunking implementation for large data**

The chunking logic correctly handles data larger than the 520-byte script element limit, enabling support for larger payloads in Bitcoin transactions as intended in the PR objectives.

---

`154-154`: **Consistent use of effective amount in output**

Good update to use the effectiveAmount in the output, ensuring consistency with the earlier calculations.

---

`230-238`: **Improved error handling for output value**

The enhanced error checking with a detailed error message makes troubleshooting easier for insufficient value scenarios. This is a significant improvement over the previous implementation.

---

`246-264`: **Well-structured fee calculation utility**

The new `calculateFees` function provides a clean interface for fee estimation, supporting the PR objectives of improving Bitcoin transaction handling. The calculation takes into account the data size, which is essential for accurate fee estimation with variable-sized inscription data.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


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

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=zeta-chain/toolkit&utm_content=325):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: fadeev
created_at: 2025-05-08T14:49:09Z
body: @hernan-clich ready for review.
author_association: MEMBER
user: login: fadeev
created_at: 2025-05-11T13:26:07Z
body: Added `--name` to get private key from `~/.zetachain/keys/bitcoin/*` instead of inline private key.
author_association: MEMBER