Skip to content

Conversation

@louisinger
Copy link
Contributor

@louisinger louisinger commented Nov 24, 2025

implements arkade-os/arkd#832

@altafan please review

Summary by CodeRabbit

  • New Features

    • Added ability to finalize pending transactions with optional timestamp-based filtering
    • Introduced method to retrieve pending transactions from the network
    • Added "pending only" filter to query unfinalized virtual UTXOs in the indexer
  • Chores

    • Updated dependency version

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Walkthrough

The PR adds support for finalizing pending transactions and querying pending offchain transactions across the SDK. It introduces the FinalizePendingTxs method to enumerate and finalize pending vtxos, extends the transport layer with GetPendingTx and AcceptedOffchainTx structures, and adds a pendingOnly filter to indexer queries for pending vtxo filtering.

Changes

Cohort / File(s) Summary
SDK Interface
ark_sdk.go
Added public FinalizePendingTxs(ctx context.Context, createdAfter *time.Time) ([]string, error) method to ArkClient interface.
SDK Implementation
client.go
Implemented FinalizePendingTxs to enumerate and finalize pending vtxos; added internal helpers finalizeTx to sign checkpoints and finalize transactions, and makeGetPendingTxIntent to construct GetPendingTx intent payloads.
Transport Layer
client/client.go
Added AcceptedOffchainTx struct with Txid, FinalArkTx, and SignedCheckpointTxs fields; added GetPendingTx(ctx context.Context, proof, message string) ([]AcceptedOffchainTx, error) to TransportClient interface; noted TODO for SubmitTx to return AcceptedOffchainTx.
gRPC Transport
client/grpc/client.go
Implemented GetPendingTx method to build GetPendingTxRequest with Intent, execute call, and map response PendingTxs to []client.AcceptedOffchainTx.
REST Transport
client/rest/client.go
Implemented GetPendingTx method to construct ArkService GetPendingTx request, execute, and map response to []client.AcceptedOffchainTx.
Base Client
base_client.go
Added listPendingSpentVtxosFromIndexer helper to retrieve pending (unconfirmed) spent vtxos from indexer with optional CreatedAt filtering.
Indexer Options
indexer/opts.go
Added pendingOnly field to GetVtxosRequestOption with setter WithPendingOnly() and getter GetPendingOnly(); added endTime field to GetTxHistoryRequestOption.
Indexer Implementations
indexer/grpc/client.go, indexer/rest/client.go
Updated GetVtxos calls to include PendingOnly parameter from options.
Indexer REST Service
indexer/rest/service/api_indexer_service.go, indexer/rest/service/model_get_vtxos_request.go
Added pendingOnly field and builder method PendingOnly(bool) to ApiIndexerServiceGetVtxosRequest; added PendingOnly *bool field with accessor methods (Get, GetOk, Has, Set) to GetVtxosRequest model.
API Specification
api-spec/openapi/swagger/ark/v1/indexer.openapi.json
Added pendingOnly boolean query parameter to GetVtxos endpoint; added pendingOnly field to GetVtxosRequest schema.
Dependencies
go.mod
Updated ark-lib dependency from v0.8.1-0.20251029114835-d33f27e11343 to v0.8.1-0.20251125124623-101ae751505f.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant arkClient
    participant Wallet
    participant Indexer
    participant TransportClient

    User->>arkClient: FinalizePendingTxs(ctx, createdAfter)
    arkClient->>arkClient: listPendingSpentVtxosFromIndexer()
    arkClient->>Indexer: GetVtxos(pendingOnly=true, createdAfter)
    Indexer-->>arkClient: pending vtxos
    
    loop for each pending vtxo
        arkClient->>arkClient: resolve tapscripts & construct inputs
        arkClient->>arkClient: makeGetPendingTxIntent(inputs, leafProofs, arkFields)
        arkClient->>Wallet: sign checkpoint transaction
        Wallet-->>arkClient: signed checkpoint
        
        arkClient->>TransportClient: GetPendingTx(proof, message)
        TransportClient-->>arkClient: AcceptedOffchainTx
        
        arkClient->>arkClient: finalizeTx(ctx, acceptedTx)
        arkClient->>TransportClient: SubmitTx / finalize
        TransportClient-->>arkClient: arkTxid
    end
    
    arkClient-->>User: []finalized txids
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • client.go: New FinalizePendingTxs implementation with complex vtxo enumeration, signing, and finalization logic; internal helpers finalizeTx and makeGetPendingTxIntent warrant careful review of integration with wallet and transport layers.
  • client/client.go: New AcceptedOffchainTx struct and GetPendingTx interface addition; verify consistency with transport implementations.
  • indexer/rest/service/model_get_vtxos_request.go: Generated model changes; verify accessor method implementations are consistent with existing patterns.
  • Indexer filter propagation: Verify pendingOnly parameter is consistently threaded through all indexer call sites (gRPC, REST, opts).

Possibly related PRs

  • Intent proof of ownership rework #22: Introduces intent-based signing and proof-of-ownership flow that this PR builds upon with new arkFields and makeGetPendingTxIntent usage.
  • Merge changes for v0.8 #26: Also adds GetPendingTx/FinalizeTx client endpoints and pending-only indexer support, touching overlapping transaction finalization code paths.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add FinalizePendingTxs' directly matches the main change: a new public method FinalizePendingTxs is added to the ArkClient interface.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pending-txs

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0932b2c and 106378d.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • go.mod (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go.mod
⏰ 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). (2)
  • GitHub Check: Sdk integration tests
  • GitHub Check: Sdk unit tests

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

@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

🧹 Nitpick comments (3)
client.go (1)

834-902: FinalizePendingTxs flow is reasonable but hides per-tx failures.

Logic looks sound: you gather non-swept vtxos (optionally filtered by createdAfter), enrich them with tapscripts, build a GetPendingTx intent, and finalize each returned AcceptedOffchainTx. However, any finalizeTx error is only logged and the method still returns successfully with a (possibly partial) set of txids.

If the caller needs to know that some pending txs failed to finalize, consider:

  • returning both []string and an aggregated error (e.g., fmt.Errorf("finalized %d/%d pending txs: %w", ...)), or
  • at least returning a non-nil error when all finalizations fail.

This can be a follow-up improvement if current API is intentionally “best-effort”.

client/client.go (2)

20-24: Add documentation for the exported struct.

The AcceptedOffchainTx struct is exported and part of the public API. Consider adding a godoc comment explaining its purpose and documenting each field to improve API usability.

Example documentation:

+// AcceptedOffchainTx represents an accepted off-chain transaction with its
+// finalization details.
 type AcceptedOffchainTx struct {
+	// Txid is the transaction identifier
 	Txid                string
+	// FinalArkTx is the finalized Ark transaction
 	FinalArkTx          string
+	// SignedCheckpointTxs contains the signed checkpoint transactions
 	SignedCheckpointTxs []string
 }

52-52: Add documentation for the interface method.

The GetPendingTx method is part of the public TransportClient interface. Consider adding a godoc comment explaining its purpose and parameters.

Example documentation:

+	// GetPendingTx retrieves pending transactions for the given proof and message.
 	GetPendingTx(ctx context.Context, proof, message string) ([]AcceptedOffchainTx, error)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fea8eb and 7045754.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • ark_sdk.go (2 hunks)
  • client.go (3 hunks)
  • client/client.go (2 hunks)
  • client/grpc/client.go (1 hunks)
  • client/rest/client.go (1 hunks)
  • go.mod (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T07:46:07.938Z
Learnt from: louisinger
Repo: arkade-os/go-sdk PR: 6
File: client.go:1194-1211
Timestamp: 2025-08-13T07:46:07.938Z
Learning: The listenForBoardingTxs() function in client.go is started from the Unlock method in base_client.go when WithTransactionFeed is enabled and UtxoMaxAmount is non-zero, not from the LoadArkClient methods.

Applied to files:

  • client.go
🧬 Code graph analysis (2)
client/grpc/client.go (4)
client/client.go (1)
  • AcceptedOffchainTx (20-24)
client/rest/service/model_get_pending_tx_request.go (1)
  • GetPendingTxRequest (21-23)
api-spec/protobuf/gen/ark/v1/service.pb.go (3)
  • GetPendingTxRequest (1237-1242)
  • GetPendingTxRequest (1255-1255)
  • GetPendingTxRequest (1270-1272)
api-spec/protobuf/gen/ark/v1/types.pb.go (3)
  • Intent (441-447)
  • Intent (460-460)
  • Intent (475-477)
client.go (3)
client/client.go (1)
  • AcceptedOffchainTx (20-24)
types/types.go (1)
  • Vtxo (80-94)
store/sql/sqlc/queries/models.go (1)
  • Vtxo (37-52)
🔇 Additional comments (6)
go.mod (1)

7-9: Ark-lib version bump looks fine; ensure tests against new API surface.

The dependency update is straightforward; just make sure all ark-lib–related changes (intents, pending-tx, finalize APIs) are covered by tests/integration runs with this exact version.

client.go (2)

354-391: Good reuse of finalizeTx in SendOffChain.

Centralizing the “sign checkpoints + finalize on server” logic via a.finalizeTx keeps the off-chain send path consistent with the new pending-tx flow; the surrounding verification (verifySignedArk / verifySignedCheckpoints) is preserved.


2206-2220: makeGetPendingTxIntent TTL choice and structure look fine.

Building a dedicated IntentMessageTypeGetPendingTx with a 10‑minute ExpireAt and reusing makeIntent (no outputs) is consistent with the other intent helpers and keeps the request lightweight; no issues spotted here.

ark_sdk.go (1)

3-8: Interface extension for FinalizePendingTxs is coherent with implementation.

Adding FinalizePendingTxs(ctx context.Context, createdAfter *time.Time) ([]string, error) to ArkClient (with a *time.Time filter) aligns with the arkClient implementation and provides a clear, optional “created-after” cutoff for finalizing pending txs. No further changes needed at the interface layer.

Also applies to: 12-57

client/rest/client.go (1)

379-405: REST GetPendingTx implementation correctly maps to AcceptedOffchainTx.

The request wiring (Intent{Message, Proof}) and response mapping (ArkTxid, FinalArkTx, SignedCheckpointTxsclient.AcceptedOffchainTx) look correct and consistent with the transport interface and gRPC implementation.

client/grpc/client.go (1)

379-404: gRPC GetPendingTx correctly wires request/response to AcceptedOffchainTx.

The construction of GetPendingTxRequest with an embedded Intent and the mapping of resp.GetPendingTxs() into client.AcceptedOffchainTx (ArkTxid, FinalArkTx, SignedCheckpointTxs) is straightforward and matches the REST client, so the transport layer remains consistent.

Copy link

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

🧹 Nitpick comments (1)
client.go (1)

834-938: Consider surfacing partial failures from FinalizePendingTxs

The overall flow—gathering (non‑swept, optionally date‑filtered) vtxos, chunking into intents, calling GetPendingTx, and then reusing finalizeTx—is coherent and matches the intent‑based design.

One behavioral concern: when finalizeTx fails for a given pending tx, the error is only logged and the loop continues, while FinalizePendingTxs still returns ([]string{successfulTxids}, nil). Callers have no way to tell that some eligible pending txs failed to finalize.

Consider returning richer information, for example:

  • a slice of successes and a slice of txids that failed, and/or
  • an aggregated error when at least one finalize call failed,

so that callers can react (retry, notify the user, etc.) instead of relying solely on logs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7045754 and b13ad33.

⛔ Files ignored due to path filters (1)
  • api-spec/protobuf/gen/ark/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (4)
  • buf.gen.yaml (1 hunks)
  • client.go (3 hunks)
  • client/client.go (2 hunks)
  • client/grpc/client.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • buf.gen.yaml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T07:46:07.938Z
Learnt from: louisinger
Repo: arkade-os/go-sdk PR: 6
File: client.go:1194-1211
Timestamp: 2025-08-13T07:46:07.938Z
Learning: The listenForBoardingTxs() function in client.go is started from the Unlock method in base_client.go when WithTransactionFeed is enabled and UtxoMaxAmount is non-zero, not from the LoadArkClient methods.

Applied to files:

  • client.go
🧬 Code graph analysis (2)
client/grpc/client.go (3)
client/client.go (1)
  • AcceptedOffchainTx (20-24)
api-spec/protobuf/gen/ark/v1/service.pb.go (5)
  • GetPendingTxRequest (1237-1245)
  • GetPendingTxRequest (1258-1258)
  • GetPendingTxRequest (1273-1275)
  • GetPendingTxRequest_Intent (1297-1299)
  • GetPendingTxRequest_Intent (1301-1301)
api-spec/protobuf/gen/ark/v1/types.pb.go (3)
  • Intent (441-447)
  • Intent (460-460)
  • Intent (475-477)
client.go (5)
client/client.go (2)
  • AcceptedOffchainTx (20-24)
  • Input (93-96)
types/types.go (1)
  • Vtxo (80-94)
api-spec/protobuf/gen/ark/v1/types.pb.go (6)
  • Vtxo (128-145)
  • Vtxo (158-158)
  • Vtxo (173-175)
  • Input (76-82)
  • Input (95-95)
  • Input (110-112)
store/sql/sqlc/queries/models.go (1)
  • Vtxo (37-52)
explorer/service.go (1)
  • Input (86-90)
🔇 Additional comments (4)
client/grpc/client.go (1)

379-406: GetPendingTx gRPC wiring and mapping look correct

The request correctly wraps the intent in GetPendingTxRequest_Intent, and the response is mapped cleanly into []client.AcceptedOffchainTx. No issues from a correctness or style standpoint.

client/client.go (1)

20-24: AcceptedOffchainTx and TransportClient.GetPendingTx API look well-shaped

The AcceptedOffchainTx struct cleanly models the off-chain acceptance data, and extending TransportClient with GetPendingTx is consistent with the new finalization flow. The SubmitTx TODO comment documents the intended future consolidation.

Also applies to: 48-52

client.go (2)

380-390: Centralizing off-chain finalization via finalizeTx in SendOffChain looks good

Reusing finalizeTx here keeps the checkpoint-signing + FinalizeTx RPC logic in one place and preserves the existing WithTransactionFeed short‑circuit behavior.


2221-2235: makeGetPendingTxIntent matches existing intent helper patterns

Encoding a GetPendingTxMessage with a short (10‑minute) expiry and reusing makeIntent for proof construction is consistent with the other intent helpers (makeRegisterIntent, makeDeleteIntent) and looks correct.

Copy link

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

🧹 Nitpick comments (3)
base_client.go (1)

709-741: Consider factoring out shared address→script logic.

listPendingSpentVtxosFromIndexer duplicates the offchain address decoding and P2TR script construction from listVtxosFromIndexer. A small helper (e.g., offchainVtxoScripts(ctx) returning []string) would reduce duplication and keep the two call sites in sync if address handling ever changes.

client.go (1)

834-909: Clarify swept-vtxo handling and partial-failure behavior in FinalizePendingTxs.

Two small points:

  • The comment says “filter out swept vtxos”, but the loop only filters by createdAfter. Either add an explicit if vtxo.Swept { continue } or update the comment so it matches behavior.
  • On finalizeTx failures you log and continue, returning only successfully finalized txids. That’s reasonable, but it might be worth documenting in the method comment or API docs so callers know errors are best-effort and not fatal for the whole batch.
indexer/opts.go (1)

22-30: pendingOnly flag and accessors are straightforward; consider optional validation.

The new pendingOnly field with WithPendingOnly/GetPendingOnly is consistent with the existing spendable/spent/recoverable flags. If you expect pendingOnly to be combined only with certain modes (e.g., implicitly “spent”), an optional guard or doc comment here could prevent accidental incompatible combinations, but it’s not strictly required since the server enforces semantics.

Also applies to: 90-96

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b13ad33 and 0932b2c.

⛔ Files ignored due to path filters (2)
  • api-spec/protobuf/gen/ark/v1/indexer.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • api-spec/openapi/swagger/ark/v1/indexer.openapi.json (2 hunks)
  • base_client.go (1 hunks)
  • buf.gen.yaml (1 hunks)
  • client.go (3 hunks)
  • go.mod (1 hunks)
  • indexer/grpc/client.go (1 hunks)
  • indexer/opts.go (2 hunks)
  • indexer/rest/client.go (1 hunks)
  • indexer/rest/service/api_indexer_service.go (3 hunks)
  • indexer/rest/service/model_get_vtxos_request.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • buf.gen.yaml
  • go.mod
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T07:46:07.938Z
Learnt from: louisinger
Repo: arkade-os/go-sdk PR: 6
File: client.go:1194-1211
Timestamp: 2025-08-13T07:46:07.938Z
Learning: The listenForBoardingTxs() function in client.go is started from the Unlock method in base_client.go when WithTransactionFeed is enabled and UtxoMaxAmount is non-zero, not from the LoadArkClient methods.

Applied to files:

  • base_client.go
  • client.go
🧬 Code graph analysis (2)
base_client.go (4)
types/types.go (1)
  • Vtxo (80-94)
api-spec/protobuf/gen/ark/v1/types.pb.go (3)
  • Vtxo (128-145)
  • Vtxo (158-158)
  • Vtxo (173-175)
store/sql/sqlc/queries/models.go (1)
  • Vtxo (37-52)
indexer/opts.go (1)
  • GetVtxosRequestOption (22-30)
client.go (5)
client/client.go (2)
  • AcceptedOffchainTx (20-24)
  • Input (93-96)
types/types.go (1)
  • Vtxo (80-94)
api-spec/protobuf/gen/ark/v1/types.pb.go (6)
  • Vtxo (128-145)
  • Vtxo (158-158)
  • Vtxo (173-175)
  • Input (76-82)
  • Input (95-95)
  • Input (110-112)
store/sql/sqlc/queries/models.go (1)
  • Vtxo (37-52)
explorer/service.go (1)
  • Input (86-90)
⏰ 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). (2)
  • GitHub Check: Sdk unit tests
  • GitHub Check: Sdk integration tests
