@cfxlabsinc/b2b-services
    Preparing search index...

    Cross-customer admin access to ledger accounts.

    Extends LedgerAccountCache, so this read shares the ledger-account-search namespace — and therefore the customer-id tag vocabulary — with the customer surface. That replaces the mutual cross-construction the two services used to do, in which each built a detached ServiceCache over the other's namespace purely to bust it.

    The inherited invalidateCache({ customerIds }) is the seam the Helius on-chain balance webhook calls, so admin pickers invalidate whenever the consumer dashboard does — including on balance changes.

    Hierarchy (View Summary)

    Index
    cache: ServiceCache<CachedSearchPage<CachedLedgerAccountSearchItem>>
    db: B2bDatabase
    • Get the given ledger account (admin, cross-customer)

      Parameters

      • args: { id: string }

      Returns Promise<
          {
              ok: true;
              value: | {
                  address: Address;
                  balance: BigNumber;
                  createdAt: Date;
                  id: string;
                  name: string;
                  notes?: string;
                  paymentInstrumentId?: string;
                  permissions: "ADMIN"
                  | "ALL";
                  referenceId?: string;
                  tokenAccounts: Record<"MOVEUSD", { address: Address }>;
                  updatedAt: Date;
                  visibility: "PRIVATE" | "PUBLIC";
              } & {
                  customerId: string;
                  customerName: string;
                  customerOrganizationId: string;
                  entityDisplayName: string;
                  entityId: string;
                  entityReferenceId: string
                  | null;
                  entityType: "IDENTITY" | "ORGANIZATION";
                  rawName: string | null;
              }
              | null;
          },
      >

    • Bust every cached page — consumer or admin — that a write touching customerIds could have affected.

      The collection tag is always cleared alongside, because an unscoped admin page carries only that tag and no customer id can reach it.

      keys deletes exact entries in addition to the tag surgery, passing the key arguments — this cache hashes and prefixes them. It exists because invalidateTag resolves keys through the FT index, which does not exist in memory mode (valkeyClient: null); there the tag bust is a total no-op, and an exact-key delete is the only thing that keeps read-after-write honest. Services that folded get onto search pass that get's arguments.

      These deletes only reach this instance's own key space, because the key space is partitioned per surface. That is sufficient, and a writer must not try to name the other surface's key:

      • With Valkey, the other surface's get entry is itself tagged (by customer id, or by the collection tag when unscoped), so the tag bust above already evicts it from the shared L2 and publishes the peer eviction.
      • Without Valkey, the two surfaces are separate instances with separate L1 maps, so deleting a key from this instance could never have affected the other one anyway.

      Parameters

      • __namedParameters: { customerIds: readonly string[]; keys?: readonly CacheKey[] }

      Returns Promise<void>

    • Tags to write on a page, derived from the scope the query searched — never from the customers present in the result.

      That distinction is load-bearing. An entry tagged with the customer ids in the page carries no tags when the page is empty, so nothing can ever bust it and a later create leaves it stale for the full L2 TTL. Tagging by scope means an empty page still carries the tag for what it searched.

      An unscoped read falls back to the collection tag, since no customer-id tag can reach a row whose owner did not exist when the entry was written.

      Parameters

      • __namedParameters: { customerIds: readonly string[] | undefined }

      Returns string[]

    • Edit an account's operator-owned properties: its name, who may debit it, whether other CFX customers can discover it, and the internal note.

      name and notes are nullable on purpose — both are genuinely absent for most accounts, and a field an operator can set but never unset is a trap. null name clears the column back to the Account • • • • 1234 display fallback; null notes drops the key out of the data jsonb rather than storing an empty string, so "has a note" stays a question with one answer.

      customerId is an optional ownership assertion, not a lookup key: pass it to make the write a no-op unless the account belongs to that customer.

      Parameters

      • __namedParameters: {
            customerId?: string;
            data: {
                name?: string | null;
                notes?: string | null;
                permissions?: "ADMIN" | "ALL";
                visibility?: "PRIVATE" | "PUBLIC";
            };
            id: string;
        }

      Returns Promise<
          | {
              ok: true;
              value: {
                  address: Address;
                  balance: BigNumber;
                  createdAt: Date;
                  id: string;
                  name: string;
                  notes?: string;
                  paymentInstrumentId?: string;
                  permissions: "ADMIN"
                  | "ALL";
                  referenceId?: string;
                  tokenAccounts: Record<"MOVEUSD", { address: Address }>;
                  updatedAt: Date;
                  visibility: "PRIVATE" | "PUBLIC";
              } & {
                  customerId: string;
                  customerName: string;
                  customerOrganizationId: string;
                  entityDisplayName: string;
                  entityId: string;
                  entityReferenceId: string
                  | null;
                  entityType: "IDENTITY" | "ORGANIZATION";
                  rawName: string | null;
              };
          }
          | { error: ServiceError; ok: false },
      >