Skip to content

Feat/153 chunked proof upload#156

Merged
Dnreikronos merged 8 commits into
mainfrom
feat/153-chunked-proof-upload
May 6, 2026
Merged

Feat/153 chunked proof upload#156
Dnreikronos merged 8 commits into
mainfrom
feat/153-chunked-proof-upload

Conversation

@Dnreikronos

@Dnreikronos Dnreikronos commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added chunked proof upload capability, allowing proofs to be uploaded incrementally in multiple chunks rather than as a single operation.
    • Implemented multi-step workflow for transfer hook payloads with separate initialization and finalization steps.
    • Enhanced error handling with additional error variants for improved validation and failure reporting.

ChunkOutOfBounds, PayloadAlreadyFinalized, PayloadNotReady,
ProofIncomplete — used by the init/write/finalize instruction
flow for multi-tx proof delivery.

Refs #153
Three new fields support the chunked proof upload flow:
expected_proof_len declares total size at init, high_water_mark
tracks the furthest byte written, finalized gates settlement.

Refs #153
…ctions

Three new instructions implement chunked proof upload: init allocates
the PDA with expected_proof_len, write copies chunks with bounds and
high-water-mark tracking, finalize sets metadata and flips the
finalized flag. Existing set_hook_payload sets finalized=true for
backward compatibility. Settlement now requires finalized=true.

Refs #153
Cover finalize input validation (zero nullifier, zero amount) and
high-water-mark tracking (sequential writes, overwrites, gaps,
partial-write-blocks-finalize).

Refs #153
Exports buildInitHookPayloadIx, buildWriteChunkIx,
buildFinalizeHookPayloadIx instruction builders plus an
uploadProofChunked orchestrator that batches 2 writes per tx.
CHUNK_SIZE=900 keeps each tx within Solana's 1,232-byte limit.

Refs #153
@Dnreikronos Dnreikronos self-assigned this May 6, 2026
@vercel

vercel Bot commented May 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
zksettle Ready Ready Preview, Comment May 6, 2026 2:02pm

@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Dnreikronos has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 51 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: accf338f-7c56-4c07-8bb4-ceb7a6dab727

📥 Commits

Reviewing files that changed from the base of the PR and between efb06e5 and 9089c03.

📒 Files selected for processing (6)
  • backend/programs/zksettle/src/error.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/handlers.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/mod.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/tests.rs
  • backend/programs/zksettle/src/lib.rs
  • sdk/src/wrap/index.ts
📝 Walkthrough

Walkthrough

The PR introduces a multi-step hook payload lifecycle for transfer hooks, replacing a single consolidated handler with three sequential steps: initialization, incremental proof writing, and finalization. Backend error variants and data structures expand to track proof state. The SDK gains chunked upload utilities and instruction builders to support this workflow.

Changes

Chunked Proof Upload Lifecycle

Layer / File(s) Summary
Data Shape & Types
backend/programs/zksettle/src/error.rs, backend/programs/zksettle/src/instructions/transfer_hook/types.rs, sdk/src/types.ts
ZkSettleError gains four variants (ChunkOutOfBounds, PayloadAlreadyFinalized, PayloadNotReady, ProofIncomplete). HookPayload struct adds expected_proof_len, high_water_mark, and finalized fields. SDK types ChunkedUploadResult and ChunkedUploadOptions introduced.
Core Handler Implementation
backend/programs/zksettle/src/instructions/transfer_hook/handlers.rs
Three new handlers replace the single set_hook_payload_handler: init_hook_payload_handler allocates and marks proof buffer with expected length; write_hook_proof_handler writes proof chunks and updates high-water mark with bounds checking; finalize_hook_payload_handler validates complete proof and populates core payload fields (nullifier, mint, recipient, amount, epoch, light_args).
Account Context Structs
backend/programs/zksettle/src/instructions/transfer_hook/mod.rs
Replaces SetHookPayload with three sequential context structs: InitHookPayload (initializes payload), WriteHookProof (prevents writes after finalization), FinalizeHookPayload (enforces non-finalized state and authority).
Settlement Validation
backend/programs/zksettle/src/instructions/transfer_hook/settlement.rs
run_settlement now requires payload to be finalized before processing; returns PayloadNotReady error otherwise.
RPC Integration
backend/programs/zksettle/src/lib.rs, backend/programs/zksettle/src/instructions/transfer_hook/mod.rs
Three new program entrypoints (init_hook_payload, write_hook_proof, finalize_hook_payload) expose handlers. Handler exports added to module public interface.
SDK Instruction Builders
sdk/src/wrap/index.ts
buildInitHookPayloadIx, buildWriteChunkIx, and buildFinalizeHookPayloadIx construct individual instructions. uploadProofChunked orchestrates the full flow: initializes, batches writes, signs/sends, and finalizes. CHUNK_SIZE constant defined. Helper makeProgram added for lazy program instantiation.
SDK Exports
sdk/src/index.ts
Exports expanded to include new builders, orchestration function, constant, and types from wrap and types modules.
Tests & Validation
backend/programs/zksettle/src/instructions/transfer_hook/tests.rs
HookPayload INIT_SPACE updated for new fields and Vec sizing. New finalize validation tests added (zero nullifier rejection, zero amount rejection, valid inputs acceptance).

