Skip to content

feat: release utxos after user rejection or error#162

Open
raul-oliveira wants to merge 1 commit intomasterfrom
raul-oliveira/feat/utxo-unlock
Open

feat: release utxos after user rejection or error#162
raul-oliveira wants to merge 1 commit intomasterfrom
raul-oliveira/feat/utxo-unlock

Conversation

@raul-oliveira
Copy link
Copy Markdown
Contributor

@raul-oliveira raul-oliveira commented Mar 31, 2026

Requires HathorNetwork/hathor-wallet-lib#1050

Motivation

The nano contract methods in rpc-lib causes utxos to be indefinitely locked when user rejects a prompt or an error is thrown.

Acceptance Criteria

  • call release utxos after a prompt rejection
  • call release utxos when an error is thrown

Checklist

  • If you are requesting a merge into master, confirm this code is production-ready and can be included in future releases as soon as it gets merged
  • Make sure either the unit tests and/or the QA tests are capable of testing the new features
  • Make sure you do not include new dependencies in the project unless strictly necessary and do not include dev-dependencies as production ones. More dependencies increase the possibility of one of them being hijacked and affecting us.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced cleanup of reserved transaction resources when users reject transaction confirmations or PIN prompts, and when transaction execution fails, preventing resources from becoming locked during operation cancellations across multiple transaction types.
  • Tests

    • Added comprehensive test coverage verifying proper resource cleanup in cancellation and failure scenarios for nano contract and standard transactions.

@raul-oliveira raul-oliveira self-assigned this Mar 31, 2026
@raul-oliveira raul-oliveira moved this from Todo to In Progress (Done) in Hathor Network Mar 31, 2026
@raul-oliveira raul-oliveira moved this from In Progress (Done) to In Progress (WIP) in Hathor Network Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR adds UTXO cleanup functionality (releaseUtxos()) across three RPC transaction methods to ensure pre-allocated UTXOs are released when transaction processing fails due to user rejection or execution errors.

Changes

Cohort / File(s) Summary
Test Files - sendTransaction
packages/hathor-rpc-handler/__tests__/rpcMethods/sendTransaction.test.ts
Extended wallet mocks with releaseUtxos method and added three new test cases verifying releaseUtxos() is called on confirmation rejection, PIN rejection, and execution failure.
Test Files - Nano Contract Methods
packages/hathor-rpc-handler/__tests__/rpcMethods/createNanoContractCreateTokenTx.test.ts, packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts
Extended createMockSendTransactionResult and nano contract transaction mocks with releaseUtxos method; added test cases asserting cleanup is performed on confirmation rejection, PIN rejection, and transaction execution failures.
Implementation - Interface & sendTransaction
packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts
Added releaseUtxos(): Promise<void> method to ISendTransactionObject interface; added cleanup calls before throwing PromptRejectedError on user rejections and in catch block for execution failures.
Implementation - Nano Contract Methods
packages/hathor-rpc-handler/src/rpcMethods/createNanoContractCreateTokenTx.ts, packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts
Added await releaseUtxos() calls before throwing PromptRejectedError on confirmation and PIN rejections; added best-effort cleanup in catch blocks before rethrowing SendNanoContractTxError.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • pedroferreira1
  • andreabadesso

Poem

🐰 UTXOs hopping free when things go wrong,
A cleanup dance, methodical and strong!
When users reject or errors take the stage,
Release them fast—a rabbit's finest cage! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: releasing UTXOs after user rejection or error, which is the core objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch raul-oliveira/feat/utxo-unlock

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.

@raul-oliveira raul-oliveira removed the status in Hathor Network Mar 31, 2026
@raul-oliveira raul-oliveira moved this to In Progress (WIP) in Hathor Network Mar 31, 2026
Copy link
Copy Markdown

@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: 1

Caution

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

⚠️ Outside diff range comments (4)
packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts (2)

176-196: ⚠️ Potential issue | 🟠 Major

Cleanup still misses pre-signing failures after the lock is acquired.

Once the pre-build succeeds, releaseUtxos() is only reached for explicit rejections and the later sign/send try/catch. If triggerHandler(...) rejects, or the confirmation payload fails safeParse(...) on Lines 179-181, or the PIN prompt call on Line 193 throws, the reserved UTXOs still leak.

