feat: handles manual transaction approval#12
Conversation
| const payload = data?.code ? data : data?.message; | ||
| if (res.status === 403 && payload?.code === "APPROVAL_REQUIRED") { | ||
| const approvalId = payload.details?.approvalId ?? ""; | ||
| const approvalUrl = payload.details?.approvalUrl ?? ""; |
There was a problem hiding this comment.
Empty approvalId fallback creates broken approval flow
Medium Severity
When payload.details?.approvalId is missing from the server's 403 response, approvalId falls back to "". This empty string is passed to awaitApproval(""), registering a pending entry keyed by "". The subsequent SSE approval event will almost certainly carry a real ID that won't match "", so the promise silently times out after 5 minutes instead of failing fast. Multiple requests hitting this case would also collide on the "" key in the global pending map, causing "already being awaited" errors.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0096d06. Configure here.
|
|
||
| throw new BaseError(msg, { | ||
| details: data?.details, | ||
| }); |
There was a problem hiding this comment.
Non-string details passed to BaseError constructor
Low Severity
data?.details is passed as the details option to viem's BaseError, which expects string | undefined. Since data is any from res.json(), and server error responses may include details as an object (e.g., { approvalId, approvalUrl }), the error message would render Details: [object Object] — producing an unhelpful error for debugging.
Reviewed by Cursor Bugbot for commit 0096d06. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 559f5a9. Configure here.
| chatStream?.close(); | ||
| walletStream?.close(); | ||
| throw err; | ||
| } |
There was a problem hiding this comment.
Promise.all cleanup leaks successfully opened EventSource streams
Medium Severity
When Promise.all rejects (one stream fails to open), the await throws before the destructuring assignment [chatStream, walletStream] = ... executes, so both variables remain null. The catch block's chatStream?.close() and walletStream?.close() calls are therefore no-ops. If one openStream resolved successfully before the other rejected, the successfully opened EventSource is leaked with no reference to close it.
Reviewed by Cursor Bugbot for commit 559f5a9. Configure here.


Note
High Risk
High risk because it changes authentication signing from message-signing to EIP-712 typed data and modifies the Privy/Alchemy transaction submission path to support manual approval and new user-operation signing, impacting wallet security and transaction execution flow.
Overview
Switches agent/provider authentication from
signMessageto EIP-712 typed-data signatures via newcore/agentAuth.tsand updates all auth clients/context to sendissuedAt+ typed data instead of a raw message.Adds a manual approval gate (
core/approvalGate.ts) and a new SSEwalletstream alongside chat;SseTransport.connect()andAcpAgent.start()now accept configurableSupportedStreams(defaulting to chat+wallet) and the wallet stream resolves pending approvals.Updates
PrivyAlchemyEvmProviderAdapterto detect server-sideAPPROVAL_REQUIREDresponses, wait for approval events before continuing, and changes call submission to a prepare/sign-user-operation/send-prepared flow using a configuredALCHEMY_SIGNING_CONTRACT.Reviewed by Cursor Bugbot for commit 1ec514b. Bugbot is set up for automated code reviews on this repo. Configure here.