Description
Stellar accounts have a hard protocol limit on the number of subentries (trustlines, offers, data entries, and signers) they can hold, gated by the account's minimum balance reserve. The SDK does not currently warn callers when an account is approaching this limit, leading to silent op_low_reserve failures when invoice splits attempt to add new trustlines or data entries to a near-full account. A subentry capacity guard would pre-check available slots before any operation that increases subentry count.
Technical Context
New module src/account/subentryGuard.ts exposing checkSubentryCapacity(accountId: string, requiredSlots: number): Promise<SubentryCapacityResult>. Loads account data via Horizon.Server.loadAccount() from @stellar/stellar-sdk. Reserve calculation follows the protocol formula: (2 + numSubentries + numSponsoring − numSponsored) × baseReserve. SubentryCapacityResult and SubentryCapacityError interfaces in src/types.ts. Integrated as a pre-flight step in src/payments/splitExecutor.ts.
Acceptance Criteria
Description
Stellar accounts have a hard protocol limit on the number of subentries (trustlines, offers, data entries, and signers) they can hold, gated by the account's minimum balance reserve. The SDK does not currently warn callers when an account is approaching this limit, leading to silent
op_low_reservefailures when invoice splits attempt to add new trustlines or data entries to a near-full account. A subentry capacity guard would pre-check available slots before any operation that increases subentry count.Technical Context
New module
src/account/subentryGuard.tsexposingcheckSubentryCapacity(accountId: string, requiredSlots: number): Promise<SubentryCapacityResult>. Loads account data viaHorizon.Server.loadAccount()from@stellar/stellar-sdk. Reserve calculation follows the protocol formula:(2 + numSubentries + numSponsoring − numSponsored) × baseReserve.SubentryCapacityResultandSubentryCapacityErrorinterfaces insrc/types.ts. Integrated as a pre-flight step insrc/payments/splitExecutor.ts.Acceptance Criteria
checkSubentryCapacityreturns{ available: number; used: number; limit: number; canAccommodate: boolean }derived from live Horizon account datanum_sponsoringandnum_sponsoredHorizon response fieldscanAccommodateisfalse,splitExecutorthrows aSubentryCapacityErrorthat names the specific account ID and the number of additional reserve XLM needed{ skipCapacityCheck: true }tosplitExecutor, which bypasses the guard entirely without altering other pre-flight checksHorizon.Server.loadAccountand cover a well-funded account, an account exactly at capacity, a sponsored-subentry account, and the skip-flag bypass path