Preview/auth payment test#1
Conversation
- agent/activate/route.ts: 'active' -> 'ACTIVE' - agent/main/route.ts: 'active' -> 'ACTIVE' - agent/route.ts: 'inactive' -> 'INACTIVE' - lib/agent-flow.ts: AgentStatus type updated to match Prisma enum
- pi-sdk.ts: rewritten ensurePiSdk to await Pi.init() before checking Pi.authenticate - wallet-context.tsx: Pi.authenticate(scopes, callback) not object syntax - pi-wallet.ts: same fix - layout.tsx: added <Script> for SDK CDN load with beforeInteractive
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a DESIGN.md design system, normalizes AgentStatus to uppercase (adds SLEEPING) and updates agent APIs, refactors the Pi SDK and wallet authentication flow, injects the Minepi script in layout, updates auth-connect tests, and restyles Terms, Privacy, and dashboard UI with themed tokens. ChangesDesign System & Agent Status Normalization
Pi SDK Refactoring & Wallet Integration
Terms, Privacy & Dashboard UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
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 `@apps/axiomid/src/lib/agent-flow.ts`:
- Line 1: Update the AgentStatus handling to tolerate legacy lowercase persisted
values by adding a read-time normalizer and a one-time DB backfill: add a
function (e.g., normalizeAgentStatus) that maps any lowercase or mixed-case
status strings to the uppercase union (referencing the AgentStatus type) and
call it wherever agent records are deserialized or serialized for responses
(e.g., in any getAgent / serializeAgent / toApiPayload code paths) so returned
payloads always emit uppercase statuses; additionally create and run a migration
script that updates existing DB rows to uppercase the status column to
permanently fix persisted data.
In `@apps/axiomid/src/lib/pi-sdk.ts`:
- Around line 46-74: The cached Promise for loading the Pi SDK currently
resolves on script.onerror and can remain pending if the internal wait loop
never reaches a terminal state; modify the loader so that both the onerror
handler and the internal wait timeout path use a shared failure routine that
clears the top-level cached variable (so subsequent calls will recreate the
loader) and resolves/rejects the Promise consistently. Concretely, add a
deadline/timeout inside the wait() loop, implement a fail() helper that does
cached = undefined and then resolves (or rejects) the Promise, and call that
fail() from script.onerror, from p.init.catch, and when the wait loop exceeds
the deadline; keep references to the existing identifiers cached,
script.onerror, wait, p.init, and p.authenticate to locate where to change the
code.
- Around line 40-43: ensurePiSdk currently chains .then/.catch off pi.init(...)
but Pi.init is synchronous and returns void; replace those promise chains by
calling pi.init(...) inside a try/catch and set the shared cached Promise
appropriately. Specifically, in the fast path (where you reference cached and
call pi.init) remove the .then/.catch, call pi.init({ version: "2.0", sandbox:
!!sandbox }) inside try { ... } and set cached = Promise.resolve() on success
and cached = Promise.resolve() (or a resolved void promise) on error after
catching it (or optionally set to Promise.reject(err) if you want callers to see
init errors); do the same change in the script-load path where pi.init is
invoked so both branches do not chain off a void return and both set cached to a
proper Promise<void>.
In `@apps/axiomid/src/lib/pi-wallet.ts`:
- Around line 42-47: Update the TypeScript declaration for Pi.authenticate in
the global.d.ts to match the SDK's positional-argument signature: change the
current object-parameter overload to a function signature
Pi.authenticate(scopes: string[] | readonly string[], onIncompletePaymentFound?:
(payment: any) => void): Promise<any> (or the appropriate return type), ensuring
the parameter names and types match the usage in Pi.authenticate(scopes,
onIncompletePaymentFound) and existing calls in wallet-context.tsx and
pi-wallet.ts; keep any additional overloads if needed but ensure the positional
form is present so the compiler enforces the two-argument call shape.
🪄 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 Plus
Run ID: 5ea0335d-d840-4601-a0e6-b8d321ad34fc
📒 Files selected for processing (8)
DESIGN.mdapps/axiomid/src/app/api/agent/activate/route.tsapps/axiomid/src/app/api/agent/main/route.tsapps/axiomid/src/app/api/agent/route.tsapps/axiomid/src/app/context/wallet-context.tsxapps/axiomid/src/lib/agent-flow.tsapps/axiomid/src/lib/pi-sdk.tsapps/axiomid/src/lib/pi-wallet.ts
| @@ -1,4 +1,4 @@ | |||
| export type AgentStatus = 'inactive' | 'active' | 'paused'; | |||
| export type AgentStatus = 'INACTIVE' | 'ACTIVE' | 'PAUSED' | 'SLEEPING'; | |||
There was a problem hiding this comment.
Add a compatibility path for legacy lowercase status values.
The type contract is now uppercase-only, but read paths still pass through raw persisted status values. Existing lowercase rows from earlier writes can break this contract at runtime. Add a read-time normalizer and a DB backfill/migration so responses always emit uppercase statuses.
Suggested direction
+export function normalizeAgentStatus(status: string): AgentStatus {
+ switch (status?.toUpperCase()) {
+ case 'ACTIVE':
+ case 'INACTIVE':
+ case 'PAUSED':
+ case 'SLEEPING':
+ return status.toUpperCase() as AgentStatus;
+ default:
+ return 'INACTIVE';
+ }
+}Then apply this when returning agent payloads (or in API serializers), and run a one-time data migration to uppercase existing stored values.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export type AgentStatus = 'INACTIVE' | 'ACTIVE' | 'PAUSED' | 'SLEEPING'; | |
| export type AgentStatus = 'INACTIVE' | 'ACTIVE' | 'PAUSED' | 'SLEEPING'; | |
| export function normalizeAgentStatus(status: string): AgentStatus { | |
| switch (status?.toUpperCase()) { | |
| case 'ACTIVE': | |
| case 'INACTIVE': | |
| case 'PAUSED': | |
| case 'SLEEPING': | |
| return status.toUpperCase() as AgentStatus; | |
| default: | |
| return 'INACTIVE'; | |
| } | |
| } |
🤖 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 `@apps/axiomid/src/lib/agent-flow.ts` at line 1, Update the AgentStatus
handling to tolerate legacy lowercase persisted values by adding a read-time
normalizer and a one-time DB backfill: add a function (e.g.,
normalizeAgentStatus) that maps any lowercase or mixed-case status strings to
the uppercase union (referencing the AgentStatus type) and call it wherever
agent records are deserialized or serialized for responses (e.g., in any
getAgent / serializeAgent / toApiPayload code paths) so returned payloads always
emit uppercase statuses; additionally create and run a migration script that
updates existing DB rows to uppercase the status column to permanently fix
persisted data.
…ix auth-connect test
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
apps/axiomid/src/app/terms/page.tsx (1)
5-11: ⚡ Quick winExtract shared legal-page shell to avoid drift between Terms and Privacy.
BentoCard+ header/footer structure is duplicated here and inapps/axiomid/src/app/privacy/page.tsx(same classes/markup pattern). Consider moving this into a shared component (e.g.,LegalPageShell) so legal pages stay visually and behaviorally consistent as edits continue.Also applies to: 19-34, 106-111
🤖 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 `@apps/axiomid/src/app/terms/page.tsx` around lines 5 - 11, The BentoCard wrapper and repeated header/footer layout is duplicated between the Terms and Privacy pages; extract a shared component (e.g., LegalPageShell) that encapsulates the current BentoCard markup plus the common header and footer structure, export it, and replace the BentoCard + header/footer usages in both page components with this new LegalPageShell so both pages consume the same shell; update/rename the existing BentoCard (or remove it) and ensure the pages pass their unique body content as children to LegalPageShell and preserve any className/prop forwarding (refer to BentoCard and the Terms/Privacy page components for locations to change).
🤖 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 `@apps/axiomid/src/app/context/wallet-context.tsx`:
- Around line 77-89: The code obtains a Pi accessToken from authenticatePi
(accessToken, piUser, walletAddress) but later POSTs to the agent endpoints
without including accessToken, causing 401s for pi:* wallets; update the fetch
calls that create/activate agents (the POSTs to the agent routes) to include the
accessToken (either in the JSON body alongside uid/username/walletAddress or as
an Authorization header) so the Pi-protected endpoints can validate the request
(refer to variables accessToken, piUser.uid, walletAddress and the fetch calls
that contact the agent and agent/activate routes).
- Around line 61-63: The current logic sets shouldUsePi = isPiBrowser() ||
isSandbox(), which causes sandbox previews to force Pi auth for all visitors and
then auto-runs connectWallet(); change the gate so sandbox alone does not enable
Pi: only enable Pi when isPiBrowser() is true OR when sandbox is true AND an
explicit runtime gate is present (e.g., a query param or env flag), and wrap the
connectWallet() auto-invoke (the block calling connectWallet()) behind that
stricter condition; update references to shouldUsePi, isPiBrowser(),
isSandbox(), and the connectWallet() call so sandbox no longer implicitly flips
users into Pi flow.
In `@apps/axiomid/src/app/privacy/page.tsx`:
- Line 60: Replace the incorrect phrase "personal identifiable information" in
the privacy copy with "personally identifiable information" (or "personal
information") in the JSX string inside the privacy page component (the privacy
text shown in page.tsx), updating the sentence that currently reads "We do not
collect personal identifiable information..." to "We do not collect personally
identifiable information..." to fix the wording.
---
Nitpick comments:
In `@apps/axiomid/src/app/terms/page.tsx`:
- Around line 5-11: The BentoCard wrapper and repeated header/footer layout is
duplicated between the Terms and Privacy pages; extract a shared component
(e.g., LegalPageShell) that encapsulates the current BentoCard markup plus the
common header and footer structure, export it, and replace the BentoCard +
header/footer usages in both page components with this new LegalPageShell so
both pages consume the same shell; update/rename the existing BentoCard (or
remove it) and ensure the pages pass their unique body content as children to
LegalPageShell and preserve any className/prop forwarding (refer to BentoCard
and the Terms/Privacy page components for locations to change).
🪄 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 Plus
Run ID: 1736ef84-08c5-422e-9925-9382aa4f8552
📒 Files selected for processing (6)
apps/axiomid/src/app/api/__tests__/auth-connect.test.tsapps/axiomid/src/app/context/wallet-context.tsxapps/axiomid/src/app/layout.tsxapps/axiomid/src/app/privacy/page.tsxapps/axiomid/src/app/terms/page.tsxapps/axiomid/src/lib/pi-sdk.ts
💤 Files with no reviewable changes (1)
- apps/axiomid/src/app/api/tests/auth-connect.test.ts
| const { accessToken, user: piUser } = await authenticatePi(["username"]); | ||
|
|
||
| piUid = auth.user.uid; | ||
| piUsername = auth.user.username; | ||
| accessToken = auth.accessToken; | ||
| walletAddress = `pi:${piUid}`; | ||
| } else { | ||
| debug("using demo wallet"); | ||
| // Reuse stored wallet if available, otherwise generate new demo wallet | ||
| const storedWallet = localStorage.getItem("axiomid_wallet"); | ||
| if (storedWallet && storedWallet.startsWith("demo:")) { | ||
| walletAddress = storedWallet; | ||
| } else { | ||
| walletAddress = `demo:${crypto.randomUUID().slice(0, 8)}`; | ||
| } | ||
| } | ||
| const walletAddress = `pi:${piUser.uid}`; | ||
|
|
||
| debug("saving wallet to localStorage", walletAddress); | ||
| localStorage.setItem("axiomid_wallet", walletAddress); | ||
| if (accessToken) localStorage.setItem("axiomid_token", accessToken); | ||
|
|
||
| const endpoint = usePi ? "/api/auth/pi" : "/api/auth/connect"; | ||
| const body = usePi | ||
| ? JSON.stringify({ accessToken, uid: piUid, walletAddress, username: piUsername }) | ||
| : JSON.stringify({ walletAddress, piUid, piUsername, accessToken }); | ||
| debug(`POST ${endpoint}`, { walletAddress, hasAccessToken: !!accessToken }); | ||
| const res = await fetch(endpoint, { | ||
| const res = await fetch("/api/auth/pi", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body, | ||
| body: JSON.stringify({ | ||
| accessToken, | ||
| uid: piUser.uid, | ||
| username: piUser.username, | ||
| walletAddress, | ||
| }), |
There was a problem hiding this comment.
Pass the Pi access token to the agent APIs again.
Lines 77-89 obtain accessToken, but Lines 164-165 and 180-181 no longer send it to the Pi-protected agent routes. apps/axiomid/src/app/api/agent/route.ts:5-29 and apps/axiomid/src/app/api/agent/activate/route.ts:4-31 still reject pi:* wallets when accessToken is missing, so authenticated Pi users will now get 401s when creating or activating an agent.
Also applies to: 164-165, 180-181
🤖 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 `@apps/axiomid/src/app/context/wallet-context.tsx` around lines 77 - 89, The
code obtains a Pi accessToken from authenticatePi (accessToken, piUser,
walletAddress) but later POSTs to the agent endpoints without including
accessToken, causing 401s for pi:* wallets; update the fetch calls that
create/activate agents (the POSTs to the agent routes) to include the
accessToken (either in the JSON body alongside uid/username/walletAddress or as
an Authorization header) so the Pi-protected endpoints can validate the request
(refer to variables accessToken, piUser.uid, walletAddress and the fetch calls
that contact the agent and agent/activate routes).
| Information We Collect | ||
| </h2> | ||
| <p> | ||
| When you connect your wallet via Pi Network, we collect your Pi wallet address and Pi Network UID for the purpose of authentication and XP tracking. We do <strong className="text-neon-green">not</strong> collect personal identifiable information such as your name, email, or IP address beyond standard server logs. |
There was a problem hiding this comment.
Fix wording in privacy copy at Line 60.
"personal identifiable information" should be "personally identifiable information" (or "personal information") to avoid legal-text ambiguity.
🤖 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 `@apps/axiomid/src/app/privacy/page.tsx` at line 60, Replace the incorrect
phrase "personal identifiable information" in the privacy copy with "personally
identifiable information" (or "personal information") in the JSX string inside
the privacy page component (the privacy text shown in page.tsx), updating the
sentence that currently reads "We do not collect personal identifiable
information..." to "We do not collect personally identifiable information..." to
fix the wording.
| @@ -691,7 +691,7 @@ export default function Dashboard() { | |||
|
|
|||
| <button | |||
| onClick={handleToggleAgentState} | |||
| className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/10 hover:border-[#00ff41]/30 hover:bg-[#00ff41]/10 hover:text-[#00ff41] text-xs font-mono text-white transition-all text-center" | |||
| className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/10 hover:border-neon-green/30 hover:bg-neon-green/10 hover:text-neon-green text-xs font-mono text-white transition-all text-center" | |||
| > | |||
| {user.agent.status === "active" ? "■ PAUSE RUNTIME" : "▶ ACTIVATE RUNTIME"} | |||
There was a problem hiding this comment.
Stale lowercase status comparison breaks agent UI
The agent routes were updated to write uppercase values ("ACTIVE", "INACTIVE"), but these two comparisons still use the old lowercase "active". As a result, every user's agent badge will always render with the gray/inactive style and the button will always read "▶ ACTIVATE RUNTIME" — even when the agent is actually ACTIVE. Fix by changing both comparisons to === "ACTIVE".
…t fallback, dynamic isPiBrowser state
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/axiomid/src/app/context/wallet-context.tsx (1)
179-209:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winMissing
accessTokenfor Pi wallets will cause 401 errors on agent APIs.
createAgentandactivateAgentsend onlywalletAddress(andname), butapps/axiomid/src/app/api/agent/route.tsand/api/agent/activate/route.tsrequireaccessTokenfor wallets starting withpi:and return 401 if it's missing. Pi wallet users will be unable to create or activate agents.The
accessTokenobtained duringconnectWallet(line 92) needs to be stored (in state or localStorage) and included in these requests.Proposed fix direction
- Add state to store the Pi access token:
+ const [piAccessToken, setPiAccessToken] = useState<string | null>(null);
- Store it during Pi auth in
connectWallet:const { accessToken, user: piUser } = await authenticatePi(["username"]); + setPiAccessToken(accessToken);
- Include it in agent API calls:
body: JSON.stringify({ walletAddress: user.walletAddress, name, + accessToken: piAccessToken }),Alternatively, store the token in localStorage alongside the wallet address and retrieve it when needed.
🤖 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 `@apps/axiomid/src/app/context/wallet-context.tsx` around lines 179 - 209, createAgent and activateAgent omit the Pi wallet accessToken, causing 401s for wallets starting with "pi:"; update the wallet context to persist the accessToken obtained in connectWallet (store in component state or localStorage) and include that accessToken when calling /api/agent and /api/agent/activate (e.g., send accessToken alongside walletAddress and name or include in Authorization header) so createAgent and activateAgent pass the token to the API.
🤖 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.
Outside diff comments:
In `@apps/axiomid/src/app/context/wallet-context.tsx`:
- Around line 179-209: createAgent and activateAgent omit the Pi wallet
accessToken, causing 401s for wallets starting with "pi:"; update the wallet
context to persist the accessToken obtained in connectWallet (store in component
state or localStorage) and include that accessToken when calling /api/agent and
/api/agent/activate (e.g., send accessToken alongside walletAddress and name or
include in Authorization header) so createAgent and activateAgent pass the token
to the API.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 504db143-9dfb-4cbc-9200-40dd7de1faa9
📒 Files selected for processing (2)
apps/axiomid/src/app/context/wallet-context.tsxapps/axiomid/src/lib/pi-sdk.ts
…tion hooks, PiButton, payment lifecycle routes - Rewrite pi-sdk.ts with PiSdkBase.connect() for reliable init→authenticate - Create usePiConnection/usePiPurchase hooks wrapping PiSdkBase - Create PiButton.tsx component from official template - Rewrite wallet-context to use connectPi with isPiSdkLoaded detection - Add cancel/error/incomplete payment API routes - Set paymentBasePath = 'api/pi/payment'
|
|
||
| export async function POST(request: Request) { | ||
| try { | ||
| const { paymentId, transactionId } = await request.json(); |
| export function usePiPurchase(paymentData: PaymentData): () => void { | ||
| const purchase = useCallback(() => { | ||
| const pi = new PiSdkBase(); | ||
| pi.createPayment(paymentData); | ||
| }, [paymentData]); | ||
|
|
||
| return purchase; | ||
| } |
There was a problem hiding this comment.
usePiPurchase creates a payment with no lifecycle callbacks
pi.createPayment(paymentData) is invoked without the required onReadyForServerApproval, onReadyForServerCompletion, onCancel, or onError handlers. The Pi SDK holds the payment open waiting for your server to approve it via the configured paymentBasePath; without a callback to trigger or acknowledge that step, the payment will hang indefinitely and never complete. Any user-facing purchase triggered by this hook results in a stuck payment that cannot be recovered without a full page reload or Pi support intervention.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/axiomid/src/app/context/wallet-context.tsx (1)
168-175:⚠️ Potential issue | 🟠 MajorFix Pi wallet agent auth: server still requires
accessToken
/api/agentand/api/agent/activate(and/api/agent/main) still parsewalletAddressand, when it starts withpi:, requireaccessToken(returning 401 if it’s missing) and callhttps://api.minepi.com/v2/mewithAuthorization: Bearer ${accessToken}. With the client now sending only{ walletAddress, name }, the request will fail forpi:*wallets—revert to sendingaccessToken(or update the server contract to not require it).🤖 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 `@apps/axiomid/src/app/context/wallet-context.tsx` around lines 168 - 175, The client createAgent function is no longer sending accessToken but the server endpoints (/api/agent, /api/agent/activate, /api/agent/main) still require it for pi:* wallets; update the payload sent from createAgent to include the user's accessToken (e.g., include accessToken from the user object in the POST body alongside walletAddress and name) or conditionally include accessToken when user.walletAddress startsWith('pi:'), so the server can call https://api.minepi.com/v2/me with Authorization: Bearer ${accessToken} and avoid 401s.
🤖 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 `@apps/axiomid/src/app/api/pi/payment/incomplete/route.ts`:
- Around line 13-20: The outbound fetch to
`https://api.minepi.com/v2/payments/${paymentId}` has no timeout/AbortSignal;
wrap the call with an AbortController, pass controller.signal into the fetch
options (where Authorization header is set), create a timer (e.g. const timeout
= setTimeout(() => controller.abort(), 5000)) before the fetch and clear it
after the response (clearTimeout(timeout)), and handle the abort case by
catching the thrown DOMException/AbortError and returning an appropriate error
response; update the fetch invocation in the route handler where `fetch(..., {
headers: { Authorization: \`Key ${apiKey}\` } })` appears to include `signal:
controller.signal`.
In `@apps/axiomid/src/app/context/wallet-context.tsx`:
- Around line 39-43: The checkPiBrowser() helper is over-detecting Pi because it
returns true when isPiSdkLoaded() is true; remove using isPiSdkLoaded() as the
browser detector and make checkPiBrowser() rely solely on a userAgent match
(e.g., /Pi Browser|minepi/i.test(navigator.userAgent)) while still guarding for
navigator === undefined; then update the auto-connect logic that currently calls
checkPiBrowser() (the block that forces Pi auth/auto-connect) to first call the
new userAgent-based check and only attempt auto-connect if the SDK is actually
present (check isPiSdkLoaded() or a window.Pi object) at the point of
connecting, not inside checkPiBrowser().
---
Outside diff comments:
In `@apps/axiomid/src/app/context/wallet-context.tsx`:
- Around line 168-175: The client createAgent function is no longer sending
accessToken but the server endpoints (/api/agent, /api/agent/activate,
/api/agent/main) still require it for pi:* wallets; update the payload sent from
createAgent to include the user's accessToken (e.g., include accessToken from
the user object in the POST body alongside walletAddress and name) or
conditionally include accessToken when user.walletAddress startsWith('pi:'), so
the server can call https://api.minepi.com/v2/me with Authorization: Bearer
${accessToken} and avoid 401s.
🪄 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 Plus
Run ID: 36463832-b265-4ce4-b0a4-59c0fe9f5953
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
apps/axiomid/package.jsonapps/axiomid/src/app/api/pi/payment/cancel/route.tsapps/axiomid/src/app/api/pi/payment/error/route.tsapps/axiomid/src/app/api/pi/payment/incomplete/route.tsapps/axiomid/src/app/context/wallet-context.tsxapps/axiomid/src/components/PiButton.tsxapps/axiomid/src/lib/pi-sdk-hooks.tsapps/axiomid/src/lib/pi-sdk.ts
✅ Files skipped from review due to trivial changes (1)
- apps/axiomid/package.json
| const response = await fetch( | ||
| `https://api.minepi.com/v2/payments/${paymentId}`, | ||
| { | ||
| headers: { | ||
| Authorization: `Key ${apiKey}`, | ||
| }, | ||
| } | ||
| ); |
There was a problem hiding this comment.
Add a timeout to the outbound Minepi fetch in apps/axiomid/src/app/api/pi/payment/incomplete/route.ts.
The handler performs a blocking external call to https://api.minepi.com/v2/payments/${paymentId} without any AbortSignal/timeout, so upstream slowness can tie up request handling and cascade latency.
🔧 Suggested fix
- const response = await fetch(
- `https://api.minepi.com/v2/payments/${paymentId}`,
- {
- headers: {
- Authorization: `Key ${apiKey}`,
- },
- }
- );
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 8000);
+ const response = await fetch(
+ `https://api.minepi.com/v2/payments/${paymentId}`,
+ {
+ headers: {
+ Authorization: `Key ${apiKey}`,
+ },
+ signal: controller.signal,
+ }
+ ).finally(() => clearTimeout(timeout));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch( | |
| `https://api.minepi.com/v2/payments/${paymentId}`, | |
| { | |
| headers: { | |
| Authorization: `Key ${apiKey}`, | |
| }, | |
| } | |
| ); | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 8000); | |
| const response = await fetch( | |
| `https://api.minepi.com/v2/payments/${paymentId}`, | |
| { | |
| headers: { | |
| Authorization: `Key ${apiKey}`, | |
| }, | |
| signal: controller.signal, | |
| } | |
| ).finally(() => clearTimeout(timeout)); |
🤖 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 `@apps/axiomid/src/app/api/pi/payment/incomplete/route.ts` around lines 13 -
20, The outbound fetch to `https://api.minepi.com/v2/payments/${paymentId}` has
no timeout/AbortSignal; wrap the call with an AbortController, pass
controller.signal into the fetch options (where Authorization header is set),
create a timer (e.g. const timeout = setTimeout(() => controller.abort(), 5000))
before the fetch and clear it after the response (clearTimeout(timeout)), and
handle the abort case by catching the thrown DOMException/AbortError and
returning an appropriate error response; update the fetch invocation in the
route handler where `fetch(..., { headers: { Authorization: \`Key ${apiKey}\` }
})` appears to include `signal: controller.signal`.
| function checkPiBrowser(): boolean { | ||
| if (typeof navigator === "undefined") return false; | ||
| return /Pi Browser|minepi/i.test(navigator.userAgent); | ||
| } | ||
|
|
||
| function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> { | ||
| return Promise.race([ | ||
| promise, | ||
| new Promise<T>((_, reject) => | ||
| setTimeout(() => reject(new Error(`Timed out after ${ms}ms`)), ms) | ||
| ), | ||
| ]); | ||
| if (isPiSdkLoaded()) return true; | ||
| const ua = navigator.userAgent; | ||
| return /Pi Browser|minepi/i.test(ua); |
There was a problem hiding this comment.
checkPiBrowser() now over-detects and forces Pi auth for regular users.
Using isPiSdkLoaded() as a browser detector (Line 41) becomes true when the SDK script is globally injected, which makes the auto-connect block (Lines 126–128) run for non-Pi visitors and bypass demo flow.
🔧 Suggested fix
function checkPiBrowser(): boolean {
if (typeof navigator === "undefined") return false;
- if (isPiSdkLoaded()) return true;
const ua = navigator.userAgent;
return /Pi Browser|minepi/i.test(ua);
}Also applies to: 124-128
🤖 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 `@apps/axiomid/src/app/context/wallet-context.tsx` around lines 39 - 43, The
checkPiBrowser() helper is over-detecting Pi because it returns true when
isPiSdkLoaded() is true; remove using isPiSdkLoaded() as the browser detector
and make checkPiBrowser() rely solely on a userAgent match (e.g., /Pi
Browser|minepi/i.test(navigator.userAgent)) while still guarding for navigator
=== undefined; then update the auto-connect logic that currently calls
checkPiBrowser() (the block that forces Pi auth/auto-connect) to first call the
new userAgent-based check and only attempt auto-connect if the SDK is actually
present (check isPiSdkLoaded() or a window.Pi object) at the point of
connecting, not inside checkPiBrowser().
…handler, iframe detection - patchPostMessageForSandbox() bridges cross-origin postMessage in iframe - listenForPiSDKMessages() responds to @pi:app:sdk:communication_information_request - SandboxProvider initialized on mount in layout.tsx - shouldUseSandbox() detects iframe from sandbox.minepi.com - Add NEXT_PUBLIC_DEV_URL to .env.example
| "use client"; | ||
|
|
||
| import { useEffect } from "react"; | ||
| import { initSandboxCompatibility } from "@/lib/pi-sandbox"; | ||
|
|
||
| export function SandboxProvider({ children }: { children: React.ReactNode }) { | ||
| useEffect(() => { | ||
| initSandboxCompatibility(); | ||
| }, []); | ||
|
|
||
| return <>{children}</>; | ||
| } |
There was a problem hiding this comment.
window.postMessage is patched unconditionally for every visitor
SandboxProvider wraps the entire app and always calls initSandboxCompatibility(), which replaces window.postMessage with a version that forwards every outgoing message to window.parent with "*" as targetOrigin whenever the app is inside an iframe (see pi-sandbox.ts lines 4–10). This runs for all users regardless of whether sandbox mode is active, broadcasting any postMessage sent in an iframe context — including auth tokens or other app state — to an arbitrary parent origin. Consider gating this behind the same sandbox flag used elsewhere.
| const response = await fetch( | ||
| `https://api.minepi.com/v2/payments/${paymentId}`, | ||
| { | ||
| headers: { | ||
| Authorization: `Key ${apiKey}`, | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| if (!response.ok) { | ||
| return NextResponse.json( | ||
| { error: "Failed to fetch incomplete payment" }, | ||
| { status: response.status } | ||
| ); | ||
| } | ||
|
|
||
| const paymentData = await response.json(); | ||
|
|
||
| return NextResponse.json({ success: true, payment: paymentData }); |
There was a problem hiding this comment.
Incomplete payment is fetched but never approved or completed
The Pi Network server-side flow for an incomplete payment requires calling /v2/payments/{id}/approve and then /v2/payments/{id}/complete (with the on-chain txid). This handler only performs a GET to fetch the payment status and returns the result — it never calls approve or complete. Any payment surfaced by the SDK as incomplete during pi.connect() will remain permanently stuck in the pending state, as neither the app nor the SDK will make further attempts to resolve it.
| const username = authResult.user.username || uid; | ||
|
|
||
| onLog?.(`[PI-SDK] 👤 User: ${username} (UID: ${uid})`); | ||
| onLog?.(`[PI-SDK] 🔑 Access token: ${authResult.accessToken.slice(0, 20)}...`); |
There was a problem hiding this comment.
Access token fragment surfaced in dashboard terminal
The first 20 characters of the Pi access token are written to onLog, which flows through pushLog → walletLogs → the visible dashboard terminal via the useEffect in dashboard/page.tsx. Any person with access to the dashboard (or observing the screen) sees the token fragment. While only the first 20 bytes are exposed, even a partial JWT token can reveal payload claims; more importantly, establishing the habit of logging tokens makes future scope creep likely. runWalletTest on line 185 makes the same mistake with 30 characters. Both log lines should be removed or replaced with a generic confirmation message.
| if (typeof Pi.init === "function") { | ||
| onLog?.("[PI-SDK] 🔄 Calling Pi.init({ version: \"2.0\" })..."); | ||
| await Pi.init({ version: "2.0" }); | ||
| onLog?.("[PI-SDK] ✅ Pi.init() completed"); |
There was a problem hiding this comment.
Pi.init() is called without a sandbox flag in connectPi, so the Pi SDK always initialises against the production network regardless of NEXT_PUBLIC_PI_SANDBOX. Any payment made through the sandbox flow will be submitted to the live Pi Network, turning test transactions into real ones for affected users.
| if (typeof Pi.init === "function") { | |
| onLog?.("[PI-SDK] 🔄 Calling Pi.init({ version: \"2.0\" })..."); | |
| await Pi.init({ version: "2.0" }); | |
| onLog?.("[PI-SDK] ✅ Pi.init() completed"); | |
| if (typeof Pi.init === "function") { | |
| const sandbox = process.env.NEXT_PUBLIC_PI_SANDBOX === "true"; | |
| onLog?.(`[PI-SDK] 🔄 Calling Pi.init({ version: "2.0", sandbox: ${sandbox} })...`); | |
| await Pi.init({ version: "2.0", sandbox }); | |
| onLog?.("[PI-SDK] ✅ Pi.init() completed"); |
| React.useEffect(() => { | ||
| if (ready && !connected && !error) { | ||
| connect(); | ||
| } | ||
| }, [ready, connected, error, connect]); |
There was a problem hiding this comment.
Pi authentication fires automatically on SDK ready without user interaction
The effect calls connect() the moment ready becomes true and the user is not yet connected. This means rendering <PiButton> anywhere in the app silently triggers the full Pi OAuth flow (prompts, authentication dialog) before the user has clicked anything. This will surprise users who navigate to a page that merely displays this component — the Pi Browser auth prompt appears without any interaction. Consider removing the auto-connect effect and relying solely on the explicit onClick → purchase handler, or at minimum gate it behind a user-intent signal.
Summary by CodeRabbit
Documentation
New Features
Improvements
Tests
**Disclaimer This is AxiomID Review Agent.
Greptile Summary
This PR introduces Pi Network payment flows (cancel/error/incomplete webhook routes), a
PiButtoncomponent,pi-sdk-hooks,SandboxProvider, and a wallet debug/test tooling layer. It also refactorspi-sdk.tsinto a simplerconnectPi/ensurePiSdkmodel, uppercases agent status values, and redesigns the Privacy and Terms pages.connectPi/ensurePiSdkflow; howeverPi.init()is now called without asandboxflag, thePiButtonauto-triggers authentication on mount, and access token fragments are piped into the visible dashboard terminal."active", so the badge and toggle button display incorrectly for all users.Confidence Score: 3/5
Not safe to merge in current form — payment transactions silently target the wrong network, token data is surfaced in the UI, and the PiButton initiates authentication without any user action.
The payment layer has multiple concurrently broken paths: Pi.init is always called without the sandbox flag so sandbox payments hit the live network; the incomplete-payment handler fetches status but never approves or completes; usePiPurchase has no lifecycle callbacks; the PiButton fires authentication on component mount. On top of that, access token fragments are written to the dashboard terminal, and window.postMessage is patched unconditionally for all visitors in iframes. The agent status case mismatch means the badge and toggle button display incorrectly for every user.
apps/axiomid/src/lib/pi-sdk.ts, apps/axiomid/src/lib/pi-sdk-hooks.ts, apps/axiomid/src/components/PiButton.tsx, apps/axiomid/src/app/context/wallet-context.tsx, apps/axiomid/src/app/context/sandbox-provider.tsx, apps/axiomid/src/app/api/pi/payment/incomplete/route.ts, apps/axiomid/src/app/dashboard/page.tsx
Security Review
pi-sdk.tslines 98, 185):connectPiandrunWalletTestboth log the first 20–30 characters of the Pi access token throughonLog. These messages are piped intowalletLogsand rendered in the dashboard terminal, exposing partial token data to anyone viewing the screen.postMessagepatching withtargetOrigin="*"(pi-sandbox.ts/sandbox-provider.tsx):SandboxProviderwraps the entire app and always callsinitSandboxCompatibility(), which replaceswindow.postMessageto forward all messages to the parent frame using"*"as the target origin. This runs for all visitors in any iframe context, broadcasting outgoing messages (including any that carry auth tokens or app state) to an arbitrary parent without origin validation.Important Files Changed
Sequence Diagram
sequenceDiagram participant U as User participant PB as PiButton participant Hook as usePiConnection participant SDK as pi-sdk.ts (connectPi) participant Pi as window.Pi (CDN) participant API as /api/auth/pi PB->>Hook: mount → useEffect polls isPiSdkLoaded() Note over Hook: ready=true when Pi SDK loads Hook-->>PB: "ready=true" PB->>Hook: auto-calls connect() (no user gesture) Hook->>SDK: connectPi() SDK->>Pi: "ensurePiSdk() then Pi.init({ version:"2.0" })" Note over SDK,Pi: sandbox flag never passed SDK->>Pi: Pi.authenticate(["payments","username"]) Pi-->>SDK: "authResult { accessToken, user }" Note over SDK: token fragment logged via onLog SDK-->>Hook: PiAuthResult Hook->>API: POST /api/auth/pi API-->>Hook: "{ user }" Hook-->>PB: "connected=true" U->>PB: click Buy with Pi PB->>Hook: usePiPurchase → pi.createPayment(data) Note over Hook: no lifecycle callbacks — payment hangsComments Outside Diff (2)
apps/axiomid/src/app/dashboard/page.tsx, line 248-264 (link)handleToggleAgentStateunconditionally callsactivateAgent(), which always sets the status toACTIVE. When the agent is already active and a user clicks "■ PAUSE RUNTIME", the handler activates it again instead of deactivating or pausing it. There is no corresponding deactivate/pause call here, so the PAUSE button is permanently broken.apps/axiomid/src/app/context/wallet-context.tsx, line 474-506 (link)The old
elsebranch that re-authenticated stored demo wallets via/api/auth/connecton page load has been removed. Now the auto-auth effect only callsconnectWallet()whenusePiis true; for all regular (non-Pi-browser, non-sandbox) users nothing happens on mount —setIsLoading(false)fires immediately anduserremainsnullforever until the user manually clicks "Connect". Any state tied to the demo wallet (XP, tier, agent) is invisible until reconnect.Reviews (7): Last reviewed commit: "fix(auth): unify pi-sdk and fix sandbox ..." | Re-trigger Greptile