Skip to content

multi: implement in-round directed VTXO sends - #176

Merged
ellemouton merged 11 commits into
mainfrom
send-in-round
Mar 26, 2026
Merged

multi: implement in-round directed VTXO sends#176
ellemouton merged 11 commits into
mainfrom
send-in-round

Conversation

@ellemouton

@ellemouton ellemouton commented Mar 12, 2026

Copy link
Copy Markdown
Member

Summary

Replaces #169
Fixes #156.

This PR implements first-class in-round directed sends through SendVTXO on
top of the admission model introduced in #175.

Instead of reusing the out-of-round spend path, directed send is treated as the
cooperative flow it actually is:

  • the wallet asks the VTXO manager to atomically select and reserve inputs for
    cooperative use
  • the selected VTXOs enter PendingForfeit
  • the wallet composes the full round intent package, including recipient and
    change outputs
  • the round actor registers that intent and proceeds with normal cooperative
    round handling

That makes this the PR 3 replacement for #169, but with the post-PR-1/PR-2
architecture rather than the older send-specific trigger flow.

Why This Replaces #169

#169 was built before the current wallet/round boundary and before actor-owned
VTXO admission existed. The main problem with reviving it directly was that it
mixed directed send with the old spend-locking path.

This PR replaces that design with the same single-source-of-truth model used by
PR 2:

  • OOR send: Live -> Spending -> Spent
  • In-round directed send: Live -> PendingForfeit -> Forfeiting -> Forfeited

So directed send now reuses cooperative admission rather than creating a second
locking path.

Flow

SendVTXO RPC
  -> validate recipients and total
  -> wallet.SendVTXOsRequest
  -> manager.SelectAndReserveForfeitRequest
  -> selected inputs move Live -> PendingForfeit
  -> wallet builds IntentPackage:
       forfeits + recipient outputs + change output
  -> wallet sends RegisterIntentMsg to round
  -> if round rejects:
       release forfeit reservation and surface any release failure
  -> if round accepts:
       normal cooperative round lifecycle continues

Main Changes

  • Add SelectAndReserveForfeitRequest so directed send can do atomic
    cooperative coin selection and reservation in one step.
  • Add dedicated wallet send messages instead of overloading refresh.
  • Implement handleSendVTXOs with strict cleanup on every post-reservation
    failure path.
  • Implement SendVTXO in the daemon RPC server, including recipient
    resolution for taproot addresses and x-only pubkeys.
  • Return send result metadata including selected input count and explicit
    change amount.
  • Persist directed-send recipient VTXOs using the resolved recipient
    ClientKey, not the sender signing key.
  • Add unit, integration, and systest coverage for the new flow.

Dependency

This PR depends on #175 (vtxo-spend-state).

It is opened against vtxo-spend-state because PR 3 relies on PR 2's actor-
owned admission model and manager-side cooperative select-and-reserve API.

Test Plan

  • go test ./darepod ./wallet ./vtxo ./round ./oor
  • go test -tags=systest ./systest -run TestSendVTXOEndToEnd -count=1

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates in-round directed VTXO sending functionality, enabling users to transfer virtual transaction outputs to other participants within a cooperative round. The changes focus on robust and atomic handling of coin selection, VTXO reservation, and round registration, ensuring secure and efficient transfers while leveraging existing admission controls. This significantly expands the system's capabilities for cooperative transactions.

Highlights

  • In-Round Directed VTXO Sends: Implemented the capability for in-round directed VTXO sends, allowing users to transfer VTXOs to other participants within a cooperative round, building upon the existing admission gate mechanism.
  • Atomic Coin Selection and Reservation: Introduced SelectAndReserveForfeitRequest to enable atomic coin selection and transition of VTXOs into PendingForfeitState within the VTXO manager, preventing race conditions during reservation.
  • Enhanced SendVTXO RPC: Wired the SendVTXO RPC to support recipient resolution for taproot addresses and x-only pubkeys, ensuring proper handling of various destination types for directed sends.
  • Corrected VTXO Ownership: Adjusted VTXO ownership logic to correctly use the recipient's ClientKey for persisted VTXO descriptors, which is crucial for directed sends where the recipient differs from the sender.
  • Comprehensive System Testing: Added a new system test that covers the entire directed send flow, including daemon setup, VTXO seeding, and interaction with a fake mailbox edge, ensuring end-to-end functionality.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • daemonrpc/daemon.pb.go
    • Added ChangeAmountSat and SelectedCount fields to SendVTXOResponse and generated their respective getter methods.
  • daemonrpc/daemon.proto
    • Added change_amount_sat and selected_count fields to the SendVTXOResponse message definition.
  • darepod/rpc_server.go
    • Updated SendVTXO RPC documentation and implemented its handler logic to resolve recipients and interact with the wallet actor.
    • Added resolveRecipientOutput function to extract pkScript and clientKey from Output protos.
  • darepod/rpc_server_test.go
    • Added a new test file with unit tests for the resolveRecipientOutput function.
  • lib/actormsg/vtxo_admission.go
    • Defined SelectAndReserveForfeitRequest and SelectAndReserveForfeitResponse types for atomic coin selection and VTXO reservation.
  • round/transitions.go
    • Modified buildClientVTXOs to use the ClientKey for VTXO ownership in persisted descriptors.
  • systest/send_vtxo_test.go
    • Added a new system test TestSendVTXOEndToEnd to validate the full directed send flow.
  • vtxo/manager.go
    • Implemented handleSelectAndReserveForfeit to perform largest-first coin selection and atomically reserve VTXOs for forfeiture.
  • vtxo/manager_admission_test.go
    • Added unit tests for SelectAndReserveForfeit functionality, including success, multiple VTXO selection, insufficient funds, and rollback scenarios.
  • vtxo/messages.go
    • Aliased SelectAndReserveForfeitRequest and SelectAndReserveForfeitResponse from the actormsg package.
  • wallet/messages.go
    • Defined SendRecipient struct for recipient details in directed sends.
    • Introduced SendVTXOsRequest and SendVTXOsResponse types for the wallet's directed send functionality.
  • wallet/wallet.go
    • Implemented handleSendVTXOs to manage the directed send process, including recipient validation, coin selection, and round registration.
    • Added releaseManagerForfeitStrict for strict error handling during forfeit release.
  • wallet/wallet_admission_test.go
    • Added mock behaviors for VTXO manager and round actor to facilitate testing.
    • Included comprehensive unit tests for SendVTXOs covering various scenarios like dry-run, round rejection, and intent package contents.
Activity
  • ellemouton created the pull request, providing a detailed summary, commit walkthrough, dependencies, and test plan.
  • ellemouton executed unit tests for SelectAndReserveForfeit, resolveRecipientOutput, and handleSendVTXOs.
  • ellemouton performed an integration test for handleSendVTXOs and a system test TestSendVTXOEndToEnd.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ellemouton

Copy link
Copy Markdown
Member Author

@claude review this

@claude

claude Bot commented Mar 12, 2026

Copy link
Copy Markdown

Claude finished @ellemouton's task in 5m 53s —— View job


Code Review

  • Read changed files and gather context
  • Review lib/actormsg/vtxo_admission.go — new SelectAndReserveForfeit types
  • Review vtxo/manager.go and vtxo/messages.go — coin selection and reservation
  • Review wallet/messages.go and wallet/wallet.go — send flow
  • Review darepod/rpc_server.go and rpc_server_test.go — RPC handler
  • Review round/transitions.go — ClientKey ownership fix
  • Review systest/send_vtxo_test.go — system test coverage
  • Post final review

The overall design is sound and the architecture is clean — using the cooperative admission gate (PR 2) as the foundation for directed sends is the right call. The flow is straightforward to follow and the test coverage is solid. A few issues to work through:


Critical

1. Sender's wallet tracks unowned recipient VTXOs

buildClientVTXOs (round/transitions.go:1668) iterates over all VTXORequests in the intent and builds ClientVTXOs for each. After the ClientKey fix these VTXOs correctly carry the recipient's key. Then handleVTXOCreated (vtxo/manager.go:176) spawns a VTXO actor and persists a Descriptor for every VTXO in the notification — without filtering out those the local wallet doesn't own.

Result:

  • The sender's store ends up with Descriptor rows whose ClientKey is the recipient's key and has no KeyLocator.
  • On restart the manager re-spawns actors for these descriptors.
  • When those VTXOs approach expiry the VTXO FSM fires a forfeit cycle that the sender cannot complete (it can't produce the recipient's co-signature).

For self-refresh and leave this was never an issue because ClientKey == SigningKey. Directed send breaks that assumption for the first time. Some kind of ownership filter is needed — either in buildClientVTXOs (only include VTXOs whose SigningKey is also the ClientKey, i.e. self-owned) or in handleVTXOCreated (skip actors for descriptors where the ClientKey has no KeyLocator). Fix this →

2. Descriptor.PkScript vs Descriptor.TapScript mismatch for directed-send outputs

In clientVTXOToDescriptor (vtxo/manager.go:808), TapScript is (re)computed via scripts.VTXOTapScript(cv.ClientKey.PubKey, cv.OperatorKey, cv.Expiry) — a standard VTXO tapscript with the 2-of-2 collaborative path + CSV exit. But Descriptor.PkScript is set directly from cv.PkScript, which for Output_Pubkey recipients is a plain BIP-86 P2TR produced in resolveRecipientOutput.

The two fields will reference different output scripts for the same VTXO. Any code path that uses PkScript to identify the on-chain output (e.g. unilateral sweep) and any path that uses TapScript (e.g. cooperative signing) will be operating on inconsistent data.

3. wallet/messages.go comment contradicts the implementation

SendRecipient.PkScript docstring (wallet/messages.go:521) says:

For pubkey destinations this is derived from the recipient's key, the operator's key, and the VTXO exit delay via tree.NewVTXODescriptor.

But resolveRecipientOutput (darepod/rpc_server.go:931) for Output_Pubkey does:

addr, err := btcutil.NewAddressTaproot(d.Pubkey, r.server.chainParams)
pkScript, err := txscript.PayToAddrScript(addr)

This is a plain BIP-86 P2TR — not a VTXO descriptor. The resolveRecipientOutput function also doesn't have access to operator terms at that point (they're fetched afterward), so it structurally cannot call tree.NewVTXODescriptor. Either the comment needs to be updated to reflect the actual design (raw P2TR is intentional), or the architecture needs to change so the descriptor is built after terms are fetched.


Significant

4. rollbackForfeit silently drops errors

rollbackForfeit (vtxo/manager.go:566) is documented as best-effort but the docstring just says "errors are logged but do not propagate." The problem is that after a partial rollback failure the caller's error reflects only the trigger (e.g. "no actor for outpoint X") — there's no indication that some VTXOs are now stuck in PendingForfeitState. Compare with handleReleaseForfeit which aggregates all failures with errors.Join.

If best-effort semantics are intentional here (rollback is already a fallback path) that's fine, but the docstring should say why it's best-effort — e.g. "a rollback failure leaves the VTXO temporarily locked in PendingForfeitState; the manager will recover it from the store on restart." Fix this →

5. Asymmetric clientKey semantics across destination types in resolveRecipientOutput

For Output_Pubkey (rpc_server.go:944), clientKey is the raw (un-tweaked) internal key parsed directly from the 32-byte input. For Output_Address (rpc_server.go:970), clientKey is extracted from tapAddr.ScriptAddress() — this is the output key, which for BIP-86 addresses is the BIP-86 tweaked key, and for VTXO descriptor addresses is the full Tapscript commitment key (not the internal key).

Downstream code in clientVTXOToDescriptor calls scripts.VTXOTapScript(cv.ClientKey.PubKey, ...) treating clientKey as the internal key in all cases. The two paths produce semantically different keys, so VTXOs created via Output_Address will have an internally inconsistent TapScript.


Minor

