Skip to content

✨ server: add allower#839

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

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

Conversation

@aguxez
Copy link
Copy Markdown
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 Google Cloud KMS (via @google-cloud/kms and @valora/viem-account-hsm-gcp) to sign firewall allow transactions with an HSM-backed key, and adds a new keeper.poke utility that proactively pokes account assets (ETH, WETH, ERC-20) after KYC approval and on activity webhooks.

Key changes:

  • server/utils/gcp.ts — new module handles credential bootstrapping (triple base64 decode → /tmp/gcp-service-account.json), lazy initialization with promise caching, and retryable KMS error classification.
  • server/utils/accounts.ts — adds getAccount() (GCP KMS → viem LocalAccount), allower() (wallet client that wraps allow on the Firewall contract), and poke() (scans balances and pokes every non-zero asset via exaSend).
  • server/hooks/persona.ts — after KYC approval calls allow then fire-and-forgets poke; allowerPromise caches the expensive KMS init across requests.
  • server/hooks/activity.ts — calls poke after account deployment, with NotAllowed in the ignore list.

Verified findings:

  • The delay function in poke's withRetry uses bitwise left-shift which overflows at count >= 31 (currently safe with retryCount: 10, but a footgun if the parameter is raised).
  • The DECODING_ITERATIONS = 3 constant lacks documentation explaining the triple base64 encoding strategy.
  • The keeper client's onFetchRequest hook lacks error handling, unlike the allower version, making it susceptible to unhandled exceptions on malformed RPC payloads.

Confidence Score: 3/5

  • Mergeable with awareness of three concrete code-quality issues requiring follow-up.
  • The PR's core allow+poke flow is functional and well-tested. However, three verified findings need attention: (1) the bitwise-shift overflow in retry delay is a preventable footgun if retryCount is raised, (2) the triple base64 decoding strategy lacks documentation creating fragility, and (3) the missing try/catch in keeper's onFetchRequest creates an inconsistency with allower that could surface unhandled exceptions. None of these are critical under normal conditions, but they represent code-quality debt worth fixing before production ramp-up.
  • server/utils/accounts.ts (bitwise-shift delay, error handling inconsistency) and server/utils/gcp.ts (undocumented constant).

Last reviewed commit: 0275499

Context used:

  • Rule from dashboard - AGENTS.md (source)
  • Rule from dashboard - CLAUDE.md (source)

This is part 1 of 2 in a stack made with GitButler:

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 25, 2026

🦋 Changeset detected

Latest commit: f9dc301

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
Copy Markdown

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
  • 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
Copy Markdown

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
Copy Markdown

sentry bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 73.52941% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.70%. Comparing base (e6efb1b) to head (f9dc301).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
server/utils/gcp.ts 74.07% 3 Missing and 4 partials ⚠️
server/hooks/persona.ts 71.42% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #839   +/-   ##
=======================================
  Coverage   71.69%   71.70%           
=======================================
  Files         228      229    +1     
  Lines        8277     8311   +34     
  Branches     2661     2668    +7     
=======================================
+ Hits         5934     5959   +25     
- Misses       2113     2116    +3     
- Partials      230      236    +6     
Flag Coverage Δ
e2e 52.44% <58.82%> (-19.26%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

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
greptile-apps[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the gcp-allower branch 2 times, most recently from c642d46 to 88aa6aa Compare March 2, 2026 12:51
sentry[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

sentry[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the gcp-allower branch 2 times, most recently from 4f4d674 to 1216f5f Compare March 27, 2026 14:10
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the gcp-allower branch 2 times, most recently from 9f8058b to 3b5805c Compare April 1, 2026 10:01
Copy link
Copy Markdown

@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: 700b17194b

ℹ️ 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".

Comment on lines +309 to +313
kms("allower")
.then((client) =>
client.exaSend(
{ name: "firewall.allow", op: "exa.firewall", attributes: { account: account.output } },
{ address, functionName: "allow", args: [account.output, true], abi: firewallAbi },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make firewall allow retryable on transient KMS/RPC failures

This kms("allower") call is fire-and-forget and its failures are only captured, while the handler still continues to createUser and returns HTTP 200. I checked the server code paths for firewall.allow, and this is the only place it is triggered, so a transient KMS/RPC failure here leaves a KYC-approved account permanently unallowed (later flows will keep encountering NotAllowed) with no automatic recovery path. Please make this side effect durable/retryable (or fail the webhook when allow cannot be scheduled).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@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 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +305 to +319
if (firewallAddress) {
const account = safeParse(Address, credential.account);
if (account.success) {
const address = firewallAddress;
kms("allower")
.then((client) =>
client.exaSend(
{ name: "firewall.allow", op: "exa.firewall", attributes: { account: account.output } },
{ address, functionName: "allow", args: [account.output, true], abi: firewallAbi },
{ ignore: [`AlreadyAllowed(${account.output})`] },
),
)
.catch((error: unknown) => captureException(error, { level: "error" }));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Fire-and-forget firewall allow becomes non-retriable after pandaId is set

The firewall allow call at server/hooks/persona.ts:309-317 is fire-and-forget (errors caught and sent to Sentry). If this call fails but the subsequent createUser at line 322 and DB update at line 332 both succeed, the pandaId guard at line 248 will cause any Persona webhook retry to return "already created" without re-attempting the firewall allow. This means a failed firewall allow is permanently lost and requires manual intervention. The team explicitly tests this behavior ("captures allower init failure without blocking panda creation" and "captures exaSend failure without blocking panda creation"), so this appears intentional. However, if the firewall is a hard requirement for the account to function, this could leave users in a broken state after KYC approval.

Open in Devin Review

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

client.exaSend(
{ name: "firewall.allow", op: "exa.firewall", attributes: { account: account.output } },
{ address, functionName: "allow", args: [account.output, true], abi: firewallAbi },
{ ignore: [`AlreadyAllowed(${account.output})`] },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The error handling logic compares a checksummed address (account.output) with a potentially non-checksummed address from a revert reason, which may cause the comparison to fail.
Severity: MEDIUM

Suggested Fix

Ensure both addresses in the comparison are in the same format. Either apply checksumAddress to the address extracted from the error's cause.data.args before comparing it, or convert account.output to a non-checksummed format for the check.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: server/hooks/persona.ts#L314

Potential issue: In `persona.ts`, the error handling logic checks if a revert reason
string includes a user's account address. The address used for this check,
`account.output`, is checksummed. However, the revert reason is constructed by directly
converting error arguments to strings using `cause.data?.args?.map(String)`, which
likely does not apply checksumming. This mismatch between a checksummed address and a
non-checksummed address in the error string will cause the check to fail, preventing
certain errors from being correctly ignored.

Did we get this right? 👍 / 👎 to inform future reviews.

aguxez and others added 3 commits April 8, 2026 12:50
co-authored-by: Miguel Diaz <github.com.hf06j@slmail.me>
co-authored-by: Miguel Diaz <github.com.hf06j@slmail.me>
Copy link
Copy Markdown

@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 1 new potential issue.

View 9 additional findings in Devin Review.

Open in Devin Review

Comment on lines +49 to +67
export async function kms(key: string) {
const account = await gcpHsmToAccount({
hsmKeyVersion: `projects/${projectId}/locations/us-west2/keyRings/${keyRing}/cryptoKeys/${key}/cryptoKeyVersions/${version}`,
kmsClient: new KeyManagementServiceClient({ keyFilename: await setupCredentials() }),
});
account.nonceManager = nonceManager;
return extender(
createWalletClient({
chain,
transport: http(`${chain.rpcUrls.alchemy.http[0]}/${alchemyAPIKey}`, {
batch: true,
async onFetchRequest(request) {
captureRequests(parse(Requests, await request.json()));
},
}),
account,
}),
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 kms() creates a new KMS client and fetches public key on every invocation — no caching

Every call to kms() at server/utils/gcp.ts:49-67 creates a brand new KeyManagementServiceClient (establishing a gRPC connection) and calls gcpHsmToAccount (which makes a KMS API call to retrieve the public key). Since kms("allower") is called on every KYC approval in server/hooks/persona.ts:309, this means each approval triggers:

  • A new gRPC connection to GCP KMS
  • An API call to fetch the same immutable public key
  • A new WalletClient with a new HTTP transport

The public key for a given KMS key version never changes, so the resolved account and client could be cached (e.g., in a Map<string, Promise<...>>). Under load, the current implementation could exhaust GCP KMS API quotas and add unnecessary latency to the firewall allow operation.

Prompt for agents
The kms() function in server/utils/gcp.ts creates a new KeyManagementServiceClient, calls gcpHsmToAccount (an API call to fetch the public key), and creates a new WalletClient on every invocation. Since the public key for a given KMS key version is immutable, the result can be cached.

A simple approach: maintain a module-level Map<string, Promise<ReturnType<typeof extender>>> keyed by the key name (e.g. 'allower'). On first call for a given key, store the promise. On subsequent calls, return the cached promise. Add error handling to clear the cache entry if the promise rejects (similar to the pending pattern already used in setupCredentials()).

This avoids repeated gRPC connection setup and KMS API calls for the same key.
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