🔇 Additional comments (8)
client.go (3)

380-391: Centralizing finalization via finalizeTx looks good.

Reusing finalizeTx here removes duplication and ensures SendOffChain uses the same signing/finalization path as pending-tx finalization. Using the returned txid for the non-feed case is consistent, given it equals arkTxid.


911-930: finalizeTx error handling is now correct.

Signing failures now propagate as errors instead of returning ("", nil), so callers won’t treat failed finalizations as success. The helper cleanly encapsulates the finalize flow for both immediate and deferred use cases.


2213-2227: makeGetPendingTxIntent is consistent with existing intent builders.

The new GetPendingTx message wiring (message type + 10‑minute expiry) and delegation to makeIntent matches the pattern used for register/delete intents, so it keeps the intent construction logic centralized.

indexer/grpc/client.go (1)

352-360: PendingOnly wiring in gRPC GetVtxos is consistent.

Propagating opt.GetPendingOnly() into the GetVtxosRequest.PendingOnly field aligns with the existing spendable/spent/recoverable flags and correctly exposes the new filter over gRPC.

indexer/rest/client.go (1)

248-253: REST GetVtxos now correctly forwards PendingOnly.

Chaining .RecoverableOnly(opt.GetRecoverableOnly()).PendingOnly(opt.GetPendingOnly()) keeps the REST client in sync with the option struct and gRPC client, so all transports now support the pending-only filter.

