Skip to content

adds mergeCheckpoints and mergeTxs utils#217

Merged
bordalix merged 4 commits intomasterfrom
merge_txs_methods
Oct 31, 2025
Merged

adds mergeCheckpoints and mergeTxs utils#217
bordalix merged 4 commits intomasterfrom
merge_txs_methods

Conversation

@bordalix
Copy link
Contributor

@bordalix bordalix commented Oct 31, 2025

@louisinger please review

Summary by CodeRabbit

  • New Features
    • Added two public API functions to merge signed transaction components into local transactions: one for combining tap-script signatures per input and one for merging checkpoint data, enabling smoother integration of externally signed transaction data.

@bordalix bordalix requested a review from louisinger October 31, 2025 13:44
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Warning

Rate limit exceeded

@bordalix has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e44237d and da3ce19.

📒 Files selected for processing (2)
  • src/index.ts (2 hunks)
  • src/utils/arkTransaction.ts (1 hunks)

Walkthrough

Added a new utility function combineTapscriptSigs that merges per-input tapScriptSig values from a signed Transaction into an original Transaction, and exported it (alongside existing mergeCheckpoints) from the module public API via src/index.ts.

Changes

Cohort / File(s) Summary
Ark transaction utilities
src/utils/arkTransaction.ts
Added export function combineTapscriptSigs(signedTx: Transaction, originalTx: Transaction): Transaction which iterates inputs and concatenates tapScriptSig from signedTx into corresponding inputs of originalTx, throwing if an original input lacks tapScriptSig. Returns the modified originalTx.
Public API exports
src/index.ts
Added public exports for mergeCheckpoints and combineTapscriptSigs (imported from ./utils/arkTransaction) and included them in the exported value/type surface.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ArkUtils as arkTransaction
  Note right of ArkUtils #D6EFFF: combineTapscriptSigs
  Caller->>ArkUtils: combineTapscriptSigs(signedTx, originalTx)
  ArkUtils->>ArkUtils: for each input i
  ArkUtils->>ArkUtils: if originalTx.inputs[i].tapScriptSig missing -> throw
  ArkUtils->>ArkUtils: originalTx.inputs[i].tapScriptSig += signedTx.inputs[i].tapScriptSig
  ArkUtils-->>Caller: return modified originalTx
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Review focus: correctness of per-input indexing, concatenation semantics, and error conditions when tapScriptSig is missing.
  • Files to inspect: src/utils/arkTransaction.ts, src/index.ts.

Suggested reviewers

  • altafan

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title states "adds mergeCheckpoints and mergeTxs utils," but the actual changeset adds mergeCheckpoints (only as a re-export from ./utils/arkTransaction without visible implementation) and combineTapscriptSigs (not mergeTxs). The function name in the title does not match the actual implementation—the new function is specifically for combining tapscript signatures across transaction inputs, not a general transaction merge utility. This discrepancy between the stated function name ("mergeTxs") and the actual implementation ("combineTapscriptSigs") makes the title misleading about what is being added. Update the pull request title to accurately reflect the functions being added. A more accurate title would be "adds mergeCheckpoints and combineTapscriptSigs utils" to correctly describe the new exports and implementations in the changeset.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dbae68 and fa305ea.

📒 Files selected for processing (2)
  • src/index.ts (2 hunks)
  • src/utils/arkTransaction.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/arkTransaction.ts (1)
src/utils/transaction.ts (1)
  • Transaction (9-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (1)
src/index.ts (1)

91-92: LGTM! Public API exports are correct.

The new utility functions are properly imported and exported, making them available as part of the public API. However, ensure the critical issues identified in src/utils/arkTransaction.ts are addressed before merging.

Also applies to: 191-192

Comment on lines 342 to 361
for (let i = 0; i < originalCheckpointTxs.length; i++) {
const myCheckpointTx = originalCheckpointTxs[i];
const signedCheckpointTx = signedCheckpointTxs.find(
(_) => _.id === myCheckpointTx.id
);
if (!signedCheckpointTx) {
throw new Error("Signed checkpoint not found");
}
// for every input, concatenate its signatures with the signature from the server
for (let j = 0; j < myCheckpointTx.inputsLength; j++) {
const input = myCheckpointTx.getInput(j);
const inputFromServer = signedCheckpointTx.getInput(j);
if (!inputFromServer.tapScriptSig)
throw new Error("No tapScriptSig");
myCheckpointTx.updateInput(i, {
tapScriptSig: input.tapScriptSig?.concat(
inputFromServer.tapScriptSig
),
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Wrong index used in updateInput call.

Line 356 uses the outer loop variable i (checkpoint index) instead of the inner loop variable j (input index) when calling updateInput. This will update the wrong input within the checkpoint transaction.

Apply this diff to fix the index bug:

-            myCheckpointTx.updateInput(i, {
+            myCheckpointTx.updateInput(j, {
                 tapScriptSig: input.tapScriptSig?.concat(
                     inputFromServer.tapScriptSig
                 ),
             });

Additionally, the optional chaining on line 357 (input.tapScriptSig?.concat(...)) can silently result in undefined if input.tapScriptSig is missing. Consider adding an explicit check:

         for (let j = 0; j < myCheckpointTx.inputsLength; j++) {
             const input = myCheckpointTx.getInput(j);
             const inputFromServer = signedCheckpointTx.getInput(j);
+            if (!input.tapScriptSig)
+                throw new Error(`Input ${j} in checkpoint ${i} missing tapScriptSig`);
             if (!inputFromServer.tapScriptSig)
-                throw new Error("No tapScriptSig");
+                throw new Error(`Input ${j} in signed checkpoint ${i} missing tapScriptSig`);
-            myCheckpointTx.updateInput(j, {
-                tapScriptSig: input.tapScriptSig?.concat(
+            myCheckpointTx.updateInput(j, {
+                tapScriptSig: input.tapScriptSig.concat(
                     inputFromServer.tapScriptSig
                 ),
             });
📝 Committable suggestion

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

Suggested change
for (let i = 0; i < originalCheckpointTxs.length; i++) {
const myCheckpointTx = originalCheckpointTxs[i];
const signedCheckpointTx = signedCheckpointTxs.find(
(_) => _.id === myCheckpointTx.id
);
if (!signedCheckpointTx) {
throw new Error("Signed checkpoint not found");
}
// for every input, concatenate its signatures with the signature from the server
for (let j = 0; j < myCheckpointTx.inputsLength; j++) {
const input = myCheckpointTx.getInput(j);
const inputFromServer = signedCheckpointTx.getInput(j);
if (!inputFromServer.tapScriptSig)
throw new Error("No tapScriptSig");
myCheckpointTx.updateInput(i, {
tapScriptSig: input.tapScriptSig?.concat(
inputFromServer.tapScriptSig
),
});
}
for (let i = 0; i < originalCheckpointTxs.length; i++) {
const myCheckpointTx = originalCheckpointTxs[i];
const signedCheckpointTx = signedCheckpointTxs.find(
(_) => _.id === myCheckpointTx.id
);
if (!signedCheckpointTx) {
throw new Error("Signed checkpoint not found");
}
// for every input, concatenate its signatures with the signature from the server
for (let j = 0; j < myCheckpointTx.inputsLength; j++) {
const input = myCheckpointTx.getInput(j);
const inputFromServer = signedCheckpointTx.getInput(j);
if (!input.tapScriptSig)
throw new Error(`Input ${j} in checkpoint ${i} missing tapScriptSig`);
if (!inputFromServer.tapScriptSig)
throw new Error(`Input ${j} in signed checkpoint ${i} missing tapScriptSig`);
myCheckpointTx.updateInput(j, {
tapScriptSig: input.tapScriptSig.concat(
inputFromServer.tapScriptSig
),
});
}
🤖 Prompt for AI Agents
In src/utils/arkTransaction.ts around lines 342 to 361, the inner-loop updates
use the outer loop index `i` instead of the input index `j` which writes the
wrong input; change the call to updateInput to use `j` as the input index, and
guard against a missing local tapScriptSig by explicitly checking that
input.tapScriptSig exists (or treating it as an empty array) before
concatenation — if missing, either throw a clear error or initialize to an empty
signature array, then concatenate with inputFromServer.tapScriptSig and pass the
result to updateInput.

Comment on lines 371 to 383
export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
for (let i = 0; i < signedTx.inputsLength; i++) {
const input = originalTx.getInput(i);
const inputFromServer = signedTx.getInput(i);
if (!input.tapScriptSig) throw new Error("No tapScriptSig");
originalTx.updateInput(i, {
tapScriptSig: input.tapScriptSig?.concat(
inputFromServer.tapScriptSig!
),
});
}
return originalTx;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation for input count and missing tapScriptSig from server.

The function has several issues:

  1. No validation that originalTx and signedTx have the same number of inputs. If they differ, this could cause out-of-bounds access or miss inputs.
  2. Line 378 uses non-null assertion on inputFromServer.tapScriptSig without checking if it exists.
  3. Line 377 uses optional chaining after already validating input.tapScriptSig exists on line 375, which is redundant.

Apply this diff to add proper validation:

 export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
+    if (signedTx.inputsLength !== originalTx.inputsLength) {
+        throw new Error(
+            `Input count mismatch: signed tx has ${signedTx.inputsLength} inputs, original tx has ${originalTx.inputsLength} inputs`
+        );
+    }
     for (let i = 0; i < signedTx.inputsLength; i++) {
         const input = originalTx.getInput(i);
         const inputFromServer = signedTx.getInput(i);
-        if (!input.tapScriptSig) throw new Error("No tapScriptSig");
+        if (!input.tapScriptSig)
+            throw new Error(`Input ${i} in original tx missing tapScriptSig`);
+        if (!inputFromServer.tapScriptSig)
+            throw new Error(`Input ${i} in signed tx missing tapScriptSig`);
         originalTx.updateInput(i, {
-            tapScriptSig: input.tapScriptSig?.concat(
-                inputFromServer.tapScriptSig!
+            tapScriptSig: input.tapScriptSig.concat(
+                inputFromServer.tapScriptSig
             ),
         });
     }
📝 Committable suggestion

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

Suggested change
export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
for (let i = 0; i < signedTx.inputsLength; i++) {
const input = originalTx.getInput(i);
const inputFromServer = signedTx.getInput(i);
if (!input.tapScriptSig) throw new Error("No tapScriptSig");
originalTx.updateInput(i, {
tapScriptSig: input.tapScriptSig?.concat(
inputFromServer.tapScriptSig!
),
});
}
return originalTx;
}
export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
if (signedTx.inputsLength !== originalTx.inputsLength) {
throw new Error(
`Input count mismatch: signed tx has ${signedTx.inputsLength} inputs, original tx has ${originalTx.inputsLength} inputs`
);
}
for (let i = 0; i < signedTx.inputsLength; i++) {
const input = originalTx.getInput(i);
const inputFromServer = signedTx.getInput(i);
if (!input.tapScriptSig)
throw new Error(`Input ${i} in original tx missing tapScriptSig`);
if (!inputFromServer.tapScriptSig)
throw new Error(`Input ${i} in signed tx missing tapScriptSig`);
originalTx.updateInput(i, {
tapScriptSig: input.tapScriptSig.concat(
inputFromServer.tapScriptSig
),
});
}
return originalTx;
}
🤖 Prompt for AI Agents
In src/utils/arkTransaction.ts around lines 371 to 383, validate that
signedTx.inputsLength === originalTx.inputsLength before the loop and throw a
descriptive error if they differ; inside the loop, remove the redundant optional
chaining on input.tapScriptSig (use the already-checked value) and explicitly
check that inputFromServer.tapScriptSig is present before concatenating,
throwing a clear error if the server is missing tapScriptSig for that index;
ensure error messages include the input index for easier debugging.

* @param signedTx signed transaction
* @param originalTx original transaction
*/
export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
export function mergeTxs(signedTx: Transaction, originalTx: Transaction) {
export function combineTapscriptSigs(signedTx: Transaction, originalTx: Transaction) {

"combine" is the term used in PSBT BIP so maybe better to reuse it ?
also it doesn't merge all fields but only tapscript sigs ?

* @param signedCheckpointTxs signed checkpoint transactions
* @param originalCheckpointTxs original checkpoint transactions
*/
export function mergeCheckpoints(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
export function mergeCheckpoints(
export function mergeCheckpoints(

do we really need this function ? in the end it's applying the mergeTx one on an array

@bordalix bordalix merged commit 95bc54d into master Oct 31, 2025
2 checks passed
@louisinger louisinger deleted the merge_txs_methods branch December 29, 2025 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants