Skip to content

refactor(billing): require wallet address in public wallet API responses - #3521

Open
baktun14 wants to merge 14 commits into
mainfrom
refactor/billing-require-wallet-address-in-api
Open

refactor(billing): require wallet address in public wallet API responses#3521
baktun14 wants to merge 14 commits into
mainfrom
refactor/billing-require-wallet-address-in-api

Conversation

@baktun14

@baktun14 baktun14 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Why

Closes CON-745

CON-744 (#3512, #3513, deployed) guarantees that every wallet returned by the public API has an address: GET /v1/wallets only returns activated wallets (activation happens strictly after address derivation), and POST /v1/start-trial responds only after the address is derived. The loose nullable contract now only hides bugs and forces null-tolerant handling in API consumers.

What

apps/api — declare address (plus id, userId, createdAt, which are DB-non-null already) as required in the wallet response schema:

  • WalletOutputSchema: drop .nullable() on the four fields; createdAt switches z.coerce.date()z.date() because coercion makes the OpenAPI generator's isNullable() probe succeed on null (new Date(null) is valid), which kept a phantom nullable: true in the spec. Response schemas are compile-time/docs only — nothing parses them at runtime.
  • The invariant is proven at compile time, with no new runtime asserts: toPublic now requires WalletInitialized (moved to the repository from wallet-reader.service.ts), WalletReaderService.getWallets filters with a type predicate (which also silently excludes any pathological activated-but-addressless legacy row instead of 500ing), and WalletInitializerService.#ensureWalletVia returns the narrowed type its JSDoc already promised.
  • New createInitializedUserWallet seeder for fixtures that mock the narrowed contract.

Generated artifacts — docs snapshot, swagger/openapi.json, and @akashnetwork/console-api-types regenerated. Note: the checked-in spec was stale (predates #3513 and the idempotent top-up work), so the regen also drops the long-removed 202 from /v1/start-trial, adds its 409, and picks up idempotencyKey on checkout — incidental drift, not introduced by this PR.

apps/deploy-web — drop the null-address tolerance that CON-744 shipped as a temporary bridge:

Verified end-to-end against a locally booted API: an activated wallet returns a non-null address, a pre-activation wallet is excluded from GET /v1/wallets, and the served OpenAPI doc marks all four fields required (only the intentionally nullable 3DS fields remain nullable).

Summary by CodeRabbit

  • New Features
    • Added idempotencyKey support for Stripe transaction confirmation requests and for trial start.
    • Added a 409 response for /v1/start-trial when trial provisioning is already in progress.
    • /v1/start-trial now includes an isNewUser field.
  • Bug Fixes
    • Wallet-dependent flows now wait for a fully initialized wallet with a definite address.
    • Wallet-related responses now return non-null id, userId, address, and createdAt where applicable.
    • Trial deployment create/close behavior is more robust and idempotent.
  • Improvements
    • Usage history date handling now consistently derives endDate/startDate.
  • Tests
    • Updated wallet seeding and readiness/persistence hook tests to match stricter wallet initialization.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Wallet initialization now guarantees initialized public wallet data, deployment provisioning handles orphan cleanup and idempotent close behavior, frontend hooks accept wallet presence independently of address presence, usage dates are materialized during parsing, and API schemas reflect updated wallet, trial, and idempotency contracts.

Changes

Wallet initialization and provisioning contracts

Layer / File(s) Summary
Backend wallet initialization flow
apps/api/src/billing/..., apps/api/src/deployment/..., apps/api/test/seeders/...
Introduces the repository-owned WalletInitialized type, normalizes wallet addresses during initialization, filters uninitialized wallets, and updates dependent fixtures and imports.
Trial deployment cleanup and idempotent close
apps/api/src/deployment/services/deployment-writer/...
Adds best-effort orphan cleanup before trial deployment creation and confirms deployment state after close failures, with matching tests and injected services.
Frontend wallet readiness and persistence
apps/deploy-web/src/hooks/...
Treats an existing wallet as ready and persists it without requiring wallet.address; related tests and helpers now use non-null addresses.
Wallet, trial, and payment API contracts
apps/api/src/billing/http-schemas/wallet.schema.ts, apps/api/swagger/openapi.json, packages/console-api-types/src/schema.d.ts
Makes wallet fields non-nullable, replaces the start-trial 202 response with 409, adds optional Stripe idempotency metadata, and updates generated schemas.
Usage history date normalization
apps/api/src/billing/http-schemas/usage.schema.ts, apps/api/src/billing/routes/usage/usage.router.ts
Derives missing usage-history dates during schema transformation, validates the resulting range, and passes concrete dates to controllers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: stalniy, ygrishajev

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/billing-require-wallet-address-in-api

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.53%. Comparing base (44bef1f) to head (3a3f940).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3521      +/-   ##
==========================================
- Coverage   76.33%   75.53%   -0.80%     
==========================================
  Files        1134     1044      -90     
  Lines       29598    27247    -2351     
  Branches     7352     6898     -454     
==========================================
- Hits        22593    20581    -2012     
+ Misses       6174     5864     -310     
+ Partials      831      802      -29     
Flag Coverage Δ *Carryforward flag
api 88.92% <100.00%> (-0.01%) ⬇️
deploy-web 66.18% <100.00%> (-0.01%) ⬇️
log-collector ?
notifications 93.84% <ø> (ø) Carriedforward from d9f8537
provider-console 81.38% <ø> (ø) Carriedforward from d9f8537
provider-inventory ?
provider-proxy 88.17% <ø> (ø) Carriedforward from d9f8537
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...repositories/user-wallet/user-wallet.repository.ts 81.96% <100.00%> (+0.61%) ⬆️
...s/wallet-initializer/wallet-initializer.service.ts 100.00% <100.00%> (ø)
...ng/services/wallet-reader/wallet-reader.service.ts 94.44% <100.00%> (+0.32%) ⬆️
...ces/deployment-reader/deployment-reader.service.ts 85.41% <ø> (ø)
...ces/deployment-writer/deployment-writer.service.ts 100.00% <ø> (ø)
apps/deploy-web/src/hooks/useManagedWallet.ts 90.47% <100.00%> (-1.53%) ⬇️

... and 94 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use type-only imports for wallet aliases.

UserWalletOutput, UserWalletPublicOutput, and WalletInitialized are type-only symbols, while UserWalletRepository is a runtime dependency. Split or annotate these imports to satisfy the shared TypeScript import rule and avoid runtime import issues.

  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts#L6-L6: mark the wallet aliases as type-only while keeping UserWalletRepository as a value import.
  • apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts#L6-L6: apply the same type-only import treatment.

As per coding guidelines, TypeScript imports must use consistent type imports.

🤖 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/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts`
at line 6, Update the imports in
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts#L6-L6
and apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts#L6-L6
so UserWalletOutput, UserWalletPublicOutput, and WalletInitialized use type-only
imports, while UserWalletRepository remains a runtime import.

Source: Coding guidelines

🤖 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/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts`:
- Around line 87-94: Update `#ensureWalletVia` to perform the wallet address
mutation through the authorization-scoped repository argument by replacing
this.userWalletRepository.updateById with repository.updateById, while
preserving the existing update parameters and returned wallet behavior.

In `@apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts`:
- Around line 22-24: Update the wallet filter in the wallet-reader mapping flow
to validate that address is present and non-empty before asserting
WalletInitialized; reject undefined, null, and empty or whitespace-only values
while preserving the activatedAt requirement and existing toPublic mapping.

In
`@apps/api/src/deployment/services/deployment-reader/deployment-reader.service.ts`:
- Around line 19-20: Change the WalletInitialized import in the deployment
reader service to a type-only import, while keeping WalletReaderService as a
regular import.

In `@apps/api/swagger/openapi.json`:
- Line 1790: Remove the generated static default value for dynamic endDate at
apps/api/swagger/openapi.json lines 1790-1790 and 1916-1916, omitting the
default property entirely at both sites so the OpenAPI schema does not contain a
date that becomes stale.
- Line 5: Update the OpenAPI server URL in the generator configuration to the
intended public API URL instead of localhost, then regenerate the specification
so Swagger UI and generated clients use the deployed server.

---

Nitpick comments:
In
`@apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts`:
- Line 6: Update the imports in
apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts#L6-L6
and apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts#L6-L6
so UserWalletOutput, UserWalletPublicOutput, and WalletInitialized use type-only
imports, while UserWalletRepository remains a runtime import.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cee7dad8-2ed7-4893-a71b-d7722cb62820

📥 Commits

Reviewing files that changed from the base of the PR and between 627ad0c and cb04b6c.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (17)
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
  • apps/api/src/billing/services/refill/refill.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.spec.ts
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.ts
  • apps/api/src/deployment/services/deployment-writer/deployment-writer.service.spec.ts
  • apps/api/src/deployment/services/deployment-writer/deployment-writer.service.ts
  • apps/api/src/deployment/services/lease/lease.service.spec.ts
  • apps/api/swagger/openapi.json
  • apps/api/test/seeders/user-wallet.seeder.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
  • apps/deploy-web/src/hooks/useManagedWallet.spec.tsx
  • apps/deploy-web/src/hooks/useManagedWallet.ts
  • packages/console-api-types/src/schema.d.ts
💤 Files with no reviewable changes (1)
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts

Comment thread apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts Outdated
Comment thread apps/api/src/deployment/services/deployment-reader/deployment-reader.service.ts Outdated
Comment thread apps/api/swagger/openapi.json Outdated
Comment thread apps/api/swagger/openapi.json Outdated
baktun14 added 5 commits July 30, 2026 13:58
- merge duplicated @src/billing/repositories import in deployment-writer
- flatten managed-wallet storage-sync effect to an early return + ternary
- revert accidental openapi regeneration artifacts (localhost server url,
  today's date defaults, dropped trailing newline)
… guard

Addresses review feedback on the wallet-address contract:

- #ensureWalletVia now mutates through the CASL-scoped `repository`
  argument instead of the unscoped `this.userWalletRepository`, so the
  get/check/mutate sequence stays authorization-scoped during trial init.
- getWallets filters activated wallets with a truthiness check on
  `address` so an empty-string address is also excluded and the
  `WalletInitialized` predicate holds.
- Use type-only imports for the wallet type aliases and WalletInitialized.
…enAPI spec

The usage-history query schema defaulted `endDate` with
`.default(() => new Date()...)`. The OpenAPI generator evaluates that at
generation time and freezes a static date into the spec, which then goes
stale and misleads generated clients into sending an old default date.

Move the "default to today" logic into the schema transform (next to the
existing startDate-from-endDate derivation) so runtime behavior is
unchanged but the generated spec no longer carries a static default.
Regenerate openapi.json, update the docs snapshot, and drop the
now-redundant non-null assertions in the router and refine.
@baktun14
baktun14 force-pushed the refactor/billing-require-wallet-address-in-api branch from 9d75988 to 9e439e5 Compare July 30, 2026 13:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/swagger/openapi.json (1)

2061-2069: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Regenerate the client types for isNewUser.

This response now requires top-level isNewUser, but packages/console-api-types/src/schema.d.ts still types /v1/register-user as containing only data. Consumers cannot access the required result without a cast.

🤖 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/api/swagger/openapi.json` around lines 2061 - 2069, Regenerate the
client types so the /v1/register-user response includes the required top-level
isNewUser boolean alongside data. Update the generated schema definition in
packages/console-api-types, preserving the required-property contract reflected
by the OpenAPI response.
🤖 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/api/src/billing/http-schemas/usage.schema.ts`:
- Around line 27-30: Update the startDate calculation in the schema’s date-range
logic to perform subtraction in UTC, replacing local getDate/setDate usage with
getUTCDate/setUTCDate or equivalent UTC millisecond arithmetic. Preserve the
existing 30-day offset and YYYY-MM-DD serialization for startDate and endDate.

---

Outside diff comments:
In `@apps/api/swagger/openapi.json`:
- Around line 2061-2069: Regenerate the client types so the /v1/register-user
response includes the required top-level isNewUser boolean alongside data.
Update the generated schema definition in packages/console-api-types, preserving
the required-property contract reflected by the OpenAPI response.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e5a334dc-c929-4816-af90-52af13d9f2cf

📥 Commits

Reviewing files that changed from the base of the PR and between 9d75988 and 9e439e5.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (19)
  • apps/api/src/billing/http-schemas/usage.schema.ts
  • apps/api/src/billing/http-schemas/wallet.schema.ts
  • apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts
  • apps/api/src/billing/routes/usage/usage.router.ts
  • apps/api/src/billing/services/refill/refill.service.spec.ts
  • apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts
  • apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.spec.ts
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.ts
  • apps/api/src/deployment/services/deployment-writer/deployment-writer.service.spec.ts
  • apps/api/src/deployment/services/deployment-writer/deployment-writer.service.ts
  • apps/api/src/deployment/services/lease/lease.service.spec.ts
  • apps/api/swagger/openapi.json
  • apps/api/test/seeders/user-wallet.seeder.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.ts
  • apps/deploy-web/src/hooks/useManagedWallet.spec.tsx
  • apps/deploy-web/src/hooks/useManagedWallet.ts
  • packages/console-api-types/src/schema.d.ts
💤 Files with no reviewable changes (1)
  • apps/deploy-web/src/hooks/useEnsureTrialStarted.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.ts
  • apps/api/src/billing/routes/usage/usage.router.ts
  • apps/api/src/deployment/services/lease/lease.service.spec.ts
  • apps/api/src/deployment/services/deployment-reader/deployment-reader.service.spec.ts

Comment thread apps/api/src/billing/http-schemas/usage.schema.ts Outdated

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

LGTM — the wallet-address non-null contract change is well-supported by the CON-744 invariants, and all prior CodeRabbit findings (CASL-scoped update, address type predicate, type-only import, localhost server URL, stale OpenAPI date default) were addressed in follow-up commits with clear author explanations in the thread.

Extended reasoning...

Overview

This PR tightens the public wallet API contract (id, userId, address, createdAt) from nullable to required, now that upstream work (CON-744, already deployed) guarantees every wallet returned by GET /v1/wallets and POST /v1/start-trial has a derived address. It introduces a WalletInitialized type in the repository layer, threads it through WalletInitializerService, WalletReaderService, and toPublic, drops the now-impossible null-address handling in deploy-web's useManagedWallet/useEnsureTrialStarted, and regenerates the OpenAPI spec/snapshot/typed client. It also folds in an unrelated but small fix to usage.schema.ts making endDate's today-default computed in the transform instead of frozen via Zod .default(), avoiding stale dates baked into the generated spec.

Security risks

None identified. No auth/permission logic changes — the CASL-scoped repository boundary in #ensureWalletVia is preserved (CodeRabbit flagged a regression here and it was fixed to use repository.updateById instead of the unscoped this.userWalletRepository). The wallet-reader filter now correctly type-guards on both activatedAt and a truthy address before narrowing to WalletInitialized.

Level of scrutiny

Medium — this is a contract-tightening refactor in billing/wallet code (sensitive but not touching auth/crypto directly), proven mostly at compile time via TypeScript narrowing rather than new runtime asserts. The response schemas are docs-only (no response-validation middleware is wired into createRoute/OpenApiHonoHandler), so the nullability changes can't cause new runtime rejections — verified this directly in create-route.ts.

Other factors

The PR thread shows thorough back-and-forth: CodeRabbit raised 5 concerns, all addressed in commit 4adc9f9/aaf18a7, plus one date-default issue the author initially deferred and then reversed course on and fixed in-PR (9d75988) with a clear rationale. Test coverage is strong (new seeder, updated specs across wallet-initializer, wallet-reader, deployment-reader/writer, lease, and the two deploy-web hooks), and Codecov reports full coverage of changed lines with no failing tests.

new Date("YYYY-MM-DD") parses at UTC midnight, but getDate()/setDate()
operate in the process timezone, so the 30-day default window could land
one day early across a DST boundary in non-UTC deployments (e.g. endDate
2024-11-15 yielded 2024-10-15 under America/New_York instead of 2024-10-16).

Compute the offset with getUTCDate()/setUTCDate() so the derived startDate
is timezone-independent, and add usage.schema specs covering the window
derivation, boundary crossing, and timezone regression.
@baktun14

Copy link
Copy Markdown
Contributor Author

Re: CodeRabbit's outside-diff comment on openapi.json#L2061-2069 — "Regenerate the client types for isNewUser"

Declining as out of scope for this PR, with evidence:

  • The isNewUser drift is pre-existing on main: openapi.json already declares isNewUser as a required top-level field of the /v1/register-user response, and packages/console-api-types/src/schema.d.ts already omits it — both on main, byte-identical to here. This PR touches neither the register-user block in openapi.json nor the register-user typing in schema.d.ts.
  • It is unrelated to this PR's purpose (tightening the public wallet-address contract).
  • A proper fix means regenerating the typed client, but openapi-typescript is pinned as ^7.13.0, and a local npm run generate:console reorders the entire file (~600KB diff, e.g. the whole start-trial block moves) due to a tool-version mismatch rather than emitting a clean isNewUser addition. That churn does not belong in this focused refactor.

Tracking separately as a dedicated typed-client regeneration (canonical tooling version), same as the earlier pre-existing endDate observation on this PR.

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

I didn't find any bugs in the current state of this PR, but given it changes the wallet address nullability contract across the public billing API (schemas, repository, and both wallet-initialization/read services) and touches CASL-authorization-scoped wallet mutation code, I think it's worth a final human look before merging.

What was reviewed:

  • The narrowing of UserWalletOutput -> WalletInitialized and its propagation through toPublic, WalletReaderService.getWallets, and WalletInitializerService#ensureWalletVia" — the CASL-scoped-repository mutation issue flagged earlier in review is fixed (mutates via the passed-in repository`, not the singleton).
  • The address-presence filter in getWallets (activatedAt !== null && !!wallet.address) correctly excludes any legacy addressless-but-activated row instead of emitting invalid data.
  • The usage-history startDate UTC arithmetic fix and its new regression test (DST boundary case) look correct.
Extended reasoning...

Overview

This PR tightens the public wallet API contract so id, userId, address, and createdAt are non-nullable, backed by a new WalletInitialized type-predicate narrowing threaded through UserWalletRepository.toPublic, WalletReaderService, and WalletInitializerService. It also updates generated OpenAPI artifacts, deploy-web hooks (useManagedWallet, useEnsureTrialStarted) to drop null-address tolerance, and separately fixes a UTC-vs-local-timezone bug in the usage-history startDate derivation (with a DST-boundary regression test added the same day).

Security risks

The main risk surface is the wallet-mutation path in WalletInitializerService#ensureWalletVia, which reads through a CASL-authorization-scoped repository but originally mutated through the unscoped singleton — a real authorization-scoping gap flagged by CodeRabbit and fixed in-PR (now mutates via the scoped repository argument). No other injection/auth-bypass/data-exposure patterns stood out; response schemas here are compile-time/docs-only, not runtime validators.

Level of scrutiny

This warrants more than a rubber-stamp: it touches billing/wallet code (financial correctness and authorization scoping), is tagged size M, and changes a public API contract (nullability) consumed by generated client types and deploy-web. The bug-hunting pass found nothing outstanding, and the PR has already been through several rounds of substantive CodeRabbit findings that were all fixed and tested (CASL scoping, address-predicate validation, localhost server URL, stale OpenAPI defaults, UTC date arithmetic), which reduces residual risk but doesn't eliminate the value of a human sign-off given the domain.

Other factors

Test coverage is strong: new/updated specs cover the tightened wallet-initialization contract, the usage-history date derivation (including a TZ-specific DST regression test), and the frontend hooks' updated readiness semantics. Coverage tooling (Codecov) reports all modified/coverable lines covered. All prior CodeRabbit review threads on this PR are marked resolved/addressed by the author.

Comment thread apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts Outdated
Comment thread apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts Outdated
Comment thread apps/api/src/billing/services/wallet-initializer/wallet-initializer.service.ts Outdated
Reconcile #3542 (background-job trial activation) with this PR's
wallet-address contract:

- initializeAndGrantTrialLimits keeps main's background-job flow
  (no start-trial endpoint, no claimActivation/409, activatedAt folded
  into the grant, activation instrumentation) while ensureWallet still
  returns WalletInitialized and toPublic still requires it. The final
  toPublic narrows via { ...activatedWallet, address: userWallet.address }.
- useEnsureTrialStarted / its spec: take main's server-side, readiness-only
  hook, which supersedes this PR's client-start tweak. The address guarantee
  is preserved by main's !!wallet?.address under the now-non-null contract.
- Dropped the now-unused UserWalletOutput import from the service.
- Regenerated openapi.json, @akashnetwork/console-api-types, and the docs
  snapshot from the merged source.
Resolve reviewer questions on the wallet-address refactor:

- Add an isWalletInitialized type guard next to WalletInitialized and use
  it in WalletInitializerService.ensureWallet and WalletReaderService
  .getWallets, replacing the { ...wallet, address: wallet.address } spread
  that read as a no-op. That spread existed only to re-apply the narrowed
  string type over the nullable field the object spread reintroduces.
- Inline #ensureWalletVia into ensureWallet: merging #3542 removed the
  second, CASL-scoped caller, leaving a single caller that always passed
  the plain repository, so the indirection no longer earned its keep.
- Drop returning: true on the address update. The returned row was
  reconstructed as { ...wallet, address } regardless, so echoing the row
  back from the DB was redundant; no consumer reads the other columns.
- Update the two specs that asserted returning: true on the address update.
@github-actions github-actions Bot added size: XL and removed size: M labels Aug 4, 2026
Comment thread apps/api/swagger/openapi.json
A regeneration ran the generator with the default SERVER_ORIGIN
(localhost:3080) and overwrote the sandbox URL that origin/main ships,
making Swagger UI and generated clients target the caller's machine.
Restore servers[0].url to https://console-api-sandbox.akash.network.
Comment thread apps/api/src/billing/repositories/user-wallet/user-wallet.repository.ts Outdated
Comment thread apps/api/src/deployment/services/deployment-writer/deployment-writer.service.ts Outdated
…ire-wallet-address-in-api

# Conflicts:
#	apps/api/swagger/openapi.json
@github-actions github-actions Bot added size: M and removed size: XL labels Aug 7, 2026

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

This automated re-review found no new bugs. Given this PR changes security-sensitive wallet/billing code (CASL-scoped repository access, address-derivation invariants) and has already gone through several substantive fix rounds (CASL-scoped mutation, empty-string address validation, an accidental localhost server URL in the generated spec), a human look is still worthwhile before merge, especially since one nit-level review comment (type-only import in deployment-writer.service.ts) is still unaddressed.

Extended reasoning...

This run of the bug hunting system found no new issues in apps/api/src/billing (wallet schema, repository, wallet-initializer/wallet-reader services) or the deploy-web useManagedWallet changes. A previously-raised candidate issue (ensureWallet returning stale non-address fields) was examined and refuted this run.

Overview

The PR tightens the public wallet API contract so address/id/userId/createdAt are non-nullable, removing null-tolerant handling in deploy-web (useManagedWallet) and adding a narrowing WalletInitialized type plus an isWalletInitialized guard used across wallet-reader, wallet-initializer, and deployment reader/writer services. It also regenerates the OpenAPI spec/types and fixes an unrelated timezone bug in the usage-history date derivation.

Security risks

The core risk surface is authorization scoping around wallet mutations (CASL-scoped repository access) and the address-initialization invariant (an uninitialized/empty address must never reach the public API or be treated as usable). Both were flagged during review (by CodeRabbit and this bot) and fixed in-branch; no new authorization or address-validation issues surfaced this run.

Level of scrutiny

This warrants more than mechanical-change scrutiny: it touches billing/wallet code paths, changes a public API contract (nullability), and has already required several correction rounds during review (CASL scoping, address validation regression, a leaked localhost server URL in generated docs). That history, not any current unresolved bug, is why a human pass is still appropriate.

Other factors

Test coverage for the changed invariants (empty-string address, UTC date derivation, wallet initialization) was added alongside the fixes. One outstanding nit-level comment (type-only import in deployment-writer.service.ts) has not yet been addressed by the author, though it mirrors an already-accepted pattern elsewhere on main and is not functionally significant.

Comment thread apps/api/src/billing/services/wallet-reader/wallet-reader.service.ts Outdated
@github-actions github-actions Bot added size: L and removed size: M labels Aug 7, 2026

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

I reviewed this PR and didn't find any new bugs in this pass. Because it's a large change touching billing wallet initialization/activation and several deployment services, a human look would still be worthwhile.

What was reviewed: the isWalletInitialized/WalletInitialized narrowing now shared across user-wallet.repository, wallet-reader, and wallet-initializer; the OpenAPI schema change making id/userId/address/createdAt non-nullable plus the regenerated swagger.json, docs snapshot, and console-api-types; and the deploy-web useManagedWallet hook reverting the null-address storage guard.

Extended reasoning...

Overview

The PR tightens the public wallet API contract so id, userId, address, and createdAt are required (non-nullable) instead of nullable, backed by a new WalletInitialized/isWalletInitialized type-predicate shared between UserWalletRepository, WalletReaderService, and WalletInitializerService. It also regenerates the OpenAPI artifacts (swagger.json, docs snapshot, console-api-types) and, in deploy-web, reverts a temporary null-address tolerance in useManagedWallet that a prior PR (CON-744) had introduced as a bridge.

Security risks

No new attack surface is introduced. The main risk class here is a logic regression that could let an uninitialized wallet (null or empty-string address) leak through the public API or bypass the trial-activation gate — this is exactly the class of bug that surfaced and was fixed twice during this PR's review cycle (the isWalletInitialized guard was briefly weakened to !== null during a refactor, allowing empty-string addresses through, then re-hardened to !!wallet.address in commit 6ca8e9e with new empty-string test coverage in both getWallets and ensureWallet).

Level of scrutiny

This warrants a human look rather than shadow-approval. It's a size-L PR touching billing wallet initialization and activation, which gates real financial/deployment allowances, and it has already been through several rounds of substantive back-and-forth — CodeRabbit, a human reviewer, and this bug-hunting system all found and the author fixed real issues (the empty-string address bypass, a no-op returning: true round-trip, and a regenerated openapi.json that briefly shipped a localhost server URL). That density of real findings on billing-adjacent code is a signal for continued human attention even though this pass found nothing new.

Other factors

The author has been responsive and precise in addressing every substantive finding with focused commits and added test coverage (e.g. the empty-string wallet tests in wallet-reader.service.spec.ts and wallet-initializer.service.spec.ts). One low-severity nit from a prior pass (a redundant { ...activatedWallet, address: userWallet.address } spread in wallet-initializer.service.ts:71, functionally inert but stylistically inconsistent with the sibling ensureWallet fix) remains open but is not a correctness concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants