feat(rewards): authenticate reward-code creates via signed transactions#822
Merged
Conversation
Switches POST /v1/rewards/code from signMessage-based auth to a signed Solana transaction containing a single Memo Program instruction whose data is the millisecond-epoch timestamp. Motivation: Phantom (and other browser wallet extensions) cannot reliably sign off-chain messages with a Ledger hardware wallet — the device either rejects with 0x6a81 or returns bytes that don't verify against any standard SIMD-0048 envelope on the server. Transaction signing is the wallet code path every wallet supports uniformly (hot wallets, Phantom + Ledger, Solflare + Ledger, mobile, etc.), so using it as the signing envelope eliminates the entire problem class. The transaction is never submitted on-chain. The server: - decodes the base64 transaction - cryptographically verifies the signature via tx.VerifySignatures() - requires the fee-payer (account[0]) to be in RewardCodeAuthorizedKeys - extracts the millisecond timestamp from the first Memo instruction - enforces the existing 12h drift window and replay-protection (keyed on the transaction signature) Removes the now-unused SIMD-0048 wrapping helper, signMessage verification helpers, and the legacy timestamp+signature fields. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
raymondjacobson
added a commit
that referenced
this pull request
May 18, 2026
#825) ## Summary Removes `9WXR4YNXhG5PYfp4bD1uzFw1TNgrX2yKid5T1mcssKyF` from `RewardCodeAuthorizedKeys`. This wallet was added in #821 purely to reproduce the Phantom+Ledger `signMessage` failure against the prod API while debugging. Now that the transaction-based auth path (#822) is live in prod and the real partner wallets are sufficient, the test wallet should not stay on the allowlist. The two intentional partner wallets remain authorized. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Switches
POST /v1/rewards/codefromsignMessage-based auth to authentication via a signed Solana transaction containing a Memo Program instruction whose data is the millisecond-epoch timestamp.Motivation: Phantom (and other browser wallet extensions) cannot reliably sign off-chain messages with Ledger hardware wallets. The device either rejects (status
0x6a81) or returns bytes that don't verify against any standard SIMD-0048 envelope on the server. Transaction signing is the wallet code path every wallet supports uniformly — hot wallets, Phantom + Ledger, Solflare + Ledger, mobile, etc. — so using a throwaway transaction as the signing envelope eliminates the entire problem class.The transaction is never submitted on-chain.
Server-side flow
tx.VerifySignatures()for the cryptographic checkAccountKeys[0]) to be inRewardCodeAuthorizedKeysRemoves the now-unused SIMD-0048 wrapping helper,
signMessageverification helpers, and the legacytimestamp+signaturerequest fields.Pairs with
AudiusProject/gift-rewardsPR (frontend switched tosignTransaction+ memo)Test plan
go test ./api/ -run 'TestGenerateCode|TestExtractMemoTimestamp'— passes locally; new tests cover memo extraction, invalid memo, missing memo.TestV1CreateRewardCode/*(require DB) run in CI; cover happy path, unauthorized signer, missing field, invalid base64, stale timestamp, and replay rejection.