handleSendVTXOs missing fee validation (wallet/wallet.go:1217): req.OperatorFee is used without a >= 0 guard. In practice it comes from fetchOperatorTerms at the RPC layer, but the wallet API is also a public actor boundary. A guard at the start of the handler would be consistent with the existing Amount checks.

Systest coverage gap: TestSendVTXOEndToEnd (systest/send_vtxo_test.go) verifies VTXO_STATUS_REFRESH_REQUESTED and ROUND_STATE_PENDING_ASSEMBLY but not that the recipient eventually receives their VTXO. That's expected given a fake mailbox, but it's worth noting the test doesn't exercise the confirmation path or the buildClientVTXOshandleVTXOCreated chain.

resolveRecipientOutput binding (darepod/rpc_server.go:931): This function only uses r.server.chainParams. It could be a package-level function taking *chaincfg.Params directly, which would make it easier to test in isolation (the existing test file has to construct a full RPCServer stub just to call it).


Overall: The atomic select-and-reserve design (SelectAndReserveForfeitRequest) is clean and the rollback-on-failure discipline is good. The main concern to resolve before merging is the sender persisting and attempting to manage recipient VTXOs (issue 1). Issues 2 and 3 are closely related and seem to share a root cause: the design intention for PkScript in directed sends (plain P2TR vs. VTXO descriptor) is not consistently captured across the comment, the RPC handler, and the descriptor builder.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant feature: in-round directed VTXO sends. The implementation is well-structured, spanning the RPC layer, wallet actor, and VTXO manager. I appreciate the focus on atomicity with the new SelectAndReserveForfeitRequest, and the robust error handling in the wallet actor which ensures reservations are cleaned up correctly on failure. The fix for VTXO ownership in buildClientVTXOs is a critical detail that has been handled well. The test coverage is comprehensive, with new unit, integration, and a full end-to-end system test. I have one minor suggestion to improve the accuracy of the new system test's setup.

Comment thread systest/send_vtxo_test.go
OperatorKey: operatorKey,
TreePath: treePath,
RoundID: roundID.String(),
CommitmentTxID: commitmentTxID,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The CommitmentTxID used when saving the seeded VTXO is inconsistent with the transaction hash used to finalize the round. commitmentTxID is a hardcoded hash, while the round is finalized with commitmentTx.TxHash(). The VTXO descriptor should store the actual transaction hash of the commitment transaction that created it. While this might not affect the current test flow, it could cause issues in the future if any logic relies on this field to match the on-chain transaction, and it makes the test setup less accurate.

Suggested change
CommitmentTxID: commitmentTxID,
CommitmentTxID: commitmentTx.TxHash(),

@ellemouton
ellemouton force-pushed the vtxo-spend-state branch 3 times, most recently from 8af9fb1 to cdb91b3 Compare March 12, 2026 17:17
@ellemouton
ellemouton force-pushed the send-in-round branch 4 times, most recently from 8d30ba3 to 8dc7b24 Compare March 12, 2026 18:48
@ellemouton
ellemouton requested a review from Roasbeef March 12, 2026 18:49
@ellemouton ellemouton linked an issue Mar 13, 2026 that may be closed by this pull request
@Roasbeef
Roasbeef force-pushed the vtxo-spend-state branch 6 times, most recently from 3715fdd to 0943444 Compare March 14, 2026 04:45
@ellemouton
ellemouton changed the base branch from vtxo-spend-state to main March 16, 2026 08:16

@Roasbeef Roasbeef left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM 🫐

Solid PR, no direct blocking comments, see some of the inline comments.

Comment thread lib/actormsg/vtxo_admission.go
Comment thread wallet/messages.go
Comment thread wallet/messages.go
actor.BaseMessage

// Status is "submitted" for real sends or "preview" for dry-run.
Status string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this return a round ID or something else that can be used to query the state of the ongoing send?

Comment thread systest/send_vtxo_test.go
Comment thread darepod/rpc_server.go
)
}