Based on learnings, the pre-build createNanoContractTransaction(..., signTx: false) intentionally locks UTXOs from that point onward.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts` around
lines 176 - 196, After pre-build (the call that locks UTXOs via
createNanoContractTransaction(..., signTx: false)), ensure
preBuildSendTx.releaseUtxos() is always executed by wrapping the sequence that
calls triggerHandler(sendNanoContractTxPrompt...), the safeParse using
sendNanoContractTxConfirmationResponseSchema, the triggerHandler(pinPrompt...)
and subsequent sign/send logic in a try/finally (or ensure a finally-like
cleanup) so any thrown errors (handler rejection, parse failure, PIN prompt
rejection) will call preBuildSendTx.releaseUtxos(); locate and modify the block
that begins after createNanoContractTransaction to add the try { ... } finally {
await preBuildSendTx.releaseUtxos(); } pattern and remove existing ad-hoc
release calls where duplicated.

187-237: ⚠️ Potential issue | 🔴 Critical

Add type bridge for preBuildSendTx — compile blocker.

The code calls preBuildSendTx.releaseUtxos() at lines 187, 196, and 237 without a type declaration. TypeScript will report TS2339: Property 'releaseUtxos' does not exist on type 'SendTransactionWalletService'. Add the ISendTransactionObject interface from sendTransaction.ts (lines 33–39) to this file before merging.

Type bridge pattern from sendTransaction.ts
interface ISendTransactionObject {
  prepareTx(): Promise<Transaction>;
  signTx(pin: string): Promise<Transaction>;
  runFromMining(): Promise<Transaction>;
  releaseUtxos(): Promise<void>;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts` around
lines 187 - 237, Declare and use the ISendTransactionObject type bridge in this
file so preBuildSendTx has the releaseUtxos signature: add the
ISendTransactionObject interface (prepareTx, signTx, runFromMining, releaseUtxos
returning Promise<void>) from sendTransaction.ts to this file and annotate the
preBuildSendTx variable/parameter with that interface (reference symbol:
preBuildSendTx) or import it if you move the interface to a shared module; this
ensures calls like preBuildSendTx.releaseUtxos() compile.
packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts (1)

186-231: ⚠️ Potential issue | 🟠 Major

The cleanup boundary still starts too late.