Sequence Diagram

sequenceDiagram
    actor Client as Client/SDK
    participant Init as init_hook_payload<br/>(Handler)
    participant Write as write_hook_proof<br/>(Handler)
    participant Finalize as finalize_hook_payload<br/>(Handler)
    participant Settle as Settlement Flow

    Client->>Init: Init w/ expected_proof_len
    activate Init
    Init->>Init: Allocate proof buffer,<br/>set high_water_mark=0,<br/>finalized=false
    Init-->>Client: InitSignature
    deactivate Init

    loop For each chunk (1..N)
        Client->>Write: Write chunk @ offset
        activate Write
        Write->>Write: Bounds check,<br/>append to proof_and_witness,<br/>update high_water_mark
        Write-->>Client: ChunkSignature
        deactivate Write
    end

    Client->>Finalize: Finalize w/ metadata
    activate Finalize
    Finalize->>Finalize: Verify complete proof<br/>(high_water_mark ==<br/>expected_proof_len)
    Finalize->>Finalize: Set nullifier_hash, mint,<br/>recipient, amount,<br/>epoch, light_args,<br/>finalized=true
    Finalize-->>Client: FinalizeSignature
    deactivate Finalize

    Client->>Settle: Execute settlement
    activate Settle
    Settle->>Settle: Check: payload.finalized?
    alt Finalized
        Settle->>Settle: Process settlement
        Settle-->>Client: Success
    else Not Finalized
        Settle-->>Client: PayloadNotReady Error
    end
    deactivate Settle
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • yuribodo/zksettle#108: Extends and refactors the transfer-hook payload lifecycle (HookPayload struct, handlers) originally introduced in this PR.
  • yuribodo/zksettle#145: Main PR's SDK chunked-proof upload APIs and types directly build on and modify the same SDK wrap and index exports.
  • yuribodo/zksettle#61: Both PRs modify transfer-hook code paths and extend ZkSettleError enum with payload-related variants.

Suggested reviewers

  • SouzaGabriel26
  • ViniNathan