pkScript, err := txscript.PayToAddrScript(addr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we actually be making an actual script here? In that we only really want a pubkey from them, as that'll be used as the pubkey in the newly created VTXO.

Comment thread wallet/wallet.go Outdated

vtxoRequests = append(vtxoRequests, types.VTXORequest{
Amount: r.Amount,
PkScript: r.PkScript,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this actually used? Given we haven't finished the AST feature yet (not merged in).

Comment thread darepod/rpc_server.go
OperatorFee: terms.MinOperatorFee,
DustLimit: terms.DustLimit,
OperatorKey: terms.PubKey,
VTXOExitDelay: terms.VTXOExitDelay,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since I had the scope of this PR, we should also have the client validate that the exit delay is actually saying otherwise if it's zero, then that means that there's no actual safety for the user.

@Roasbeef

Copy link
Copy Markdown
Member

One other thing that we should consider here is the receiver side. For example, for OOR, we actually have the indexer and then the receiver can hit the indexer to basically know when something is sent to it and to get all the checkpoints, etc. Here we don't actually have anything like that. How is the receiver supposed to know that they have a new Inron VTXO?

I think we should make a follow-up here to put the proper plumbing to make sure that the receiver can also know whether there's a new and run VTXO because, for example, the server would also do a similar push notification as it did for O or R to the receiver so they can actually know that they have a new VTXO to get all the information and it's imported and recognized as its own.

@ellemouton

Copy link
Copy Markdown
Member Author

thanks for review 🙏

working on addressing things locally. realised i think we first need to fix this bug: #210

ellemouton and others added 10 commits March 25, 2026 17:38
Add the atomic cooperative select-and-reserve message pair for
directed send. This is the cooperative counterpart of
SelectAndReserveSpendRequest: it selects VTXOs covering a target
amount and drives each into PendingForfeitState rather than
SpendingState. Without this atomic API, a split select-then-reserve
flow would re-open the race that PR 2's admission model closes.
Add handleSelectAndReserveForfeit to the VTXO manager. This is the
directed-send counterpart of handleSelectAndReserveSpend: it selects
VTXOs covering a target amount using largest-first coin selection,
then atomically reserves each via PendingForfeitEvent. On partial
failure, already-reserved VTXOs are rolled back.

Five tests cover success, multi-VTXO selection, insufficient funds,
non-live exclusion, and partial rollback.
Add SendVTXOsRequest, SendVTXOsResponse, and SendRecipient types
for in-round directed send. The request carries resolved recipient
pkScripts, operator terms, and a dry-run flag. The response returns
selection details and change amount.
Add the core wallet handler for in-round directed send. The flow:
1. Validate recipients (non-empty pkScript, positive amounts)
2. Atomic select-and-reserve via SelectAndReserveForfeitRequest
3. Compute change and reject below-dust change
4. Build IntentPackage: forfeits + recipient VTXOs + change VTXO
5. Register with round actor via RegisterIntentMsg
6. On failure: release forfeit reservation

Dry-run exercises the real admission path then immediately releases,
surfacing release failures explicitly.

Seven tests cover success, no-change, dust rejection, dry-run,
dry-run release failure, round rejection with release, and
insufficient funds.
Extend the SendVTXOResponse proto message with two new fields that
the RPC handler will populate after coin selection:
- change_amount_sat: change returned to sender (zero if exact match)
- selected_count: number of VTXOs selected as inputs
Replace the SendVTXO stub with a full implementation that delegates
to the wallet actor's SendVTXOsRequest. The RPC handler resolves
recipient destinations (taproot address or x-only pubkey) into both
a pkScript and a client public key, fetches operator terms, and
forwards the request to the wallet for atomic coin selection,
reservation, and round registration.

A new resolveRecipientOutput helper extracts the client key from
taproot addresses (witness program) or raw pubkeys. Raw pk_script
destinations are rejected since they lack the public key needed for
VTXO descriptor construction and MuSig2 signing.
Cover all five destination resolution paths for directed sends:
- Pubkey: valid x-only key → pkScript + clientKey
- Address: taproot bech32m → pkScript + clientKey extraction
- PkScript: rejected (no public key for VTXO construction)
- Non-taproot address: rejected (segwit v0 lacks x-only key)
- Invalid pubkey: wrong length rejected
buildClientVTXOs was assigning req.SigningKey (the sender's MuSig2
co-signing key) as the ClientKey on persisted ClientVTXO records.
For self-refresh this is harmless since sender and recipient are the
same, but for directed sends the resulting VTXO would appear owned
by the sender's derived key instead of the recipient's declared
public key.

Use req.ClientKey (the declared VTXO owner) wrapped in a
KeyDescriptor so directed send recipients get VTXOs they actually
control.
Add a full-daemon SendVTXO system test that runs darepod against the
regtest harness and a fake operator mailbox edge. The test preseeds a
live VTXO, exercises the public gRPC API, and asserts that directed send
admission moves the input into pending forfeit and creates a temp round.

This gives stronger integration coverage for PR 3 than the existing
wallet and RPC seam tests without requiring the full operator round
protocol in the systest harness.
Add documentation for the new SelectAndReserveForfeitRequest admission
type, SendVTXO RPC handler, wallet directed send flow, and the
OwnerKey vs SigningKey distinction for VTXO ownership persistence.
@Roasbeef

Copy link
Copy Markdown
Member

Rebased on top of master after the recent changes.

@ellemouton

Copy link
Copy Markdown
Member Author

Follow-up items (separate PR)

The following changes are planned for a follow-up PR on top of this one:

Hardening

  • Overflow protection: validate individual recipient amounts against 21M BTC cap, overflow-safe addition
  • Deferred cleanup: protect VTXO reservation with defer + committed flag so panics don't leak PendingForfeitState
  • context.WithoutCancel for cleanup operations so forfeit release survives client disconnection
  • Cap recipients at 256 in RPC validation
  • Explicit nil destination check before type switch

Ownership model (replace IsOwner with data-driven ownership)

  • Add OwnedScriptChecker interface to round FSM's ClientEnvironment
  • Replace IsOwner flag with OwnedScriptChecker.IsOwnedScript(pkScript) — ownership determined by checking the owned_receive_scripts DB store
  • Remove IsOwner from VTXOIntent and VTXORequest
  • Add OwnedScriptRegistrar interface on round actor — registers pkScripts for boarding, refresh, and change VTXOs at intent time
  • Wire ownedScriptCheckerAdapter and ownedScriptRegistrarAdapter into daemon using OORArtifactPersistenceStore
  • Self-send works correctly (both recipient + change VTXOs persisted)

Server-side VTXO event publishing

  • VTXOEventPublisher interface on server rounds actor
  • After round confirmation, publish VTXO_CREATED indexer events for each tree leaf
  • Wire adapter using indexer.Operator.PublishVTXOEvent

Recipient VTXO materialization (bob sees his VTXO)

  • Client-side IncomingVTXOEvent handler — currently no envelope route exists for MethodIncomingVTXO
  • Handler needs: indexer query for full VTXO details, owner key lookup from owned_receive_scripts, persist + notify VTXO manager
  • Server-side publish works (confirmed via testing), client-side handler is the remaining work

Other

  • Rename NewOORReceiveScriptNewReceiveScript (proto rename, needs make rpc)
  • Send tracking (send_id concept for correlating sends with round lifecycle)
  • Server itest: TestDirectedSendSelfSend (alice → alice, both VTXOs persisted)

Companion server itest PR: lightninglabs/darepo#205

- Fix bare log reference in SendVTXO RPC handler (use r.server.log)
- Fix systest: update NewVTXODescriptor call to 5-arg signature
  and rename ClientKey → OwnerKey per #210
- Fix gofmt alignment in wallet send handler
@ellemouton
ellemouton merged commit bd75b3b into main Mar 26, 2026
16 checks passed
@ellemouton
ellemouton deleted the send-in-round branch March 26, 2026 08:33
ellemouton pushed a commit that referenced this pull request Mar 26, 2026
Part 1: real-daemon integration tests (partial coverage)
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.

darepod: implement in-round directed sends via SendVTXO RPC

3 participants