indexer/rest/service/api_indexer_service.go (1)

1176-1187: Pending-only support in REST service client is wired correctly.

Adding pendingOnly *bool to ApiIndexerServiceGetVtxosRequest, the PendingOnly(bool) builder, and emitting the "pendingOnly" query parameter when non-nil mirrors the existing boolean filters and matches the OpenAPI contract.

Also applies to: 1219-1223, 1292-1294

api-spec/openapi/swagger/ark/v1/indexer.openapi.json (1)

654-661: OpenAPI pendingOnly additions are coherent with the new behavior.

Defining a pendingOnly query parameter on /v1/indexer/vtxos and adding the corresponding GetVtxosRequest.pendingOnly boolean property (both described as “Include only spent vtxos that are not finalized.”) cleanly exposes the new filter at the API level and aligns with the client implementations.

Also applies to: 982-985

indexer/rest/service/model_get_vtxos_request.go (1)

21-35: PendingOnly field and accessors follow the existing generated pattern.

The new PendingOnly *bool field, its Get*/Has*/Set* helpers, and the ToMap serialization branch are consistent with the other optional bool flags on GetVtxosRequest, so the model correctly supports emitting pendingOnly only when set.

Also applies to: 118-148, 286-296

@altafan altafan merged commit 74830e2 into master Nov 25, 2025
4 checks passed
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.

3 participants