After prepareTx() succeeds, any exception before the inner try on Line 208 still bypasses releaseUtxos()—for example fetchTokenDetails(), fee validation, either promptHandler(...) call rejecting, or a malformed prompt payload. Those paths still leave the prepared inputs locked, which is the exact failure mode this PR is trying to eliminate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts` around lines
186 - 231, The cleanup (sendTransactionObject.releaseUtxos()) must run for any
error that occurs after prepareTx() succeeds; wrap the whole logic that follows
prepareTx() (everything from prompt creation/validation, both promptHandler
calls, fee/token checks, signTx, runFromMining, and the inner try/catch) in a
try/finally (or ensure a finally block) that always calls
sendTransactionObject.releaseUtxos() (best-effort) so prepared UTXOs are
released on any exception, and keep the existing inner try/catch for handling
send errors; locate prepareTx(), sendTransactionObject.releaseUtxos(),
promptHandler(...) and sendTransactionObject.signTx()/runFromMining() to apply
the finally around the correct region.
packages/hathor-rpc-handler/src/rpcMethods/createNanoContractCreateTokenTx.ts (1)

155-179: ⚠️ Potential issue | 🟠 Major

Errors between pre-build and the post-PIN try still leak the lock.

This only unlocks on explicit rejection and inside the later try/catch. If the confirmation prompt throws, the response fails safeParse(...), or the PIN prompt call itself throws, the pre-built UTXOs still remain locked.

Based on learnings, the signTx: false pre-build step is intentionally allowed to lock UTXOs from the pre-build phase onward.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/hathor-rpc-handler/src/rpcMethods/createNanoContractCreateTokenTx.ts`
around lines 155 - 179, The code currently can leak the pre-build UTXO lock if
any error occurs between pre-build and the post-PIN try/catch (e.g.,
promptHandler throws, safeParse fails, or PIN prompt throws); ensure
preBuildResult.releaseUtxos() is always called on any early exit. Fix by
surrounding the confirmation + PIN flow (the promptHandler call that produces
rawResponse, the safeParse using
createNanoContractCreateTokenTxConfirmationResponseSchema, and the PIN prompt
creation/await) with a try/finally (or add catch blocks) that calls
preBuildResult.releaseUtxos() before rethrowing; alternatively, call
preBuildResult.releaseUtxos() right before every throw (including after
safeParse failure, on promptHandler errors, and before throwing when
pinResponse.data.accepted is false) so the lock is released in all error paths.
Reference symbols: preBuildResult.releaseUtxos(), promptHandler(...),
createNanoContractCreateTokenTxConfirmationResponseSchema.safeParse(...),
PinConfirmationPrompt and pinResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts`:
- Around line 423-509: The tests in sendNanoContractTx.test.ts currently use
generic rejects.toThrow() for the two rejection cases; update both assertions to
specifically expect a PromptRejectedError to lock the contract (replace await
expect(sendNanoContractTx(...)).rejects.toThrow() with await
expect(...).rejects.toThrow(PromptRejectedError) or equivalent instance check).
Ensure the test file imports PromptRejectedError (or references the exact
exported symbol) and keep the existing mockReleaseUtxos assertions unchanged.

---

Outside diff comments:
In
`@packages/hathor-rpc-handler/src/rpcMethods/createNanoContractCreateTokenTx.ts`:
- Around line 155-179: The code currently can leak the pre-build UTXO lock if
any error occurs between pre-build and the post-PIN try/catch (e.g.,
promptHandler throws, safeParse fails, or PIN prompt throws); ensure
preBuildResult.releaseUtxos() is always called on any early exit. Fix by
surrounding the confirmation + PIN flow (the promptHandler call that produces
rawResponse, the safeParse using
createNanoContractCreateTokenTxConfirmationResponseSchema, and the PIN prompt
creation/await) with a try/finally (or add catch blocks) that calls
preBuildResult.releaseUtxos() before rethrowing; alternatively, call
preBuildResult.releaseUtxos() right before every throw (including after
safeParse failure, on promptHandler errors, and before throwing when
pinResponse.data.accepted is false) so the lock is released in all error paths.
Reference symbols: preBuildResult.releaseUtxos(), promptHandler(...),
createNanoContractCreateTokenTxConfirmationResponseSchema.safeParse(...),
PinConfirmationPrompt and pinResponse.

In `@packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts`:
- Around line 176-196: After pre-build (the call that locks UTXOs via
createNanoContractTransaction(..., signTx: false)), ensure
preBuildSendTx.releaseUtxos() is always executed by wrapping the sequence that
calls triggerHandler(sendNanoContractTxPrompt...), the safeParse using
sendNanoContractTxConfirmationResponseSchema, the triggerHandler(pinPrompt...)
and subsequent sign/send logic in a try/finally (or ensure a finally-like
cleanup) so any thrown errors (handler rejection, parse failure, PIN prompt
rejection) will call preBuildSendTx.releaseUtxos(); locate and modify the block
that begins after createNanoContractTransaction to add the try { ... } finally {
await preBuildSendTx.releaseUtxos(); } pattern and remove existing ad-hoc
release calls where duplicated.
- Around line 187-237: Declare and use the ISendTransactionObject type bridge in
this file so preBuildSendTx has the releaseUtxos signature: add the
ISendTransactionObject interface (prepareTx, signTx, runFromMining, releaseUtxos
returning Promise<void>) from sendTransaction.ts to this file and annotate the
preBuildSendTx variable/parameter with that interface (reference symbol:
preBuildSendTx) or import it if you move the interface to a shared module; this
ensures calls like preBuildSendTx.releaseUtxos() compile.

In `@packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts`:
- Around line 186-231: The cleanup (sendTransactionObject.releaseUtxos()) must
run for any error that occurs after prepareTx() succeeds; wrap the whole logic
that follows prepareTx() (everything from prompt creation/validation, both
promptHandler calls, fee/token checks, signTx, runFromMining, and the inner
try/catch) in a try/finally (or ensure a finally block) that always calls
sendTransactionObject.releaseUtxos() (best-effort) so prepared UTXOs are
released on any exception, and keep the existing inner try/catch for handling
send errors; locate prepareTx(), sendTransactionObject.releaseUtxos(),
promptHandler(...) and sendTransactionObject.signTx()/runFromMining() to apply
the finally around the correct region.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5b84f95-f872-4d9b-91b1-6f9222ab09b4

📥 Commits

Reviewing files that changed from the base of the PR and between 9cb53f8 and a2506b1.

📒 Files selected for processing (6)
  • packages/hathor-rpc-handler/__tests__/rpcMethods/createNanoContractCreateTokenTx.test.ts
  • packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts
  • packages/hathor-rpc-handler/__tests__/rpcMethods/sendTransaction.test.ts
  • packages/hathor-rpc-handler/src/rpcMethods/createNanoContractCreateTokenTx.ts
  • packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts
  • packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts

Comment on lines +423 to +509
it('should call releaseUtxos when user rejects the confirmation prompt', async () => {
const address = 'address123';

const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);

promptHandler.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: false,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
});

await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();

expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});

it('should call releaseUtxos when user rejects the PIN prompt', async () => {
const address = 'address123';

const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);

promptHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: true,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
data: {
accepted: false,
pinCode: '',
},
});

await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();

expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert PromptRejectedError in the new rejection cleanup tests.

Both new rejection cases use rejects.toThrow(), so they would still pass if releaseUtxos() or prompt parsing started throwing some other error. These should lock in the intended contract: user rejection still surfaces PromptRejectedError.

🧪 Tighten the assertions
-import { SendNanoContractTxError, InvalidParamsError } from '../../src/errors';
+import { SendNanoContractTxError, InvalidParamsError, PromptRejectedError } from '../../src/errors';
...
-    await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();
+    await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow(PromptRejectedError);
...
-    await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();
+    await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow(PromptRejectedError);
📝 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
it('should call releaseUtxos when user rejects the confirmation prompt', async () => {
const address = 'address123';
const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);
promptHandler.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: false,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
});
await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();
expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});
it('should call releaseUtxos when user rejects the PIN prompt', async () => {
const address = 'address123';
const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);
promptHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: true,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
data: {
accepted: false,
pinCode: '',
},
});
await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow();
expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});
it('should call releaseUtxos when user rejects the confirmation prompt', async () => {
const address = 'address123';
const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);
promptHandler.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: false,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
});
await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow(PromptRejectedError);
expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});
it('should call releaseUtxos when user rejects the PIN prompt', async () => {
const address = 'address123';
const mockTransaction = {
getFeeHeader: jest.fn().mockReturnValue({ entries: [] }),
getNanoHeaders: jest.fn().mockReturnValue([{ address: null, seqnum: 0 }]),
prepareToSend: jest.fn(),
toHex: jest.fn().mockReturnValue('tx-hex'),
};
const mockReleaseUtxos = jest.fn().mockResolvedValue(undefined);
const mockSendTx = {
transaction: mockTransaction,
runFromMining: jest.fn().mockResolvedValue({ tx_id: 'mock-tx-id' }),
releaseUtxos: mockReleaseUtxos,
};
(wallet.createNanoContractTransaction as jest.Mock).mockResolvedValue(mockSendTx);
promptHandler
.mockResolvedValueOnce({
type: TriggerResponseTypes.SendNanoContractTxConfirmationResponse,
data: {
accepted: true,
nc: {
caller: address,
method: rpcRequest.params.method,
blueprintId: rpcRequest.params.blueprint_id,
ncId: rpcRequest.params.nc_id,
args: rpcRequest.params.args,
parsedArgs: [],
actions: [],
pushTx: true,
fee: 0n,
},
},
})
.mockResolvedValueOnce({
type: TriggerResponseTypes.PinRequestResponse,
data: {
accepted: false,
pinCode: '',
},
});
await expect(sendNanoContractTx(rpcRequest, wallet, {}, promptHandler)).rejects.toThrow(PromptRejectedError);
expect(mockReleaseUtxos).toHaveBeenCalledTimes(1);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts`
around lines 423 - 509, The tests in sendNanoContractTx.test.ts currently use
generic rejects.toThrow() for the two rejection cases; update both assertions to
specifically expect a PromptRejectedError to lock the contract (replace await
expect(sendNanoContractTx(...)).rejects.toThrow() with await
expect(...).rejects.toThrow(PromptRejectedError) or equivalent instance check).
Ensure the test file imports PromptRejectedError (or references the exact
exported symbol) and keep the existing mockReleaseUtxos assertions unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress (WIP)

Development

Successfully merging this pull request may close these issues.

1 participant