🐇 Proof chunks hop along with grace,
Init, write, then finalize in place,
Payload state tracked true and neat,
Chunked uploads make the flow complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Feat/153 chunked proof upload' directly corresponds to the main changes across both backend and SDK: introducing a chunked upload mechanism for proofs via new handlers and helper functions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/153-chunked-proof-upload

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/programs/zksettle/src/instructions/transfer_hook/handlers.rs`:
- Around line 82-102: The handler write_hook_proof_handler currently lets
clients write out-of-order chunks because it sets payload.high_water_mark =
max(...), so finalize can be bypassed; enforce sequential writes by requiring
the incoming offset equals the current payload.high_water_mark before copying
(return an error like ZkSettleError::ChunkOutOfOrder if not), then perform the
copy and set payload.high_water_mark = end_u32 (no max), which ensures
contiguous writes; update any callers/tests that assume out-of-order or
overlapping writes accordingly or implement a bitmap/contiguous-range tracker if
parallel/non-sequential uploads are needed.

In `@backend/programs/zksettle/src/instructions/transfer_hook/types.rs`:
- Around line 72-74: The finalize logic is wrong: checking only high_water_mark
== expected_proof_len allows gaps because write() may update high_water_mark
from non-sequential writes; modify the implementation so finalize verifies full
coverage instead of just the max end offset—either enforce sequential writes in
the write handler (reject writes whose start != current high_water_mark) or
maintain written-coverage (e.g., a bitmap/interval set) and have finalize check
that coverage spans [0, expected_proof_len); update references to
expected_proof_len, high_water_mark, finalized, the finalize handler and the
write handler (and adjust the gap_write_inflates_water_mark test accordingly) so
finalize only succeeds when every byte in [0, expected_proof_len) has been
written.

In `@sdk/src/wrap/index.ts`:
- Around line 162-178: The loop stride uses chunkSize derived from
opts.chunkSize without validation, which can produce non-terminating or
incorrect loops; validate and normalize chunkSize before the loop (e.g., read
opts.chunkSize into chunkSize, if it's undefined use CHUNK_SIZE, otherwise
coerce to a positive integer via Math.floor and ensure > 0), and if invalid
throw or fallback to CHUNK_SIZE; update the code around the chunkSize assignment
(symbols: chunkSize, CHUNK_SIZE, opts.chunkSize, proofBytes.length, and the for
loop using offset += chunkSize) so the for loop always advances by a positive
integer stride.
- Around line 70-85: The program client is being constructed with
makeProgram(connection) which ignores the caller-supplied programId, so builders
like buildInitHookPayloadIx, buildWriteChunkIx, buildFinalizeHookPayloadIx and
uploadProofChunked silently use the IDL's embedded address or hardcoded
ZKSETTLE_PROGRAM_ID; update makeProgram to accept a programId (e.g.,
makeProgram(connection, programId)) and use it when constructing the Anchor
Program, then call makeProgram(connection, programId) from
buildInitHookPayloadIx (and the other builders) and add a programId
parameter/override to uploadProofChunked (or alternatively remove the programId
parameters from the builders if you choose to enforce a single fixed ID); ensure
references to ZKSETTLE_PROGRAM_ID remain only as a default fallback and that
PDAs and Program use the same programId consistently.
- Line 68: The CHUNK_SIZE (export const CHUNK_SIZE) is too large for batched
writeHookProof instructions and causes transactions to exceed Solana's
~1232-byte limit; either reduce CHUNK_SIZE or disable batching. Fix by updating
CHUNK_SIZE to a safe value (e.g., ~600 bytes so that 2 * (16 + CHUNK_SIZE) <
1232, where 16 accounts for the 8-byte discriminator + 4-byte offset + 4-byte
length prefix) or change WRITES_PER_TX to 1 to send one writeHookProof per
transaction; ensure you modify the CHUNK_SIZE constant and/or the WRITES_PER_TX
usage where writeHookProof(offset, chunk) batching is assembled so instruction
sizes no longer exceed the limit.
- Around line 157-218: The uploadProofChunked flow (uploadProofChunked)
currently uses signAndSend which returns only a signature (initSignature /
chunkSignatures / finalizeSignature) and can cause races between
buildInitHookPayloadIx, buildWriteChunkIx and buildFinalizeHookPayloadIx; modify
the function to explicitly wait for on-chain confirmation before moving to the
next phase: after obtaining initSignature call the RPC confirmation (e.g.,
connection.confirmTransaction or equivalent) and only then build and send
writeIxs; after each chunk tx (each sig pushed to chunkSignatures) confirm that
signature before proceeding to the next batch; finally confirm all write
signatures before calling buildFinalizeHookPayloadIx and before sending
finalizeSignature; keep using opts.connection and the existing signAndSend but
add these confirmTransaction waits to ensure correct ordering and avoid changing
external signAndSend type.
🪄 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: c5deca45-c98a-4ef9-b358-e85e5da40f07

📥 Commits

Reviewing files that changed from the base of the PR and between da93d08 and efb06e5.

📒 Files selected for processing (10)
  • backend/programs/zksettle/src/error.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/handlers.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/mod.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/settlement.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/tests.rs
  • backend/programs/zksettle/src/instructions/transfer_hook/types.rs
  • backend/programs/zksettle/src/lib.rs
  • sdk/src/index.ts
  • sdk/src/types.ts
  • sdk/src/wrap/index.ts

Comment thread backend/programs/zksettle/src/instructions/transfer_hook/types.rs
Comment thread sdk/src/wrap/index.ts Outdated
Comment thread sdk/src/wrap/index.ts
Comment on lines +70 to +85
function makeProgram(connection: Connection): Program {
const dummyWallet = new Wallet(Keypair.generate());
const provider = new AnchorProvider(connection, dummyWallet, {});
return new Program(idl as any, provider);
}

export async function buildInitHookPayloadIx(
wallet: PublicKey,
proofLen: number,
connection: Connection,
programId = ZKSETTLE_PROGRAM_ID,
program?: Program,
): Promise<TransactionInstruction> {
const [issuerPda] = findIssuerPda(wallet, programId);
const [hookPayloadPda] = findHookPayloadPda(wallet, programId);
const prog = program ?? makeProgram(connection);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In @coral-xyz/anchor0.31.1 TypeScript, when constructingnew Program(idl, provider), is the program ID always taken from the IDL's embedded address/metadata?

💡 Result:

Yes—when constructing a Program client in Anchor TS using the 0.31.x style new Program(idl, provider), the program’s on-chain address is taken from the IDL’s top-level address field (not from metadata.address). This is because the newer Anchor TS API removed the separate programId constructor argument as redundant, since address is now required in the IDL. [1] Evidence: - Anchor’s TypeScript client IDL shape includes address: string as “The on-chain program address.” [2] - The Anchor TS PR notes that removing the programId constructor parameter is based on the fact that the address field is now required, and that the constructor will “use the program id from the IDL.” [1] So, in @coral-xyz/anchor 0.31.1 specifically, new Program(idl, provider) expects the program ID to come from idl.address. Note: Older/legacy IDL variants and conversions discuss metadata.address specifically, but that’s about legacy IDL formats and conversion requirements; the current required field for the TS Program constructor is the top-level address. [3][4]

Citations:


🏁 Script executed:

# First, let's look at the file to understand its structure and find the functions mentioned
head -n 200 sdk/src/wrap/index.ts

Repository: yuribodo/zksettle

Length of output: 5689


🏁 Script executed:

# Search for uploadProofChunked and ZKSETTLE_PROGRAM_ID usage
rg "uploadProofChunked|ZKSETTLE_PROGRAM_ID" sdk/src/wrap/index.ts -A 3 -B 2

Repository: yuribodo/zksettle

Length of output: 1460


🏁 Script executed:

# Search for PDA derivation functions (findIssuerPda, findHookPayloadPda) to see how they use programId
rg "findIssuerPda|findHookPayloadPda" sdk/src/ -A 5 -B 2

Repository: yuribodo/zksettle

Length of output: 5311


🏁 Script executed:

# Check the function signatures around the mentioned line numbers to understand the full context
sed -n '1,200p' sdk/src/wrap/index.ts | cat -n

Repository: yuribodo/zksettle

Length of output: 7089


Thread program ID consistently through chunked upload APIs, or remove the false override parameters.

PDAs are correctly derived from the caller-supplied programId (or default), but the Anchor Program client is constructed from the IDL's embedded address via new Program(idl, provider), which ignores the programId parameter entirely. This means:

  • buildInitHookPayloadIx, buildWriteChunkIx, and buildFinalizeHookPayloadIx accept programId but silently ignore it when constructing the program via makeProgram(connection).
  • uploadProofChunked hardcodes ZKSETTLE_PROGRAM_ID with no override option, so callers cannot target an alternative deployment.

Either pass programId to makeProgram() (and extend it to accept and apply a program ID), ensure the IDL's embedded address matches deployment expectations, or remove the redundant programId parameter from the builders to avoid false configurability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/wrap/index.ts` around lines 70 - 85, The program client is being
constructed with makeProgram(connection) which ignores the caller-supplied
programId, so builders like buildInitHookPayloadIx, buildWriteChunkIx,
buildFinalizeHookPayloadIx and uploadProofChunked silently use the IDL's
embedded address or hardcoded ZKSETTLE_PROGRAM_ID; update makeProgram to accept
a programId (e.g., makeProgram(connection, programId)) and use it when
constructing the Anchor Program, then call makeProgram(connection, programId)
from buildInitHookPayloadIx (and the other builders) and add a programId
parameter/override to uploadProofChunked (or alternatively remove the programId
parameters from the builders if you choose to enforce a single fixed ID); ensure
references to ZKSETTLE_PROGRAM_ID remain only as a default fallback and that
PDAs and Program use the same programId consistently.

Comment thread sdk/src/wrap/index.ts
Comment on lines +157 to +218
export async function uploadProofChunked(
opts: ChunkedUploadOptions,
signAndSend: (tx: Transaction) => Promise<string>,
): Promise<ChunkedUploadResult> {
const programId = ZKSETTLE_PROGRAM_ID;
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
const proofBytes = opts.proof;
const program = makeProgram(opts.connection);

const initIx = await buildInitHookPayloadIx(
opts.wallet,
proofBytes.length,
opts.connection,
programId,
program,
);
const initSignature = await signAndSend(new Transaction().add(initIx));

const writeIxs: TransactionInstruction[] = [];
for (let offset = 0; offset < proofBytes.length; offset += chunkSize) {
const end = Math.min(offset + chunkSize, proofBytes.length);
const chunk = proofBytes.slice(offset, end);
writeIxs.push(
await buildWriteChunkIx(
opts.wallet,
offset,
chunk,
opts.connection,
programId,
program,
),
);
}

const WRITES_PER_TX = 2;
const chunkSignatures: string[] = [];
for (let i = 0; i < writeIxs.length; i += WRITES_PER_TX) {
const tx = new Transaction();
for (const ix of writeIxs.slice(i, i + WRITES_PER_TX)) {
tx.add(ix);
}
const sig = await signAndSend(tx);
chunkSignatures.push(sig);
}

const epoch =
opts.transferContext.epoch ?? Math.floor(Date.now() / 1000 / 86400);
const finalizeIx = await buildFinalizeHookPayloadIx(
opts.wallet,
{
nullifierHash: opts.nullifierHash,
mint: opts.transferContext.mint,
epoch,
recipient: opts.transferContext.recipient,
amount: opts.transferContext.amount,
lightArgs: opts.lightArgs,
},
opts.connection,
programId,
program,
);
const finalizeSignature = await signAndSend(new Transaction().add(finalizeIx));

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 | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

cd sdk && find . -name "*.ts" -path "*/wrap/*" | head -20

Repository: yuribodo/zksettle

Length of output: 99


🏁 Script executed:

# Let me look at the full context around the function
cd sdk && sed -n '140,230p' src/wrap/index.ts

Repository: yuribodo/zksettle

Length of output: 2291


🏁 Script executed:

# Find where signAndSend type is defined
cd sdk && rg -A 5 "signAndSend.*function\|signAndSend.*=>" src/wrap/index.ts | head -40

Repository: yuribodo/zksettle

Length of output: 43


🏁 Script executed:

# Look for the type definition of ChunkedUploadOptions
cd sdk && rg -B 5 -A 10 "ChunkedUploadOptions" src/wrap/index.ts | head -60

Repository: yuribodo/zksettle

Length of output: 1037


🏁 Script executed:

# Check the build functions to see if they have dependencies on prior phases
cd sdk && rg -A 20 "buildInitHookPayloadIx|buildWriteChunkIx|buildFinalizeHookPayloadIx" src/wrap/index.ts | head -100

Repository: yuribodo/zksettle

Length of output: 2841


🏁 Script executed:

cd sdk && rg -B 3 -A 3 "uploadProofChunked" src/ --include="*.ts" | head -80

Repository: yuribodo/zksettle

Length of output: 503


🏁 Script executed:

# Check the full ChunkedUploadOptions type definition
cd sdk && rg -B 5 -A 15 "interface ChunkedUploadOptions\|type ChunkedUploadOptions" src/

Repository: yuribodo/zksettle

Length of output: 43


🏁 Script executed:

# Look for any documentation or comments around signAndSend
cd sdk && rg -B 10 "signAndSend.*=" src/wrap/index.ts | head -60

Repository: yuribodo/zksettle

Length of output: 317


🏁 Script executed:

# Check if there's a pattern elsewhere for confirming transactions
cd sdk && rg "confirmTransaction\|signAndConfirm" src/ --include="*.ts" | head -30

Repository: yuribodo/zksettle

Length of output: 503


🏁 Script executed:

cd sdk && rg "uploadProofChunked" src/ -A 3 -B 3 | head -100

Repository: yuribodo/zksettle

Length of output: 725


🏁 Script executed:

# Look for ChunkedUploadOptions definition
cd sdk && find . -name "*.ts" -type f | xargs grep -l "ChunkedUploadOptions" | head -5

Repository: yuribodo/zksettle

Length of output: 111


🏁 Script executed:

# Check types file
cd sdk && cat src/types.ts | head -150

Repository: yuribodo/zksettle

Length of output: 2808


🏁 Script executed:

# Search for confirmTransaction or similar confirmation patterns
cd sdk && rg "confirmTransaction|getSignatureStatus" src/ -A 2 | head -80

Repository: yuribodo/zksettle

Length of output: 43


Require confirmation between init, write, and finalize phases.

This workflow is stateful on-chain: initHookPayload creates the hookPayloadPda account, writeHookProof writes to it, and finalizeHookPayload reads from it. However, signAndSend only returns a signature string with no guarantee of on-chain confirmation. A caller can provide a sendTransaction-style function that returns immediately after broadcasting, allowing phase 2 writes and phase 3 finalization to execute before phase 1 is confirmed. This creates race conditions that cause silent failures and flaky behavior under load.

The API should either:

  • Change signAndSend type to require "send and confirm" semantics, or
  • Add explicit confirmTransaction calls within the helper before proceeding to the next phase.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/wrap/index.ts` around lines 157 - 218, The uploadProofChunked flow
(uploadProofChunked) currently uses signAndSend which returns only a signature
(initSignature / chunkSignatures / finalizeSignature) and can cause races
between buildInitHookPayloadIx, buildWriteChunkIx and
buildFinalizeHookPayloadIx; modify the function to explicitly wait for on-chain
confirmation before moving to the next phase: after obtaining initSignature call
the RPC confirmation (e.g., connection.confirmTransaction or equivalent) and
only then build and send writeIxs; after each chunk tx (each sig pushed to
chunkSignatures) confirm that signature before proceeding to the next batch;
finally confirm all write signatures before calling buildFinalizeHookPayloadIx
and before sending finalizeSignature; keep using opts.connection and the
existing signAndSend but add these confirmTransaction waits to ensure correct
ordering and avoid changing external signAndSend type.

Comment thread sdk/src/wrap/index.ts Outdated
SetHookPayload was identical to InitHookPayload (both init the PDA).
WriteHookProof was identical to FinalizeHookPayload (both mutate
non-finalized payload with issuer check). Consolidate into two shared
structs: InitHookPayload and ModifyHookPayload.
Reject writes where offset != high_water_mark to prevent gaps
that would let finalize pass with unwritten proof regions.
900-byte chunks exceed the ~1232-byte Solana txn limit when
batching 2 writeHookProof IXs. Co-locate CHUNK_SIZE and
WRITES_PER_TX with sizing rationale. Guard against zero or
fractional chunkSize causing infinite loops.
@sonarqubecloud

sonarqubecloud Bot commented May 6, 2026

Copy link
Copy Markdown

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.

1 participant