Skip to content

✨ server: add allower#839

Open
aguxez wants to merge 4 commits intomainfrom
gcp-allower
Open

✨ server: add allower#839
aguxez wants to merge 4 commits intomainfrom
gcp-allower

Conversation

@aguxez
Copy link
Contributor

@aguxez aguxez commented Feb 25, 2026

Summary by CodeRabbit

  • New Features

    • GCP KMS integration for account keys, an account-allower (firewall) flow, and automated asset poking for ETH/WETH/ERC‑20
  • Bug Fixes

    • Simplified per-account activity orchestration, error handling, and poke/account activation flow
  • Tests

    • Added GCP credentials validation tests and expanded tests for account, firewall, and poke behaviors
  • Chores

    • Added runtime env vars and cloud/KMS-related dependencies

Open with Devin

Greptile Summary

this pr integrates gcp kms for a hardware-secured "allower" account and adds a firewall allow flow after kyc completion. the main changes:

  • renamed keeper module to accounts and extracted poke method for simplified asset activation
  • new allower() function creates gcp kms-backed wallet client for firewall allow operations
  • after kyc, accounts are allowed through firewall and assets are automatically poked with user notification
  • activity hook simplified by delegating asset poking to centralized keeper.poke() method
  • comprehensive test coverage for firewall integration and poke behavior

issues found:

  • ordering problem: firewall allow happens before panda user creation, risking inconsistent state if user creation fails
  • gcp credentials triple-decoded without json validation before filesystem write
  • promise caching in getAllower() has race condition during failures
  • request.clone() optimization opportunity in allower's rpc interceptor

Confidence Score: 3/5

  • this pr introduces critical infrastructure changes with several logic issues that need resolution before merge
  • score reflects strong test coverage and good refactoring, but critical ordering bug in persona hook could leave accounts in inconsistent state (firewall-allowed but no panda user), plus unvalidated triple base64 decode creates potential runtime failures
  • pay close attention to server/hooks/persona.ts for the firewall/panda ordering issue and server/utils/gcp.ts for credential validation

Important Files Changed

Filename Overview
server/utils/accounts.ts adds gcp kms integration for allower account, refactors keeper logic into accounts module with new poke method that simplifies asset poking
server/utils/gcp.ts new gcp credentials management that triple-decodes base64 and writes to /tmp with secure permissions, but lacks json validation before file write
server/hooks/persona.ts adds firewall allow call after kyc completion and pokes account with notification, but has ordering issues with panda user creation
server/hooks/activity.ts simplified activity orchestration using new poke method, removes retry logic and inline asset poking in favor of centralized implementation
server/test/hooks/persona.test.ts comprehensive tests for firewall integration including allow calls, error handling, and poke behavior with various asset balances

Sequence Diagram

sequenceDiagram
    participant Persona as Persona Webhook
    participant Hook as persona.ts Hook
    participant Allower as GCP KMS Allower
    participant Firewall as Firewall Contract
    participant Keeper as Keeper Account
    participant Account as Smart Account
    participant Panda as Panda API

    Persona->>Hook: KYC completed event
    Hook->>Hook: validate risk (sardine)
    alt risk level very_high
        Hook->>Persona: 200 (reject)
    else risk acceptable
        Hook->>Allower: getAllower() + allow(account)
        Allower->>Firewall: allow(account, true)
        Firewall-->>Allower: success/AlreadyAllowed
        Allower-->>Hook: allowed
        Hook->>Keeper: poke(account) [fire-and-forget]
        Keeper->>Account: pokeETH/poke(market)
        Account-->>Keeper: assets activated
        Keeper->>Account: send push notification
        Hook->>Panda: createUser()
        Panda-->>Hook: pandaId
        Hook->>Persona: 200 (success)
    end
    
    Note over Hook,Panda: ISSUE: firewall allow happens before panda user creation
Loading

Last reviewed commit: 85464d7

@changeset-bot
Copy link

changeset-bot bot commented Feb 25, 2026

🦋 Changeset detected

Latest commit: 85464d7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

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

