Skip to content

feat: add tenant remove command#597

Merged
michael1011 merged 1 commit into
masterfrom
feat/add-tenant-remove
Nov 24, 2025
Merged

feat: add tenant remove command#597
michael1011 merged 1 commit into
masterfrom
feat/add-tenant-remove

Conversation

@kilrau

@kilrau kilrau commented Nov 21, 2025

Copy link
Copy Markdown
Member

closes #161

Summary by CodeRabbit

  • New Features
    • Tenant removal added to CLI and API with confirmation and --force; tenant lifecycle management added including a default tenant and CRUD operations.
  • Bug Fixes
    • Fix wallet import description typo ("ot" → "or") and adjust wallet command category/usage for clarity.
  • Documentation
    • Add RemoveTenant API docs and update related RPC docs and descriptions.
  • Tests
    • Add database and RPC tests covering tenant lifecycle, removal rules, and permissions.

✏️ Tip: You can customize this high-level summary in your review settings.

@kilrau kilrau requested a review from jackstar12 November 21, 2025 22:52
@kilrau kilrau self-assigned this Nov 21, 2025
@coderabbitai

coderabbitai Bot commented Nov 21, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds tenant model and DB operations, a RemoveTenant RPC and client method, RPC-server removal logic with checks and permission, CLI tenant remove command and CLI wallet metadata fixes, documentation for RemoveTenant, and tests for DB and RPC behaviors.

Changes

Cohort / File(s) Summary
CLI: Wallet & Tenant commands
cmd/boltzcli/commands.go
Adjust wallet command metadata (Category → "Wallet", Usage shortened) and fix import description text; add tenant remove subcommand with --force confirmation that calls client.RemoveTenant(name) and integrate tenant handling for wallet commands.
gRPC proto & client
pkg/boltzrpc/boltzrpc.proto, pkg/boltzrpc/client/client.go
Add RemoveTenant(RemoveTenantRequest) returns (google.protobuf.Empty) and message RemoveTenantRequest { string name = 1; }; add client method Boltz.RemoveTenant(name string) error.
RPC server & permissions
internal/rpcserver/router.go, internal/macaroons/permissions.go
Add RemoveTenant RPC handler: lookup by name, forbid removing default tenant, check HasTenantWallets transactionally, delete tenant if empty; add macaroon permission entry for endpoint.
Database: tenant model & helpers
internal/database/tenant.go
Add Tenant type, DefaultTenantId/DefaultTenantName and DefaultTenant var; implement CreateDefaultTenant, CreateTenant, GetTenant, GetTenantByName, QueryTenants, DeleteTenant, HasTenantWallets, and parseTenant.
Tests
internal/database/tenant_test.go, internal/rpcserver/rpcserver_test.go
Add DB tests for tenant CRUD, query, HasTenantWallets and default tenant; add RPC tests for RemoveTenant cases (non-existent, default, with wallets, without wallets, permission checks).
Docs
docs/grpc.md
Document RemoveTenantRequest and RemoveTenant RPC; unify inline protobuf type references; fix GetTenant typo.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as boltzcli
    participant Client as boltz client
    participant RPC as RPC Server
    participant DB as Database

    rect rgb(220,240,250)
    note over CLI,RPC: RemoveTenant flow
    CLI->>Client: RemoveTenant(name)
    Client->>RPC: RemoveTenantRequest{name}
    RPC->>DB: GetTenantByName(name)
    alt tenant not found
        RPC-->>Client: NotFound
    else found
        RPC->>RPC: if tenant == default -> InvalidArgument
        alt default tenant
            RPC-->>Client: InvalidArgument
        else
            RPC->>DB: HasTenantWallets(tenantId)
            alt has wallets
                RPC-->>Client: FailedPrecondition
            else no wallets
                RPC->>DB: DeleteTenant(tenantId) (tx)
                RPC-->>Client: Empty (success)
            end
        end
    end
    end
Loading
sequenceDiagram
    participant CLI as boltzcli
    participant Client as boltz client
    participant RPC as RPC Server
    participant DB as Database

    rect rgb(240,250,220)
    note over CLI,RPC: Create/Import Wallet with tenant
    CLI->>Client: CreateWallet(params, tenantName?)
    alt tenantName provided
        Client->>RPC: Resolve tenant -> GetTenantByName(tenantName)
        RPC->>DB: GetTenantByName
        DB-->>RPC: Tenant{id}
        RPC-->>Client: Tenant{id}
        Client->>RPC: CreateWallet(params with tenant_id)
    else none
        Client->>RPC: CreateWallet(params)
    end
    RPC->>DB: CreateWallet(record)
    DB-->>RPC: Wallet created
    RPC-->>Client: Success
    Client-->>CLI: Success
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • internal/rpcserver/router.go — transactional deletion, accurate gRPC error mapping, and permission enforcement.
    • internal/database/tenant.go — correctness of HasTenantWallets query, default tenant creation, and ID handling.
    • cmd/boltzcli/commands.go — tenant name → id resolution and confirmation/force behavior.
    • pkg/boltzrpc/boltzrpc.proto & client — proto compatibility and client method wiring.

Possibly related PRs

Suggested reviewers

  • jackstar12

Poem

🐇 I hopped through fields and rows tonight,

Gave tenants names and kept their rights.
I checked each chest before I roam,
If wallets hide, I leave it alone.
A tiny remove, with care and light — hop on!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR only partially addresses issue #161. It implements tenant remove functionality but fails to add wallet entity association and wallet create entity parameter. Implement the remaining requirements: add wallet create entity parameter and complete entity-related command fixes mentioned in issue #161.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add tenant remove command' clearly and concisely describes the main change—adding a tenant remove command functionality.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the tenant remove command and associated infrastructure (database, RPC, CLI, permissions, tests).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-tenant-remove

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 597a2c4 and 6c016cc.

