[DRAFT][PLAN] Reset buyer operator and guard credit deposits
Parent: #22
This plan keeps the parent issue unchanged and maps the implementation to the current repository state.
Relevant code and existing behavior
backend/src/worker.ts
- Routes
POST /v1/accounts/:buyer/operator-consent through OperatorConsentSchema to AntSeedFundingVaultClient.acceptBuyerOperator.
- All event, manual stream-credit, and scheduled stream-credit paths converge on
fundCredit, so the operator check belongs in that shared path or in the client method it calls.
- Existing routes use zod validation plus
logInfo, logWarn, logError, redactAddress, and the global error handler.
backend/src/antseed-funding-vault.ts
FUNDING_VAULT_ABI currently exposes accept/deposit methods but omits the already-implemented transferBuyerOperator operation and operator-status read.
depositForBuyerWithId performs the duplicate-ID check before submitting depositForWithId.
contracts/src/AntseedBuyerOperator.sol
transferBuyerOperator(address buyer, address newOperator) is onlyOwner and forwards to the deposits contract.
_requireDepositsOperator already verifies IAntseedDeposits.getOperator(buyer) == address(this) for on-chain deposits.
contracts/src/interfaces/IAntseedDeposits.sol
- Already declares
transferOperator and getOperator.
contracts/test/AntseedBuyerOperator.t.sol
- Already covers successful transfer and rejects an outsider, but not specifically transfer to
address(0).
backend/test/worker.test.ts
- Contains route-level tests for operator consent and the disabled bridge; extend the same test style for reset validation/disabled behavior and guarded funding.
No new shared package or UI component is needed in this backend repository. The companion GoodWidget issue can consume the new endpoint through its existing backend client/runtime abstractions.
Required states, flows, and behaviors
- When the bridge is configured and the service wrapper is the buyer's current operator, a valid reset request transfers the operator to
address(0) and returns the confirmed transaction hash.
- When the bridge is disabled, reset follows the existing client convention and returns
enabled: false without attempting a transaction.
- Before a credit deposit is submitted, the backend reads the buyer's current operator. A match with the configured wrapper address permits the existing deposit flow.
- A missing or different operator prevents
depositForWithId from being submitted. The result is logged with redacted addresses and surfaced through the existing failed-funding flow rather than silently marked funded.
- Duplicate-ID behavior remains intact. The operator check should occur before any irreversible local success state; the contract remains the final authority.
- Existing consent, event ingestion, manual stream-credit, and scheduled credit paths keep their current behavior when operator status is valid.
Execution plan
- Extend
FUNDING_VAULT_ABI and AntSeedFundingVaultClient with:
transferBuyerOperator(address,address) for the existing wrapper call.
- An operator-status read. Prefer adding a small
getBuyerOperator(address) view to AntseedBuyerOperator that delegates to IAntseedDeposits.getOperator, because the backend is configured with the wrapper address rather than the registry/deposits address. Add the matching ABI entry and client method.
- A client accessor for the wrapper address used in equality checks, normalized with
ethers.getAddress or consistently lowercased.
- Add a buyer-address zod schema and a
POST /v1/accounts/:buyer/operator-reset route in worker.ts following the consent route's validation, response, and logging conventions. Call transferBuyerOperator(buyer, ethers.ZeroAddress) through the client. No buyer signature is required by the current wrapper contract because the configured owner/admin initiates the transfer; call this out in endpoint documentation.
- Add the current-operator check at the shared funding boundary before
depositForBuyerWithId. On mismatch, throw or return a typed failure that fundCredit records via KVCreditStore.markFundingResult, with a specific non-secret error such as buyer operator is not configured for funding.
- Add Solidity tests for
getBuyerOperator, reset to address(0), and deposit rejection after reset. Preserve the existing only-owner/only-admin authorization coverage.
- Add backend unit tests using a mocked JSON-RPC/provider boundary for:
- invalid buyer address -> 400;
- disabled bridge ->
enabled: false;
- configured reset submits zero-address transfer and returns a transaction hash;
- matching operator permits funding;
- zero/different operator prevents
depositForWithId and records/logs a failed funding result;
- existing operator-consent tests remain green.
- Update backend/API documentation with the endpoint request/response, authorization assumption, and the fact that all credit sources are protected by the shared pre-deposit check.
- Run
forge test under contracts, then npm run typecheck, npm test, and npm run build under backend.
Acceptance criteria
Human-reviewer checklist
[DRAFT][PLAN] Reset buyer operator and guard credit deposits
Parent: #22
This plan keeps the parent issue unchanged and maps the implementation to the current repository state.
Relevant code and existing behavior
backend/src/worker.tsPOST /v1/accounts/:buyer/operator-consentthroughOperatorConsentSchematoAntSeedFundingVaultClient.acceptBuyerOperator.fundCredit, so the operator check belongs in that shared path or in the client method it calls.logInfo,logWarn,logError,redactAddress, and the global error handler.backend/src/antseed-funding-vault.tsFUNDING_VAULT_ABIcurrently exposes accept/deposit methods but omits the already-implementedtransferBuyerOperatoroperation and operator-status read.depositForBuyerWithIdperforms the duplicate-ID check before submittingdepositForWithId.contracts/src/AntseedBuyerOperator.soltransferBuyerOperator(address buyer, address newOperator)isonlyOwnerand forwards to the deposits contract._requireDepositsOperatoralready verifiesIAntseedDeposits.getOperator(buyer) == address(this)for on-chain deposits.contracts/src/interfaces/IAntseedDeposits.soltransferOperatorandgetOperator.contracts/test/AntseedBuyerOperator.t.soladdress(0).backend/test/worker.test.tsNo new shared package or UI component is needed in this backend repository. The companion GoodWidget issue can consume the new endpoint through its existing backend client/runtime abstractions.
Required states, flows, and behaviors
address(0)and returns the confirmed transaction hash.enabled: falsewithout attempting a transaction.depositForWithIdfrom being submitted. The result is logged with redacted addresses and surfaced through the existing failed-funding flow rather than silently marked funded.Execution plan
FUNDING_VAULT_ABIandAntSeedFundingVaultClientwith:transferBuyerOperator(address,address)for the existing wrapper call.getBuyerOperator(address)view toAntseedBuyerOperatorthat delegates toIAntseedDeposits.getOperator, because the backend is configured with the wrapper address rather than the registry/deposits address. Add the matching ABI entry and client method.ethers.getAddressor consistently lowercased.POST /v1/accounts/:buyer/operator-resetroute inworker.tsfollowing the consent route's validation, response, and logging conventions. CalltransferBuyerOperator(buyer, ethers.ZeroAddress)through the client. No buyer signature is required by the current wrapper contract because the configured owner/admin initiates the transfer; call this out in endpoint documentation.depositForBuyerWithId. On mismatch, throw or return a typed failure thatfundCreditrecords viaKVCreditStore.markFundingResult, with a specific non-secret error such asbuyer operator is not configured for funding.getBuyerOperator, reset toaddress(0), and deposit rejection after reset. Preserve the existing only-owner/only-admin authorization coverage.enabled: false;depositForWithIdand records/logs a failed funding result;forge testundercontracts, thennpm run typecheck,npm test, andnpm run buildunderbackend.Acceptance criteria
POST /v1/accounts/:buyer/operator-resetvalidates the buyer address and transfers the configured buyer operator toaddress(0)on-chain.IAntseedDeposits.getOperator.fundCreditcall path checks that the configured wrapper is still the buyer's operator before callingdepositForWithId.Human-reviewer checklist
transferBuyerOperator(buyer, address(0))is the intended authorization model; no new buyer signature is necessary under the current contract.failed(retryable after renewed consent) or receive a distinct skipped status in a follow-up schema change.