feat: add ThruBox relay service layer for encrypted invoice delivery - #194
feat: add ThruBox relay service layer for encrypted invoice delivery#194Atharva0506 wants to merge 1 commit into
Conversation
|
Warning Review limit reached
Next review available in: 28 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
WalkthroughThe frontend adds relay configuration, wallet-derived ECIES keys, encrypted invoice messaging, deterministic invoice hashes, mailbox polling, and React integration for key registration. ChangesRelay invoice flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Wallet
participant useRelayKeys
participant relayKeyManager
participant relayInvoiceMessaging
participant invoiceCrypto
participant RelayClient
Wallet->>useRelayKeys: provide account and wallet client
useRelayKeys->>relayKeyManager: derive or restore relay key pair
relayInvoiceMessaging->>invoiceCrypto: encrypt invoice envelope
relayInvoiceMessaging->>RelayClient: submit encrypted mailbox message
RelayClient-->>relayInvoiceMessaging: return mailbox messages
relayInvoiceMessaging->>invoiceCrypto: decrypt and filter messages
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 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 `@frontend/.env.example`:
- Around line 24-25: Update the comment above VITE_RELAY_API_KEY to explicitly
state that Vite exposes VITE_-prefixed variables in the client bundle, so this
key is visible to visitors and cannot be treated as a secret or application
access restriction. Mention that private keys require routing relay requests
through a same-origin server-side proxy; otherwise document the browser-exposure
limitation.
In `@frontend/package.json`:
- Line 17: Update the `@aossie-org/thrubox-client` dependency entry in
package.json to use the exact compatible version rather than the caret range,
and pin any related eciesjs dependency to the relay-compatible version so
encryption behavior cannot drift across installs.
In `@frontend/src/hooks/useRelayKeys.js`:
- Around line 142-152: Update the error handling in useRelayKeys to store stable
i18n error codes in the returned error state instead of user-visible English
strings. Add translations for both the default registration failure and
rejected-signature messages to the existing i18n resource files, then translate
the stored codes at component render time while preserving the current rejection
detection behavior.
- Around line 52-73: Update the rehydrate effect in useRelayKeys to use a
cache-only restoration helper that checks memory and sessionStorage without
deriving keys or invoking signMessage. Ensure it respects rememberSession,
avoids restoring stale or unavailable cache entries, and only updates
setKeys/setHasKeys when cached keys are found; replace the deriveRelayKeyPair
call in this effect while leaving normal key derivation behavior unchanged.
In `@frontend/src/services/relay/invoiceHashUtils.js`:
- Around line 47-49: Update the array handling in stableStringify so undefined
elements and sparse array slots are serialized as null, matching
invoiceCrypto.js JSON semantics and keeping [] distinct from [undefined]. Add
regression coverage for both an explicit undefined entry and a sparse array.
In `@frontend/src/services/relay/relayInvoiceMessaging.js`:
- Around line 30-38: Update buildEnvelope to validate chainId is present and
numeric, and invoiceId is neither null nor undefined, before converting or
constructing the envelope. Fail fast with explicit errors for invalid inputs,
while preserving the existing normalized chainId and stringified invoiceId
fields for valid values.
- Around line 90-109: Update decodeMessages so the returned sender field is
named claimedFrom instead of from, making clear that message.from is
unauthenticated relay metadata. Document in the
sendEncryptedInvoice/decodeMessages contract that callers must verify
envelope.data against the on-chain invoiceDataHash for envelope.invoiceId before
trusting or using the claimed sender.
In `@frontend/src/services/relay/relayKeyManager.js`:
- Around line 85-88: Remove privateKey persistence from the rememberSession
branch in the relay key-management flow, including the cacheKeysToSession call
from relay key derivation. Keep the ECIES private key memory-only so reloads
require a new signature; if persistence is necessary, replace browser storage
with expo-secure-store and do not enable it by default in useRelayKeys.
In `@frontend/tests/services/relayInvoiceMessaging.test.js`:
- Around line 186-196: Update the pollInvoiceMessages test helper capturePoll to
retain relayApi.poll’s third options argument, then add tests covering
intervalMs forwarding, onError delegation, and suppression of errors after
stopPolling(). Use the captured callback and stop handle to assert each behavior
while preserving existing message polling coverage.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 068ae92f-d400-4954-9b5c-999bed4f9eef
📒 Files selected for processing (14)
frontend/.env.examplefrontend/jest.config.cjsfrontend/package.jsonfrontend/src/hooks/useRelayKeys.jsfrontend/src/services/relay/index.jsfrontend/src/services/relay/invoiceCrypto.jsfrontend/src/services/relay/invoiceHashUtils.jsfrontend/src/services/relay/relayClient.jsfrontend/src/services/relay/relayInvoiceMessaging.jsfrontend/src/services/relay/relayKeyManager.jsfrontend/tests/services/invoiceCrypto.test.jsfrontend/tests/services/invoiceHashUtils.test.jsfrontend/tests/services/relayInvoiceMessaging.test.jsfrontend/vite.config.js
40d6e50 to
089ff04
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@frontend/src/hooks/useRelayKeys.js`:
- Around line 87-102: Update checkRegistration in the useRelayKeys hook to track
the latest address with a ref synchronized by useEffect, capture the address
used for the chain read, and discard the resolved result when it no longer
matches the current address before calling setIsRegistered or returning the
registration state.
In `@frontend/src/services/relay/relayInvoiceMessaging.js`:
- Around line 189-198: Update pollInvoiceMessages in
frontend/src/services/relay/relayInvoiceMessaging.js:189-198 so
seen.add(item.messageId) runs only after await onInvoice(item) succeeds,
allowing failed handlers to be retried. Update
frontend/tests/services/relay/relayInvoiceMessaging.test.js:387-408 to assert
the delivered messageId sequence instead of only call count and add coverage
proving a failed message is delivered again on the next poll.
In `@frontend/tests/services/invoiceHashUtils.test.js`:
- Around line 42-68: Add a test in the existing array edge-case describe block
that serializes a representative object through JSON.stringify and JSON.parse,
then compares stableStringify values before and after the round trip to verify
the sender/receiver hash contract. Include values such as Date and bigint to
cover types that stableStringify may represent but relay JSON cannot preserve,
while keeping the test focused on detecting mismatched hashes.
In `@frontend/tests/services/relayInvoiceMessaging.test.js`:
- Around line 387-408: Update the “keeps polling when a handler throws” test to
assert the delivered invoice IDs, ensuring the failed m1 is retried before m2 on
the next poll. Add or adjust a focused case that verifies a handler failure
leaves that message eligible for retry on the subsequent poll, rather than only
asserting onInvoice’s call count.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3dd65896-02be-4a52-8ba1-4aaa6c74f929
📒 Files selected for processing (8)
frontend/.env.examplefrontend/package.jsonfrontend/src/hooks/useRelayKeys.jsfrontend/src/services/relay/invoiceHashUtils.jsfrontend/src/services/relay/relayInvoiceMessaging.jsfrontend/src/services/relay/relayKeyManager.jsfrontend/tests/services/invoiceHashUtils.test.jsfrontend/tests/services/relayInvoiceMessaging.test.js
089ff04 to
8c3003c
Compare
8c3003c to
3c4303d
Compare
There was a problem hiding this comment.
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 `@frontend/src/hooks/useRelayKeys.js`:
- Around line 56-61: Update the cleanup logic in useRelayKeys so clearCachedKeys
runs only when the wallet address actually transitions to disconnected, not on
ordinary unmounts or address changes. Preserve the shared/session cache across
StrictMode remounts, route unmounts, and multiple hook instances; use a ref or
equivalent lifecycle tracking to reliably compare the previous and current
address while respecting effect cleanup ordering.
- Around line 68-69: Replace the computed import.meta.env access in the relay
key lookup with a static contract-address map keyed by chainId, following the
existing frontend pattern. Read the address from that map in the surrounding
lookup flow, retain the null return when no address is configured, and ensure
all environment keys are statically referenced for Vite replacement.
In `@frontend/src/services/relay/relayClient.js`:
- Around line 64-69: Remove the privileged-key usage from the browser-side
RelayClient initialization in resolveBaseUrl/client setup, or replace it with a
server-side proxy that injects the key; do not read a secret from
VITE_RELAY_API_KEY. If the relay key is intentionally public and used only for
rate limiting, document that constraint in frontend/.env.example and ensure no
sensitive credential is exposed.
In `@frontend/src/services/relay/relayKeyManager.js`:
- Around line 161-175: Update getSessionCachedKeys to validate the byte lengths
of both decoded keys against the required key sizes before returning the pair;
return null when either length is invalid so getCachedKeyPair does not promote
corrupted data and the next call re-derives the keys.
- Around line 181-191: Update clearCachedKeys and the derivation completion path
to track a per-address generation or cleared marker, incrementing or setting it
when keys are cleared and capturing its value when derivation starts. Before the
pending derivation writes to memoryCache or sessionStorage, verify that its
captured generation is still current; skip both writes when clearCachedKeys has
invalidated it.
In `@frontend/tests/services/relayInvoiceMessaging.test.js`:
- Around line 313-330: Add an overlapping-poll test alongside “emits each
message only once across polls” that starts two pump(batch) calls before
awaiting either promise, then awaits both and asserts onInvoice was called once.
Keep the same mailbox batch and receiver setup so the test verifies message
claiming occurs before the asynchronous handler completes.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: cd8cd6a6-3585-4f9e-a343-93fe9b7ac877
📒 Files selected for processing (7)
frontend/src/hooks/useRelayKeys.jsfrontend/src/services/relay/index.jsfrontend/src/services/relay/relayClient.jsfrontend/src/services/relay/relayInvoiceMessaging.jsfrontend/src/services/relay/relayKeyManager.jsfrontend/tests/services/invoiceHashUtils.test.jsfrontend/tests/services/relayInvoiceMessaging.test.js
3c4303d to
47ee6f1
Compare
There was a problem hiding this comment.
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 `@frontend/src/hooks/useRelayKeys.js`:
- Around line 74-81: The getContract/checkRegistration flow must distinguish a
disconnected wallet from an unsupported chain with no configured contract
address. Update getContract and its callers so missing wallet or chain remains a
disconnected state, while an absent VITE_CONTRACT_ADDRESS_${chainId} produces a
dedicated unsupported-network result; ensure checkRegistration and the
registration UI do not set isRegistered=false or offer registration for that
unsupported state.
- Around line 123-126: Update the catch block in the registry-read flow around
isStale and setIsRegistered to capture the thrown error and log it with
sufficient context before returning false, while preserving the existing
stale-state behavior. Ensure the failed RPC read is distinguishable from a valid
unregistered result.
In `@frontend/src/services/relay/relayKeyManager.js`:
- Around line 292-298: Update fetchPublicKeyFromChain to validate the registry
key using the same expected size and prefix requirements enforced by
getSessionCachedKeys before calling hexToBytes. Return null or otherwise reject
malformed keys so invalid entries cannot reach encryptPayload or downstream key
comparisons.
In `@frontend/tests/services/relayInvoiceMessaging.test.js`:
- Around line 73-75: Update the “throws on a missing address” test around
toMailboxAddress to use a regex error-message matcher like the chainId and
invoiceId validation tests, and add equivalent cases for null and undefined
alongside the empty string.
- Around line 79-96: Update the test for sendEncryptedInvoice to decrypt
sent.payload using receiver.privateKey and assert the resulting envelope,
including invoiceId as String(invoiceId). Validate the decrypted envelope’s
expected fields so the test covers buildEnvelope output, while retaining the
existing participant address assertions.
- Around line 461-481: Add a test in the pollInvoiceMessages suite covering a
stop triggered while processing a batch: have the first onInvoice handler invoke
the returned stop mechanism, then verify subsequent messages in the same batch
are not handled. Keep the existing later-message-after-error test unchanged and
assert the expected handler calls using the existing capturePoll and
relayMessage helpers.
🪄 Autofix
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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1e108b17-e545-4780-a0a1-6816d26a02ff
📒 Files selected for processing (3)
frontend/src/hooks/useRelayKeys.jsfrontend/src/services/relay/relayKeyManager.jsfrontend/tests/services/relayInvoiceMessaging.test.js
rohans02
left a comment
There was a problem hiding this comment.
reviewed, no blocking issues. But address the coderabbit comments, if they aren't relevant resolve them with reason.
47ee6f1 to
5d013ef
Compare
Waku supplied two things: end-to-end encryption and a transport. ThruBox is a dumb mailbox that supplies only the transport, so this adds both halves as an independent service layer: invoiceCrypto.js ECIES over secp256k1 (eciesjs), base64 framing relayKeyManager.js keypair derivation + on-chain key registry relayInvoiceMessaging.js send / fetch / poll of encrypted envelopes relayClient.js configured ThruBox client singleton invoiceHashUtils.js deterministic keccak256 commitment over payloads Key derivation is unchanged from the Waku implementation, deliberately: keys are already registered in the on-chain registry, and a different derivation message would silently break decryption for those users. The same 65-byte uncompressed public keys the contract already validates are what ECIES consumes here, so no contract change is needed. Three relay behaviours drove the design: - Address lookup is an exact string match, so a message sent to a checksummed address is invisible to a client polling the lowercase form. Every address crossing the boundary is normalised. - There is no cursor; each poll returns the whole mailbox. The poller tracks message IDs it has already surfaced rather than deleting from the relay, so a second device can still collect an invoice before it expires. - The SDK does not retry POSTs, since a retried send would duplicate the message. One timed-out send is therefore one undelivered invoice, so the timeout is configurable via VITE_RELAY_TIMEOUT_MS for hosts that suspend idle instances and cold-start slowly. The relay serves no CORS headers, so a browser cannot reach it cross-origin: the preflight for POST /api/messages is rejected outright and GET responses carry no Access-Control-Allow-Origin. A Vite dev proxy on /relay keeps development requests same-origin; production needs CORS on the relay or a reverse proxy in front of it. Nothing is wired into the invoice pages yet, so behaviour is unchanged.
5d013ef to
a6ea0f5
Compare
|
Please resolve the merge conflicts before review. Your PR will only be reviewed by a maintainer after all conflicts have been resolved. 📺 Watch this video to understand why conflicts occur and how to resolve them: |
|
Closing as superseded — this PR has nothing left to contribute. It was part 2 of a 5-part stack, and #195 was stacked on top of it, so merging #195 brought this branch's entire contribution onto The conflict is not a merge that needs resolving. This branch is now behind Nothing is lost by closing. #196 and #197 carry the rest of the stack and are both rebased onto current |
Addressed Issues:
Part of #139 (2 of 5). Does not close it on its own.
Screenshots/Recordings:
Not applicable — this PR is purely additive and is not wired into any page yet, so there is no visible change. The full flow is demonstrated in #195.
Additional Notes:
Waku supplied two things: end-to-end encryption and a transport. ThruBox is a dumb mailbox that supplies only the transport, so this adds both halves as an independent service layer:
invoiceCrypto.jseciesjs), base64 framingrelayKeyManager.jsrelayInvoiceMessaging.jsrelayClient.jsinvoiceHashUtils.jsNothing is wired into the invoice pages yet, so application behaviour is unchanged. This PR is safe to land on its own.
Design notes
Key derivation is unchanged from the Waku implementation, deliberately. Keys are already registered in the on-chain registry, and a different derivation message would silently break decryption for those users. The same 65-byte uncompressed public keys the contract already validates are what ECIES consumes, so no contract change is needed.
Three relay behaviours drove the design, each verified against a running instance:
VITE_RELAY_TIMEOUT_MSfor hosts that suspend idle instances and cold-start slowly.CORS
The relay serves no CORS headers, so a browser cannot reach it cross-origin: the preflight for
POST /api/messagesis rejected outright and GET responses carry noAccess-Control-Allow-Origin. Two supported ways around it:/relayin development; in production setVITE_RELAY_URLto a path (/relay) and let the host rewrite it (Vercel rewrites, Netlify redirects, nginx). The browser stays same-origin and no CORS is needed.VITE_RELAY_URL, which requires CORS support on the relay.Tests
19 new unit tests covering the crypto round-trip, envelope decoding, chain filtering, address normalisation, and poll deduplication.
Review order: independent of #193; can land in either order. Required by #195.
AI Usage Disclosure:
Check one of the checkboxes below:
I have used the following AI models and tools: Claude Code (CLI), model Claude Opus 5
Checklist
Summary by CodeRabbit