⛔ Files ignored due to path filters (4)
  • pkg/boltzrpc/autoswaprpc/autoswaprpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (9)
  • cmd/boltzcli/commands.go (3 hunks)
  • docs/grpc.md (6 hunks)
  • internal/database/tenant.go (7 hunks)
  • internal/database/tenant_test.go (1 hunks)
  • internal/macaroons/permissions.go (1 hunks)
  • internal/rpcserver/router.go (2 hunks)
  • internal/rpcserver/rpcserver_test.go (1 hunks)
  • pkg/boltzrpc/boltzrpc.proto (2 hunks)
  • pkg/boltzrpc/client/client.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-16T09:46:41.613Z
Learnt from: jackstar12
Repo: BoltzExchange/boltz-client PR: 487
File: internal/rpcserver/router.go:2304-2314
Timestamp: 2025-07-16T09:46:41.613Z
Learning: In the boltz-client codebase, RPC methods are protected by the macaroon permission system defined in internal/macaroons/permissions.go. Methods like GetSwapMnemonic and SetSwapMnemonic are already restricted to admin entities through the RPCServerPermissions map, so explicit isAdmin(ctx) checks are not needed in these methods.

Applied to files:

  • internal/macaroons/permissions.go
🧬 Code graph analysis (3)
pkg/boltzrpc/client/client.go (1)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/rpcserver/router.go (3)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/database/database.go (2)
  • Id (336-336)
  • Transaction (249-251)
internal/database/tenant.go (1)
  • DefaultTenantId (18-18)
internal/database/tenant.go (3)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • Tenant (557-564)
  • Tenant (579-579)
  • Tenant (594-596)
internal/database/database.go (1)
  • Database (240-247)
internal/logger/logger.go (1)
  • Errorf (97-99)
🔇 Additional comments (11)
docs/grpc.md (1)

85-85: LGTM! Documentation accurately reflects the new RemoveTenant API.

The documentation changes correctly describe the new RemoveTenant RPC, including the precondition that the tenant must have no associated wallets. The formatting changes for protobuf types are also appropriate.

Also applies to: 285-285, 293-293, 309-309, 329-342, 1296-1304, 1887-1887

internal/database/tenant.go (1)

107-121: LGTM! HasTenantWallets correctly uses errors.Is for sql.ErrNoRows.

The past review comment about using the sentinel error instead of string comparison has been properly addressed. The implementation now uses errors.Is(err, sql.ErrNoRows) which is the idiomatic Go approach.

pkg/boltzrpc/client/client.go (1)

194-197: LGTM! Client method correctly wraps the RemoveTenant RPC.

The implementation is consistent with other client methods and properly constructs the RemoveTenantRequest message.

pkg/boltzrpc/boltzrpc.proto (1)

193-200: LGTM! Proto definitions are well-structured and consistent.

The RemoveTenant RPC and RemoveTenantRequest message follow the established patterns in the codebase. The comment correctly documents the wallet precondition.

Also applies to: 234-236

internal/rpcserver/rpcserver_test.go (1)

528-574: LGTM! Comprehensive test coverage for RemoveTenant.

The test suite thoroughly validates all expected scenarios:

  • Non-existent tenant (NotFound)
  • Default tenant protection (InvalidArgument)
  • Wallet association check (FailedPrecondition)
  • Successful removal of empty tenant
  • Permission enforcement (admin-only access)
internal/macaroons/permissions.go (1)

200-203: LGTM! Permission correctly restricts RemoveTenant to admin write.

The permission entry is consistent with other tenant management operations and appropriately restricts tenant removal to admin-level access. Based on learnings, the macaroon permission system handles authorization, so explicit isAdmin(ctx) checks in the RPC handler are not required.

internal/rpcserver/router.go (2)

44-45: Use of emptypb.Empty for new RPC is appropriate

Importing google.golang.org/protobuf/types/known/emptypb to return *emptypb.Empty from RemoveTenant aligns with the proto definition and keeps the RPC surface consistent.


2345-2377: RemoveTenant behavior matches requirements and is transaction-safe

The method correctly:

  • Resolves the tenant by name and returns NotFound when it does not exist.
  • Rejects removal of the default tenant with InvalidArgument.
  • Uses a transaction to check HasTenantWallets and fails with FailedPrecondition when wallets exist.
  • Deletes the tenant inside the same transaction and returns an empty response on success.

This is aligned with the intended semantics and avoids partial updates.

internal/database/tenant_test.go (1)

12-100: Solid coverage of tenant DB lifecycle and associations

This test suite nicely exercises tenant creation, lookup by ID/name, listing, wallet-association checks, deletion (including non-existent IDs), and default-tenant invariants against an in-memory DB. The structure with subtests keeps it readable and focused.

cmd/boltzcli/commands.go (2)

1555-1582: Wallet command metadata and description cleanups look good

Changing the wallet command category/usage strings and fixing the import description to “BTC or LBTC” improves CLI help text without affecting behavior. All arguments and semantics remain consistent with the existing commands.


2299-2328: Tenant remove CLI mirrors server-side semantics with safe UX

The tenant remove subcommand:

  • Enforces a tenant name via requireNArgs(1, ...).
  • Adds a --force / -f flag to skip confirmation when desired.
  • Uses a confirmation prompt that includes the tenant name before calling client.RemoveTenant.
  • Reports a clear success message.

This is consistent with other destructive commands (like wallet remove) and aligns with the RemoveTenant RPC behavior.


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.

@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)
internal/rpcserver/router.go (1)

1468-1483: WalletParams.tenant_id is ignored in ImportWallet/CreateWallet

WalletParams now has a tenant_id field, and the tests (e.g. TestMacaroons/AdminCreateForTenant) expect an admin to be able to create a wallet for a specific tenant by setting it. However, ImportWallet still unconditionally uses requireTenantId(ctx):

currency := serializers.ParseCurrency(&request.Params.Currency)
credentials := &onchain.WalletCredentials{
    WalletInfo: onchain.WalletInfo{
        Name:     request.Params.Name,
        Currency: currency,
        TenantId: requireTenantId(ctx),
    },
    ...
}

So:

  • For admin macaroons without a tenant bound, wallets are always created under DefaultTenantId, regardless of params.tenant_id.
  • The new CLI wallet create --tenant and the new tests’ expectations about cross-tenant wallet creation won’t actually be met.
  • There is currently no way for an admin to seed wallets directly into a different tenant without switching the whole context via SetTenant.

Suggested fix (outline):

  • Derive a tenantId variable that can be overridden when the caller is admin and a tenant_id is provided, and validate the tenant exists:
 func (server *routedBoltzServer) ImportWallet(ctx context.Context, request *boltzrpc.ImportWalletRequest) (*boltzrpc.Wallet, error) {
     if request.Params == nil {
         return nil, errors.New("missing wallet parameters")
     }
     if err := checkName(request.Params.Name); err != nil {
         return nil, err
     }

-    currency := serializers.ParseCurrency(&request.Params.Currency)
+    currency := serializers.ParseCurrency(&request.Params.Currency)
+    tenantId := requireTenantId(ctx)
+    if request.Params.TenantId != nil {
+        // Only admins should be allowed to override the target tenant.
+        if !isAdmin(ctx) {
+            return nil, status.Errorf(codes.PermissionDenied, "only admin can specify tenant_id")
+        }
+        // Ensure the tenant exists before creating a wallet for it.
+        id := database.Id(request.Params.GetTenantId())
+        if _, err := server.database.GetTenant(id); err != nil {
+            if errors.Is(err, sql.ErrNoRows) {
+                return nil, status.Errorf(codes.NotFound, "tenant %d does not exist", id)
+            }
+            return nil, err
+        }
+        tenantId = id
+    }
     credentials := &onchain.WalletCredentials{
         WalletInfo: onchain.WalletInfo{
             Name:     request.Params.Name,
             Currency: currency,
-            TenantId: requireTenantId(ctx),
+            TenantId: tenantId,
         },
         ...
     }

This would:

  • Make tenant_id on WalletParams actually effective for admin RPC/CLI callers.
  • Preserve existing behavior for tenant-scoped macaroons (they can’t escape their tenant even if they try to set tenant_id).
  • Align runtime behavior with the new proto docs and TestMacaroons/AdminCreateForTenant.
🧹 Nitpick comments (4)
internal/database/tenant.go (1)

89-99: HasTenantWallets is correct; consider EXISTS for efficiency

The implementation is logically sound and matches the intended boolean “has wallets” semantics. If the wallets table grows large and this check is on a hot path, you could switch to an existence check instead of COUNT(*), e.g. SELECT 1 FROM wallets WHERE tenantId = ? LIMIT 1, and treat sql.ErrNoRows as “no wallets” to avoid scanning more rows than necessary.

internal/rpcserver/router.go (1)

2345-2371: RemoveTenant handler enforces invariants; consider DB-level protection for races

The handler correctly:

  • Returns NotFound when the tenant name doesn’t exist.
  • Rejects deletion of the default tenant.
  • Checks HasTenantWallets and fails with FailedPrecondition when wallets are associated.
  • Deletes the tenant otherwise and returns an empty response.

If you need a hard guarantee that a tenant with wallets can never be deleted even under concurrent load, consider enforcing this at the database level (e.g. a FOREIGN KEY (tenantId) REFERENCES tenants(id) ON DELETE RESTRICT) or wrapping the wallet check and delete into a single transaction that re-verifies the precondition before delete. As written, there’s a small window between HasTenantWallets and DeleteTenant where a new wallet could be created.

internal/database/tenant_test.go (1)

43-47: Clarify or adjust the hardcoded tenant count expectation.

The test asserts that at least 3 tenants exist, but by this point the test has created multiple tenants ("test-tenant", "named-tenant") plus the default tenant. The expected count should reflect the actual tenants created or use a more specific assertion.

Consider either:

  1. Asserting the exact count: require.Equal(t, len(tenants), 3) (or the actual count at this point)
  2. Adding a comment explaining why >= 3 is used
  3. Counting only the tenants created in this test function to make the test more robust
 	t.Run("QueryTenants", func(t *testing.T) {
 		tenants, err := db.QueryTenants()
 		require.NoError(t, err)
-		require.GreaterOrEqual(t, len(tenants), 3)
+		// At this point we have: default tenant + test-tenant + named-tenant
+		require.GreaterOrEqual(t, len(tenants), 3, "should have at least default tenant plus created tenants")
 	})
cmd/boltzcli/commands.go (1)

1571-1578: Reduce code duplication with a helper function.

The tenant lookup logic is duplicated between wallet create and wallet import commands. Consider extracting this into a reusable helper function.

Add a helper function before the wallet create command:

func getTenantIdFromName(ctx *cli.Context, tenantName string) (*uint64, error) {
	if tenantName == "" {
		return nil, nil
	}
	client := getClient(ctx)
	tenant, err := client.GetTenant(tenantName)
	if err != nil {
		return nil, fmt.Errorf("tenant '%s' does not exist", tenantName)
	}
	return &tenant.Id, nil
}

Then update both wallet create and import to use it:

 				if tenantName := ctx.String("tenant"); tenantName != "" {
-					client := getClient(ctx)
-					tenant, err := client.GetTenant(tenantName)
+					tenantId, err := getTenantIdFromName(ctx, tenantName)
 					if err != nil {
-						return fmt.Errorf("tenant '%s' does not exist", tenantName)
+						return err
 					}
-					info.TenantId = &tenant.Id
+					info.TenantId = tenantId
 				}

Also applies to: 1601-1608

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d65dd25 and b024cd6.

⛔ Files ignored due to path filters (4)
  • pkg/boltzrpc/autoswaprpc/autoswaprpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (9)
  • cmd/boltzcli/commands.go (3 hunks)
  • docs/grpc.md (3 hunks)
  • internal/database/tenant.go (1 hunks)
  • internal/database/tenant_test.go (1 hunks)
  • internal/macaroons/permissions.go (1 hunks)
  • internal/rpcserver/router.go (2 hunks)
  • internal/rpcserver/rpcserver_test.go (1 hunks)
  • pkg/boltzrpc/boltzrpc.proto (3 hunks)
  • pkg/boltzrpc/client/client.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-16T09:46:41.613Z
Learnt from: jackstar12
Repo: BoltzExchange/boltz-client PR: 487
File: internal/rpcserver/router.go:2304-2314
Timestamp: 2025-07-16T09:46:41.613Z
Learning: In the boltz-client codebase, RPC methods are protected by the macaroon permission system defined in internal/macaroons/permissions.go. Methods like GetSwapMnemonic and SetSwapMnemonic are already restricted to admin entities through the RPCServerPermissions map, so explicit isAdmin(ctx) checks are not needed in these methods.

Applied to files:

  • internal/macaroons/permissions.go
  • internal/rpcserver/rpcserver_test.go
🧬 Code graph analysis (5)
internal/database/tenant.go (1)
internal/database/database.go (1)
  • Database (240-247)
internal/rpcserver/router.go (4)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/onchain/onchain.go (1)
  • Id (21-21)
internal/database/database.go (1)
  • Id (336-336)
internal/database/tenant.go (1)
  • DefaultTenantId (12-12)
internal/rpcserver/rpcserver_test.go (3)
pkg/boltzrpc/boltzrpc.pb.go (16)
  • WalletParams (4492-4503)
  • WalletParams (4518-4518)
  • WalletParams (4533-4535)
  • Currency (132-132)
  • Currency (161-163)
  • Currency (165-167)
  • Currency (174-176)
  • Wallet (5899-5910)
  • Wallet (5925-5925)
  • Wallet (5940-5942)
  • BakeMacaroonRequest (659-666)
  • BakeMacaroonRequest (681-681)
  • BakeMacaroonRequest (696-698)
  • Wallets (5986-5992)
  • Wallets (6007-6007)
  • Wallets (6022-6024)
pkg/boltzrpc/client/client.go (2)
  • FullPermissions (20-23)
  • NewBoltzClient (29-34)
pkg/boltzrpc/client/connection.go (1)
  • Connection (17-29)
pkg/boltzrpc/client/client.go (1)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/database/tenant_test.go (3)
internal/database/database.go (1)
  • Database (240-247)
internal/database/tenant.go (3)
  • Tenant (7-10)
  • DefaultTenantId (12-12)
  • DefaultTenantName (13-13)
pkg/boltz/currency.go (1)
  • CurrencyBtc (11-11)
🪛 markdownlint-cli2 (0.18.1)
docs/grpc.md

341-341: Link fragments should be valid

(MD051, link-fragments)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (9)
internal/macaroons/permissions.go (1)

188-205: LGTM: RemoveTenant correctly restricted to admin write

The new /boltzrpc.Boltz/RemoveTenant entry is consistent with the existing tenant admin RPCs (CreateTenant, ListTenants, GetTenant) and ensures only admin macaroons can remove tenants, so no extra isAdmin checks are needed in the handler. Based on learnings.

pkg/boltzrpc/client/client.go (1)

194-197: LGTM: Client helper cleanly wraps RemoveTenant RPC

The helper mirrors the existing style (e.g. Stop, RemoveWallet), passes the name through, and returns only the RPC error, which is exactly what the CLI needs.

pkg/boltzrpc/boltzrpc.proto (1)

193-201: Proto surface for tenant removal and wallet tenant_id looks good

The new pieces here line up well:

  • rpc RemoveTenant(RemoveTenantRequest) returns (google.protobuf.Empty); plus message RemoveTenantRequest { string name = 1; } are consistent with the router’s RemoveTenant handler and the permission model (admin-only, and guarded by wallet association checks).
  • The WalletParams.tenant_id field and its comment (“Defaults to the current tenant. Only relevant for admin users.”) clearly describe the intended behavior and match how the tests use it (admin creating wallets for another tenant).

From the proto/API perspective this all looks coherent; the remaining work is purely on the server side to honor tenant_id in ImportWallet/CreateWallet as noted in the router.go comment.

Also applies to: 234-237, 784-786

internal/rpcserver/rpcserver_test.go (2)

527-561: Strong coverage for admin-created tenant wallets

This subtest nicely captures the intended semantics of WalletParams.tenant_id:

  • Admin creates a dedicated tenant.
  • Admin creates a wallet with TenantId set to that tenant.
  • A macaroon scoped to the new tenant sees exactly that wallet and not the admin’s wallets.
  • The original tenant still only sees its own wallet.

Once the server side honors tenant_id in ImportWallet/CreateWallet (see router.go comment), this will be a solid regression test for tenant-scoped wallet visibility.


564-614: Comprehensive RemoveTenant behavior tests

The RemoveTenant suite covers all the important cases:

  • NonExistent: verifies NotFound and error text.
  • Default: protects the default admin tenant with InvalidArgument.
  • WithWallets: ensures tenants with wallets (both the main test tenant and the “wallet-tenant”) are rejected with FailedPrecondition.
  • WithoutWallets: validates the happy path by creating, fetching, deleting, and then observing NotFound.
  • Permission: confirms a tenant-scoped macaroon gets PermissionDenied for tenant removal while admin succeeds.

This is a good, end-to-end specification of the new RPC’s contract and will keep the server and DB logic honest.

docs/grpc.md (1)

329-329: LGTM! Documentation updates are clear and consistent.

The documentation changes properly describe the new RemoveTenant RPC, including the important constraint that removal is only allowed when no wallets are associated. The typo fix and new tenant_id field in WalletParams are also correctly documented.

Note: The static analysis warning about the link fragment on line 341 appears to be a false positive, as the pattern [.google.protobuf.Empty](#.google.protobuf.empty) is used consistently throughout this auto-generated documentation.

Also applies to: 335-341, 1296-1304, 1643-1643

internal/database/tenant_test.go (1)

12-100: Excellent test coverage for tenant operations!

The test comprehensively covers all critical tenant operations including creation, retrieval, listing, wallet associations, deletion, and the default tenant. The test structure is clear and well-organized using subtests.

cmd/boltzcli/commands.go (2)

1557-1558: Good improvements to user-facing text!

The category name change from "wallet" to "Wallet", the updated usage text, and the typo fix ("ot" → "or") improve consistency and clarity.

Also applies to: 1595-1595


2327-2342: Tenant remove command looks good overall.

The command is well-structured with clear description and proper error handling. Once the confirmation prompt is added, this will be production-ready.

Comment thread cmd/boltzcli/commands.go
@kilrau kilrau force-pushed the feat/add-tenant-remove branch from b024cd6 to fa71438 Compare November 21, 2025 23:34

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
internal/database/tenant.go (1)

7-41: Tighten error handling in CreateDefaultTenant, fix locking in write operations, and use sql.ErrNoRows properly

Three issues confirmed in this block:

  1. CreateDefaultTenant swallows GetTenant errors

    • Line 28: defaultTenant, _ := d.GetTenant(DefaultTenantId) discards all errors, masking real database issues during startup.
    • Should explicitly check for sql.ErrNoRows and propagate other errors.
  2. Write operations lack d.lock synchronization

    • CreateTenant (lines 35–40) and DeleteTenant (lines 86–94) in tenant.go perform writes without acquiring d.lock.
    • This is inconsistent with read operations (GetTenant, GetTenantByName, QueryTenants) which all use d.lock.RLock().
    • Same pattern exists in wallet.go: CreateWallet, UpdateWalletCredentials, DeleteWallet, and SetWalletSubaccount all lack locks.
    • For thread safety on shared state like d.tx / d.db, write operations should acquire d.lock.Lock().
  3. HasTenantWallets uses brittle string comparison

    • Line 110: if err.Error() == "sql: no rows in result set" should use errors.Is(err, sql.ErrNoRows) instead, following Go standard library conventions.

Example fixes:

 func (d *Database) CreateDefaultTenant() error {
-	defaultTenant, _ := d.GetTenant(DefaultTenantId)
-	if defaultTenant == nil {
+	defaultTenant, err := d.GetTenant(DefaultTenantId)
+	if err != nil && !errors.Is(err, sql.ErrNoRows) {
+		return fmt.Errorf("failed to get default tenant: %w", err)
+	}
+	if defaultTenant != nil {
+		return nil
+	}
-		if err := d.CreateTenant(&Tenant{Id: DefaultTenantId, Name: DefaultTenantName}); err != nil {
-			return err
-		}
-	}
-	return nil
+	return d.CreateTenant(&Tenant{Id: DefaultTenantId, Name: DefaultTenantName})
 }

 func (d *Database) CreateTenant(tenant *Tenant) error {
+	d.lock.Lock()
+	defer d.lock.Unlock()
+
 	query := "INSERT INTO tenants (name) VALUES (?) RETURNING id"
 	row := d.QueryRow(query, tenant.Name)
 	return row.Scan(&tenant.Id)
 }

 func (d *Database) HasTenantWallets(tenantId Id) (bool, error) {
 	d.lock.RLock()
 	defer d.lock.RUnlock()
 	query := "SELECT 1 FROM wallets WHERE tenantId = ? LIMIT 1"
 	var exists int
 	err := d.QueryRow(query, tenantId).Scan(&exists)
 	if err != nil {
-		if err.Error() == "sql: no rows in result set" {
+		if errors.Is(err, sql.ErrNoRows) {
 			return false, nil
 		}
 		return false, fmt.Errorf("failed to check tenant wallets: %w", err)
 	}
 	return true, nil
 }

Also applies to: DeleteTenant (lines 86–94) and wallet.go write methods.

🧹 Nitpick comments (1)
internal/database/tenant_test.go (1)

12-100: Good coverage of tenant lifecycle and wallet association

This test suite nicely exercises tenant creation, lookup by id/name, listing, wallet association (HasTenantWallets), deletion (including non‑existent id), and the default tenant invariants. For longer‑running test suites you might optionally defer db.Close() after Connect() to keep resource handling uniform across DB tests, but it’s not critical for an in‑memory database here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b024cd6 and fa71438.

⛔ Files ignored due to path filters (4)
  • pkg/boltzrpc/autoswaprpc/autoswaprpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (9)
  • cmd/boltzcli/commands.go (3 hunks)
  • docs/grpc.md (3 hunks)
  • internal/database/tenant.go (7 hunks)
  • internal/database/tenant_test.go (1 hunks)
  • internal/macaroons/permissions.go (1 hunks)
  • internal/rpcserver/router.go (2 hunks)
  • internal/rpcserver/rpcserver_test.go (1 hunks)
  • pkg/boltzrpc/boltzrpc.proto (3 hunks)
  • pkg/boltzrpc/client/client.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/rpcserver/rpcserver_test.go
  • internal/rpcserver/router.go
  • pkg/boltzrpc/boltzrpc.proto
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-16T09:46:41.613Z
Learnt from: jackstar12
Repo: BoltzExchange/boltz-client PR: 487
File: internal/rpcserver/router.go:2304-2314
Timestamp: 2025-07-16T09:46:41.613Z
Learning: In the boltz-client codebase, RPC methods are protected by the macaroon permission system defined in internal/macaroons/permissions.go. Methods like GetSwapMnemonic and SetSwapMnemonic are already restricted to admin entities through the RPCServerPermissions map, so explicit isAdmin(ctx) checks are not needed in these methods.

Applied to files:

  • internal/macaroons/permissions.go
🧬 Code graph analysis (3)
internal/database/tenant_test.go (3)
internal/database/database.go (1)
  • Database (240-247)
internal/database/tenant.go (3)
  • Tenant (9-12)
  • DefaultTenantId (16-16)
  • DefaultTenantName (18-18)
pkg/boltz/currency.go (1)
  • CurrencyBtc (11-11)
internal/database/tenant.go (2)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • Tenant (557-564)
  • Tenant (579-579)
  • Tenant (594-596)
internal/database/database.go (1)
  • Database (240-247)
pkg/boltzrpc/client/client.go (1)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
🪛 markdownlint-cli2 (0.18.1)
docs/grpc.md

341-341: Link fragments should be valid

(MD051, link-fragments)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (5)
internal/macaroons/permissions.go (1)

188-203: RemoveTenant RPC permission wiring looks correct

The new /boltzrpc.Boltz/RemoveTenant entry reuses the admin write permission like other tenant-admin operations (CreateTenant, BakeMacaroon), which is consistent with the macaroon model in this codebase. No changes needed here.

pkg/boltzrpc/client/client.go (1)

194-200: Client RemoveTenant wrapper is consistent with existing API surface

The RemoveTenant method is a thin, idiomatic wrapper around the new RPC and matches the style of other client methods (CreateTenant, GetTenant, etc.). The doc comment correctly describes the high‑level semantics enforced server‑side.

cmd/boltzcli/commands.go (3)

1555-1565: LGTM! Clean helper implementation.

The helper function correctly handles both the "no tenant" case (empty string → nil) and tenant resolution with clear error messaging. The pattern is consistent with the existing getWalletId helper.


1569-1623: LGTM! Tenant flag integration is clean and consistent.

The tenant flag is correctly integrated into both wallet create and import commands with:

  • Consistent flag definitions and usage descriptions
  • Proper tenant resolution before wallet operations
  • Appropriate error handling

Also fixes the typo on Line 1604: "BTC ot LBTC" → "BTC or LBTC".


2333-2362: LGTM! Addresses previous review feedback.

The tenant remove command now includes:

  • User confirmation prompt (addressing past review comment)
  • --force flag for automation scenarios
  • Clear success messaging
  • Proper error propagation from server

The implementation follows the same pattern as removeWallet and correctly delegates wallet association validation to the server.

Comment thread docs/grpc.md Outdated
Comment thread internal/database/tenant.go
@kilrau kilrau force-pushed the feat/add-tenant-remove branch from fa71438 to ba61e0e Compare November 22, 2025 18:35

@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 (3)
pkg/boltzrpc/boltzrpc.proto (1)

779-786: Server currently ignores WalletParams.tenant_id for wallet creation/import

The new tenant_id field is documented as “Only relevant for admin users. Defaults to the current tenant.”, but in internal/rpcserver/router.go the ImportWallet/CreateWallet path still always uses requireTenantId(ctx) when setting WalletCredentials.WalletInfo.TenantId, ignoring request.Params.TenantId. This makes admin‑side tenant_id ineffective and will cause the AdminCreateForTenant test to fail, as well as not meeting the PR objective of targeting a specific tenant.

You likely want logic in the RPC handler along the lines of:

-    credentials := &onchain.WalletCredentials{
-        WalletInfo: onchain.WalletInfo{
-            Name:     request.Params.Name,
-            Currency: currency,
-            TenantId: requireTenantId(ctx),
-        },
-        ...
-    }
+    // Default to the current tenant (macaroon context), but allow admins to override via params.tenant_id.
+    tenantId := requireTenantId(ctx)
+    if isAdmin(ctx) && request.Params.TenantId != nil {
+        tenantId = database.Id(request.Params.GetTenantId())
+    }
+
+    credentials := &onchain.WalletCredentials{
+        WalletInfo: onchain.WalletInfo{
+            Name:     request.Params.Name,
+            Currency: currency,
+            TenantId: tenantId,
+        },
+        ...
+    }

and the same behavior for CreateWallet, so that non‑admin tenants cannot escape their tenant, while admins can explicitly create wallets for another tenant.

internal/rpcserver/router.go (2)

1468-1495: Wallet creation/import ignores WalletParams.TenantId, breaking admin “create for tenant” semantics

In ImportWallet, you always set credentials.WalletInfo.TenantId = requireTenantId(ctx), and CreateWallet just forwards to ImportWallet. As a result:

  • Admins cannot choose a tenant via params.tenant_id; wallets always end up under the current (or default) tenant.
  • The AdminCreateForTenant test in internal/rpcserver/rpcserver_test.go (lines 527‑561) will fail, because it expects CreateWallet with a TenantId set in params to create a wallet owned by that tenant.

You likely want behavior along these lines:

 func (server *routedBoltzServer) ImportWallet(ctx context.Context, request *boltzrpc.ImportWalletRequest) (*boltzrpc.Wallet, error) {
@@
-   currency := serializers.ParseCurrency(&request.Params.Currency)
+   currency := serializers.ParseCurrency(&request.Params.Currency)
+
+   // Determine target tenant:
+   // - Non‑admin callers are restricted to their own tenant (macaroon/context).
+   // - Admin callers can override via params.tenant_id.
+   tenantId := requireTenantId(ctx)
+   if isAdmin(ctx) && request.Params.TenantId != nil {
+       tenantId = database.Id(request.Params.GetTenantId())
+   }
@@
    credentials := &onchain.WalletCredentials{
        WalletInfo: onchain.WalletInfo{
            Name:     request.Params.Name,
            Currency: currency,
-           TenantId: requireTenantId(ctx),
+           TenantId: tenantId,
        },

CreateWallet can stay as a thin wrapper around ImportWallet. This preserves tenant isolation for normal tenants while giving admins the explicit override hook promised by the proto comment and tests.


3-6: Update router.go to use *emptypb.Empty consistently for all RPC methods

Verification confirms the type mismatch: the generated BoltzServer interface expects *emptypb.Empty for Stop, Unlock, ChangeWalletPassword, GetPairs, and RemoveTenant. However, router.go currently uses *empty.Empty for Stop, Unlock, ChangeWalletPassword, and GetPairs—only RemoveTenant correctly uses *emptypb.Empty.

Update the following methods to use *emptypb.Empty and remove the deprecated github.com/golang/protobuf/ptypes/empty import:

  • Stop (line 1920): parameter and return type
  • Unlock (line 1966): return type
  • ChangeWalletPassword (line 2094): return type
  • GetPairs (line 2202): parameter type
🧹 Nitpick comments (5)
internal/database/tenant.go (3)

26-35: Consider propagating unexpected errors from GetTenant in CreateDefaultTenant

CreateDefaultTenant ignores the error from GetTenant(DefaultTenantId) and only checks whether the returned tenant is nil. That means any DB error (e.g. connection issue) is treated as “no default tenant”, and you then proceed to create one, potentially masking a real problem.

You may want to distinguish the “no rows” case from others, e.g.:

- defaultTenant, _ := d.GetTenant(DefaultTenantId)
- if defaultTenant == nil {
+ defaultTenant, err := d.GetTenant(DefaultTenantId)
+ if errors.Is(err, sql.ErrNoRows) {
+     defaultTenant = nil
+ } else if err != nil {
+     return err
+ }
+ if defaultTenant == nil {
     if err := d.CreateTenant(&Tenant{Id: DefaultTenantId, Name: DefaultTenantName}); err != nil {
         return err
     }
 }

so unexpected DB failures surface instead of being silently retried as a create.


93-105: Handle potential error from RowsAffected in DeleteTenant

DeleteTenant checks RowsAffected() but discards its error:

if rows, _ := result.RowsAffected(); rows == 0 {
    return fmt.Errorf("failed to delete tenant with id %d", id)
}

In normal cases this is fine, but ignoring the error can hide driver issues or statement types that don’t support RowsAffected.

A slightly safer pattern is:

- if rows, _ := result.RowsAffected(); rows == 0 {
-     return fmt.Errorf("failed to delete tenant with id %d", id)
- }
+ rows, err := result.RowsAffected()
+ if err != nil {
+     return fmt.Errorf("failed to determine affected rows for tenant %d: %w", id, err)
+ }
+ if rows == 0 {
+     return fmt.Errorf("failed to delete tenant with id %d", id)
+ }

This keeps your current behavior while surfacing unexpected driver errors.


107-121: HasTenantWallets behavior and race window are acceptable but rely on DB constraints

The HasTenantWallets implementation (RLock, simple SELECT 1 ... LIMIT 1, treating sql.ErrNoRows as “no wallets”) is correct and fixes the previous brittle err.Error() == "sql: no rows in result set" pattern.

There is still a small race window between the HasTenantWallets check and DeleteTenant in the RemoveTenant RPC; if you don’t already have a foreign key constraint wallets.tenantId REFERENCES tenants(id) ON DELETE RESTRICT, a concurrent wallet creation could slip in and leave orphaned wallets. If you don’t already, consider enforcing this at the schema level so the DB will reject deletions that would violate the “no wallets” guarantee, even under concurrency.

docs/grpc.md (1)

335-341: RemoveTenant RPC documentation is consistent with the API and CLI

The new RemoveTenant method and RemoveTenantRequest message are documented with:

  • A clear precondition: only allowed when the tenant has no associated wallets.
  • A name field on RemoveTenantRequest, consistent with GetTenantRequest.

This aligns with the new tenant remove CLI behavior and the intended safety check on the server side.

If the RPC is admin-only (admin macaroon required), consider explicitly noting that in the RemoveTenant description for extra clarity.

Also applies to: 1296-1304

cmd/boltzcli/commands.go (1)

1555-1565: Avoid masking non‑NotFound errors in getTenantIdFromName

Right now any error from client.GetTenant is turned into “tenant '' does not exist”, which hides permission, connectivity, or server errors and can mislead users.

Consider only mapping real “not found” errors to that message and otherwise returning the original error:

 func getTenantIdFromName(ctx *cli.Context, tenantName string) (*uint64, error) {
 	if tenantName == "" {
 		return nil, nil
 	}
 	client := getClient(ctx)
 	tenant, err := client.GetTenant(tenantName)
 	if err != nil {
-		return nil, fmt.Errorf("tenant '%s' does not exist", tenantName)
+		st, ok := status.FromError(err)
+		if ok && st.Code() == codes.NotFound {
+			return nil, fmt.Errorf("tenant '%s' does not exist", tenantName)
+		}
+		return nil, err
 	}
 	return &tenant.Id, nil
 }

You’d also need to import codes alongside the existing status import:

import (
    // ...
    "google.golang.org/grpc/codes"
)

This keeps the nicer “does not exist” message for actual 404s without obscuring other failures.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa71438 and ba61e0e.

⛔ Files ignored due to path filters (4)
  • pkg/boltzrpc/autoswaprpc/autoswaprpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc.pb.go is excluded by !**/*.pb.go
  • pkg/boltzrpc/boltzrpc_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (9)
  • cmd/boltzcli/commands.go (3 hunks)
  • docs/grpc.md (7 hunks)
  • internal/database/tenant.go (7 hunks)
  • internal/database/tenant_test.go (1 hunks)
  • internal/macaroons/permissions.go (1 hunks)
  • internal/rpcserver/router.go (2 hunks)
  • internal/rpcserver/rpcserver_test.go (1 hunks)
  • pkg/boltzrpc/boltzrpc.proto (3 hunks)
  • pkg/boltzrpc/client/client.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/database/tenant_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-16T09:46:41.613Z
Learnt from: jackstar12
Repo: BoltzExchange/boltz-client PR: 487
File: internal/rpcserver/router.go:2304-2314
Timestamp: 2025-07-16T09:46:41.613Z
Learning: In the boltz-client codebase, RPC methods are protected by the macaroon permission system defined in internal/macaroons/permissions.go. Methods like GetSwapMnemonic and SetSwapMnemonic are already restricted to admin entities through the RPCServerPermissions map, so explicit isAdmin(ctx) checks are not needed in these methods.

Applied to files:

  • internal/macaroons/permissions.go
  • internal/rpcserver/rpcserver_test.go
🧬 Code graph analysis (4)
internal/rpcserver/router.go (2)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/database/tenant.go (1)
  • DefaultTenantId (18-18)
pkg/boltzrpc/client/client.go (1)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • RemoveTenantRequest (510-516)
  • RemoveTenantRequest (531-531)
  • RemoveTenantRequest (546-548)
internal/rpcserver/rpcserver_test.go (3)
pkg/boltzrpc/boltzrpc.pb.go (16)
  • WalletParams (4492-4503)
  • WalletParams (4518-4518)
  • WalletParams (4533-4535)
  • Currency (132-132)
  • Currency (161-163)
  • Currency (165-167)
  • Currency (174-176)
  • Wallet (5899-5910)
  • Wallet (5925-5925)
  • Wallet (5940-5942)
  • BakeMacaroonRequest (659-666)
  • BakeMacaroonRequest (681-681)
  • BakeMacaroonRequest (696-698)
  • Wallets (5986-5992)
  • Wallets (6007-6007)
  • Wallets (6022-6024)
pkg/boltzrpc/client/client.go (2)
  • FullPermissions (20-23)
  • NewBoltzClient (29-34)
pkg/boltzrpc/client/connection.go (1)
  • Connection (17-29)
internal/database/tenant.go (2)
pkg/boltzrpc/boltzrpc.pb.go (3)
  • Tenant (557-564)
  • Tenant (579-579)
  • Tenant (594-596)
internal/database/database.go (1)
  • Database (240-247)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (11)
pkg/boltzrpc/boltzrpc.proto (1)

193-201: RemoveTenant RPC/message shape looks good

The RemoveTenant RPC and RemoveTenantRequest { string name = 1; } are minimal and consistent with the server implementation that removes tenants by name with safety checks. No issues from the proto side.

Also applies to: 234-237

internal/macaroons/permissions.go (1)

200-203: RemoveTenant correctly restricted to admin/write via macaroons

The /boltzrpc.Boltz/RemoveTenant entry using {Entity: "admin", Action: "write"} aligns with the authorization model and avoids needing extra isAdmin checks in the handler.

pkg/boltzrpc/client/client.go (1)

194-197: Client RemoveTenant wrapper is consistent and sufficient

The new RemoveTenant(name string) error helper correctly forwards the name into RemoveTenantRequest and follows the same pattern as other client methods.

internal/rpcserver/router.go (1)

2345-2377: RemoveTenant handler enforces the right safety checks

The RemoveTenant RPC implementation correctly:

  • looks up the tenant by name and returns codes.NotFound when missing,
  • prevents deletion of the default tenant by ID,
  • runs a transaction that checks HasTenantWallets and returns codes.FailedPrecondition when wallets exist,
  • and deletes the tenant when safe, returning an empty response.

This matches the intended semantics from the issue and the tests.

internal/rpcserver/rpcserver_test.go (1)

564-613: RemoveTenant tests comprehensively cover edge cases and permissions

The RemoveTenant test block nicely exercises:

  • non-existent name → codes.NotFound,
  • attempt to remove default tenant → codes.InvalidArgument,
  • tenants with wallets (both the main tenantName and "wallet-tenant") → codes.FailedPrecondition,
  • empty tenant lifecycle (create, remove, then GetTenantcodes.NotFound),
  • and permission enforcement (tenant macaroon cannot remove, admin can).

These expectations align with the RemoveTenant handler’s logic and the macaroon permissions map.

docs/grpc.md (3)

85-85: Inline .google.protobuf.Empty type formatting looks good and fixes the anchor issue

Switching from fragment links to inline code for .google.protobuf.Empty in these method tables keeps the docs clear and should resolve the previous markdownlint MD051 complaint about invalid #.google.protobuf.empty anchors. No further change needed here.

Also applies to: 285-285, 293-293, 309-309, 341-341, 1888-1888


329-329: GetTenant description typo fix is correct

The description now reads “Get a specific tenant.” which matches the RPC behavior and removes the previous typo.


1643-1643: WalletParams.tenant_id docs match new wallet tenant association behavior

Documenting tenant_id as an optional admin-only field that defaults to the current tenant mirrors the new wallet create/import --tenant CLI wiring and clarifies how wallets are scoped.

cmd/boltzcli/commands.go (3)

1568-1571: Wallet command metadata is clearer

Changing the wallet command Category to "Wallet" and Usage to "Manage wallets" is concise and consistent with the other top‑level command categories.


1579-1596: Wallet create/import correctly support --tenant

Both wallet create and wallet import now:

  • Resolve the --tenant flag via getTenantIdFromName, failing early when the tenant name is invalid.
  • Wire the resulting *uint64 into WalletParams.TenantId before calling createWallet/importWallet.
  • Document the flag as “Tenant name to associate the wallet with (admin only)”.
  • Fix the “BTC ot LBTC” typo in the import description.

This cleanly exposes tenant scoping at the CLI level and matches the new WalletParams.tenant_id field on the RPC side.

Also applies to: 1604-1623


2333-2362: Tenant remove command matches destructive‑operation UX expectations

The new tenant remove subcommand:

  • Requires a tenant name via requireNArgs(1, ...).
  • Prompts for confirmation by default, clearly including the tenant name in the message.
  • Honors a --force / -f flag to skip the prompt when desired.
  • Delegates to client.RemoveTenant(tenantName) and reports success.

This is consistent with the existing wallet remove flow while adding a standard force override for scripted use.

Comment thread internal/rpcserver/rpcserver_test.go Outdated

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba61e0e and 1163ca6.

📒 Files selected for processing (1)
  • internal/rpcserver/router.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/rpcserver/router.go (3)
internal/database/database.go (1)
  • Id (336-336)
internal/onchain/wallet.go (1)
  • WalletInfo (34-40)
internal/database/tenant.go (1)
  • DefaultTenantId (18-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (2)
internal/rpcserver/router.go (2)

44-44: LGTM: Import added for empty protobuf response.

The emptypb import is correctly added to support the RemoveTenant method's return type.


1476-1495: LGTM: Admin tenant resolution logic is correct.

The implementation correctly allows admin users to associate a wallet with any tenant while falling back to the context tenant for non-admin users. The tenant existence validation and error handling are appropriate.

Comment thread internal/rpcserver/router.go
Comment thread pkg/boltzrpc/boltzrpc.proto Outdated
@kilrau kilrau force-pushed the feat/add-tenant-remove branch from 1163ca6 to 6c016cc Compare November 24, 2025 16:37
@kilrau kilrau requested a review from jackstar12 November 24, 2025 17:19
@kilrau

kilrau commented Nov 24, 2025

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

return result, nil
}

// parseTenant parses a database row into a Tenant struct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For future reference, tell claude to skip adding these useless comments

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did, several times. Removed manually as well but must have re-added in some commit and I missed it.

@michael1011 michael1011 merged commit 5817724 into master Nov 24, 2025
3 checks passed
@michael1011 michael1011 deleted the feat/add-tenant-remove branch November 24, 2025 19:43
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.

boltzcli: finish up entity commands

3 participants