Adds GCP KMS integration and runtime envs, replaces default keeper with a named keeper from server/utils/accounts (new getAccount/allower/withExaSend APIs), integrates firewall allower and keeper.poke into persona/activity hooks, restructures account orchestration, and updates tests/mocks.

Changes

Cohort / File(s) Summary
Changesets & small config
.changeset/lucky-jokes-change.md, .changeset/silly-yaks-divide.md, cspell.json
New changesets and minor cspell addition.
Deployment & runtime envs
.do/app.yaml, server/script/openapi.ts, server/vitest.config.mts
Add GCP env vars: GCP_BASE64_JSON, GCP_KMS_KEY_RING, GCP_KMS_KEY_VERSION, GCP_PROJECT_ID for runtime and tests.
Dependencies
server/package.json
Add @google-cloud/kms and @valora/viem-account-hsm-gcp.
GCP utilities & credential init
server/utils/gcp.ts, server/test/utils/gcp.test.ts
New GCP credential initializer, idempotent init/reset, hasCredentials, retryable-KMS-error helper, and tests for file write/access and permissions.
Accounts API & keeper export
server/utils/accounts.ts, server/test/mocks/accounts.ts, server/test/utils/accounts.test.ts
New keeper named export and APIs: getAccount(), allower(), withExaSend(); extender signatures adjusted; tests/mocks updated to import from .../accounts.
Removed legacy keeper
server/utils/keeper.ts
Removed previous default-exported keeper and legacy extender/exaSend implementation.
Hook imports & behavior
server/hooks/activity.ts, server/hooks/persona.ts, server/hooks/block.ts, server/hooks/panda.ts
Switch to named keeper from utils/accounts; activity refactored to account-lookup per-account spans and simplified flows; persona integrates firewall allower + keeper.poke and adjusts addCapita/account derivation.
Tests & mocks updates
server/test/hooks/*.test.ts, server/test/api/card.test.ts, server/test/e2e.ts, server/test/mocks/accounts.ts, server/test/hooks/persona.test.ts
Replace keeper imports/usages, adapt tests to keeper.poke semantics and balance setups, add accounts mocks and firewall/allower mocks, add GCP-related tests.
Vitest / test env
server/vitest.config.mts
Expose new GCP env vars to Vitest environment.

Sequence Diagram

sequenceDiagram
    participant Client
    participant PersonaHook as Persona Hook
    participant RiskSvc as Risk Assessment
    participant Allower as Firewall/Allower
    participant GCP as GCP KMS
    participant Chain as Chain / keeper

    Client->>PersonaHook: POST inquiry
    PersonaHook->>RiskSvc: perform risk assessment
    RiskSvc-->>PersonaHook: pass/fail

    alt pass
        PersonaHook->>PersonaHook: derive account
        PersonaHook->>Allower: getAllower()
        Allower->>GCP: init credentials / request KMS-backed account
        GCP-->>Allower: LocalAccount / credentials
        Allower-->>PersonaHook: allower client ready
        PersonaHook->>Allower: allow(account)
        Allower-->>PersonaHook: allow result
        PersonaHook->>Chain: keeper.poke(account, { ignore: [...] })
        Chain-->>PersonaHook: tx hash / result
        PersonaHook-->>Client: 200 success
    else fail
        PersonaHook-->>Client: rejection
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
  • nfmelendez
  • dieguezguille
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '✨ server: add allower' accurately reflects the main changes: adding an allower function to the server's accounts module for firewall integration, which is a central addition across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gcp-allower

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.

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @aguxez, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the server's account management and security by integrating Google Cloud KMS for a new allower functionality. It centralizes wallet client operations into a dedicated utility, ensuring that account creation and asset 'poking' are handled securely and efficiently, especially after user verification. The changes also include necessary environment configurations and dependency updates to support this new cryptographic infrastructure.

Highlights

  • GCP KMS Integration for Allower: Introduced a new allower functionality that leverages Google Cloud Key Management Service (KMS) for secure cryptographic operations, specifically for allowing accounts within the system. This enhances the security posture of account management.
  • Refactored Account Management Utilities: Consolidated and refactored the keeper and new allower wallet client logic into a dedicated server/utils/accounts.ts module. This centralizes account-related transaction sending and interaction with smart contracts.
  • Automated Account Poking Post-KYC: Implemented logic within the persona hook to automatically 'poke' an account after a successful Know Your Customer (KYC) process. This ensures that newly verified accounts are properly initialized and their assets are recognized by the system, including interaction with the new allower firewall.
  • Environment Variable Configuration: Added new environment variables (GCP_KMS_KEY_RING, GCP_KMS_KEY_VERSION, GCP_PROJECT_ID, GCP_BASE64_JSON) to .do/app.yaml and server/vitest.config.mts to support the configuration and testing of the GCP KMS integration.
  • Dependency Updates: Updated server/package.json to include @google-cloud/kms and @valora/viem-account-hsm-gcp, providing the necessary libraries for interacting with GCP KMS and integrating HSM-backed accounts with Viem.
Changelog
  • .changeset/lucky-jokes-change.md
    • Added a new changeset file to document the integration of GCP KMS for the allower functionality.
  • .changeset/silly-yaks-divide.md
    • Added a new changeset file to document the feature of poking accounts after KYC completion.
  • .do/app.yaml
    • Added new environment variables for GCP KMS configuration, including key ring, key version, project ID, and base64 encoded JSON credentials.
  • cspell.json
    • Added 'valora' to the custom spelling dictionary.
  • server/hooks/activity.ts
    • Removed unused ABI imports and the withRetry utility from viem.
    • Updated the import path for keeper to the new ../utils/accounts module.
    • Renamed accounts variable to accountLookup for clarity in database queries.
    • Modified the logic for processing transfers, replacing pokes map with a Set of accounts.
    • Refactored the account creation and poking logic to use the new keeper.poke function, simplifying asset handling and error management.
  • server/hooks/block.ts
    • Updated the import path for keeper to the new ../utils/accounts module.
  • server/hooks/panda.ts
    • Updated the import path for keeper to the new ../utils/accounts module.
  • server/hooks/persona.ts
    • Imported firewallAddress from @exactly/common/generated/chain.
    • Imported allower and keeper from the new ../utils/accounts module.
    • Added a getAllower function to lazily initialize the allower client.
    • Integrated allower().then((client) => client.allow(account)) call after risk assessment to allow the account through the firewall.
    • Added a keeper.poke call after KYC to update account assets with a notification.
    • Refactored the addCapita call to directly use the parsed account address, removing the safeParse check.
  • server/package.json
    • Added @google-cloud/kms as a dependency.
    • Added @valora/viem-account-hsm-gcp as a dependency.
  • server/script/openapi.ts
    • Added stub environment variables for GCP KMS configuration.
  • server/test/api/card.test.ts
    • Updated the mock import from ../mocks/keeper to ../mocks/accounts.
  • server/test/e2e.ts
    • Updated the mock import from ../mocks/keeper to ../mocks/accounts.
  • server/test/hooks/activity.test.ts
    • Updated the mock import from ../mocks/keeper to ../mocks/accounts.
    • Removed the test case for capturing 'no balance' errors after retries.
    • Adjusted captureException expectation to be more general.
    • Added anvilClient.setBalance calls to various tests to ensure sufficient balance for transactions.
    • Replaced exaSend spy with pokeSpy for keeper interactions.
    • Added a new test case to verify poke is called with the correct ignore option.
  • server/test/hooks/block.test.ts
    • Updated the mock import from ../mocks/keeper to ../mocks/accounts.
  • server/test/hooks/panda.test.ts
    • Updated the mock import from ../mocks/keeper to ../mocks/accounts.
  • server/test/hooks/persona.test.ts
    • Added mocks for accounts and firewallAddress.
    • Updated afterEach hook to restore all mocks.
    • Updated persona signature header in test payloads.
    • Added tests for poking assets when balances are positive, poking only ETH, skipping WETH when ETH balance is positive, and not poking when balances are zero.
    • Added a test case to verify error handling when the firewall call fails.
    • Added a mock for panda.createUser in the manteca template test.
  • server/test/mocks/accounts.ts
    • Renamed server/test/mocks/keeper.ts to server/test/mocks/accounts.ts.
    • Updated the mock to include allower and keeper from the new accounts module.
    • Configured allower mock to resolve with an allow function.
  • server/test/utils/accounts.test.ts
    • Renamed server/test/utils/keeper.test.ts to server/test/utils/accounts.test.ts.
    • Updated imports to reference the new accounts mock and module.
    • Updated type imports to use @exactly/common/validation for Hash.
  • server/test/utils/gcp.test.ts
    • Added a new test file to verify GCP credentials security, including file creation with secure permissions and early return for existing credentials.
  • server/utils/accounts.ts
    • Added a new file to define keeper and allower wallet clients.
    • Implemented extender function to add poke functionality to the wallet client, handling ETH and ERC20 asset poking based on balances.
    • Implemented withExaSend function for robust transaction sending with Sentry tracing, error handling, and nonce management.
    • Integrated GCP KMS for the allower client, including credential initialization and retry logic for KMS errors.
    • Defined getAccount to retrieve a local account from GCP HSM.
  • server/utils/gcp.ts
    • Added a new file to manage GCP credentials, including base64 decoding and secure file writing.
    • Implemented initializeGcpCredentials to ensure GCP service account credentials are set up securely.
    • Added hasCredentials to check for the existence of the credentials file.
    • Provided isRetryableKmsError to identify transient KMS errors for retry mechanisms.
  • server/utils/keeper.ts
    • Removed the file, as its functionality has been refactored into server/utils/accounts.ts.
  • server/vitest.config.mts
    • Added GCP KMS related environment variables for Vitest testing.
Activity
  • The pull request introduces new functionality and refactors existing code, indicating active development.
  • New changeset files were added, suggesting that these changes are intended for release notes.
  • Extensive test updates and additions were made, reflecting thorough testing of the new allower and refactored accounts modules.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@sentry
Copy link

sentry bot commented Feb 25, 2026

✅ All tests passed.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@aguxez aguxez marked this pull request as ready for review February 25, 2026 23:38
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

24 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +378 to +385
try {
captureRequests(parse(Requests, await request.clone().json()));
} catch (error: unknown) {
captureMessage("failed to parse or capture rpc requests", {
level: "error",
extra: { error },
});
}
Copy link

Choose a reason for hiding this comment

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

request.clone() called without checking if request body was already consumed

Suggested change
try {
captureRequests(parse(Requests, await request.clone().json()));
} catch (error: unknown) {
captureMessage("failed to parse or capture rpc requests", {
level: "error",
extra: { error },
});
}
async onFetchRequest(request) {
try {
const cloned = request.clone();
captureRequests(parse(Requests, await cloned.json()));
} catch (error: unknown) {
captureMessage("failed to parse or capture rpc requests", {
level: "error",
extra: { error },
});
}
},

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85464d766d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +128 to +131
const hasETH = settled.some((r) => r.asset === ETH && r.balance > 0n);
for (const { asset, market, balance } of settled) {
if (hasETH && asset === WETH) continue;
if (balance > 0n) assetsToPoke.push({ asset, market });

Choose a reason for hiding this comment

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

🚩 WETH skip logic is consistent with old code but operates on full balances

At server/utils/accounts.ts:128-131, when ETH balance is positive, WETH is skipped from poking. This mirrors the old activity.ts logic (if (assets.has(ETH)) assets.delete(WETH)), relying on the assumption that pokeETH wraps ETH to WETH and deposits the full WETH balance (including any pre-existing WETH). The difference is that the old code operated on the set of assets from a specific transfer, while the new code checks all account balances. If an account has both ETH and WETH balances, the pre-existing WETH is handled by pokeETH wrapping + depositing the combined total. This assumption should be verified against the pokeETH contract implementation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants