@@ -863,47 +863,27 @@ async def get_or_create_charge_manager() -> str:
863863
864864 logger .info ("Looking for existing ChargeManager contract..." )
865865
866- # Query for existing ChargeManager
867- template_id = f"{ BILLING_PACKAGE_ID } :MCP.Billing:ChargeManager"
868-
869866 try :
870- # Get current ledger offset
871- offset = await get_ledger_offset ()
872-
873- data = await _make_ledger_request (
874- "POST" ,
875- "/v2/state/active-contracts" ,
876- {
877- "filter" : {
878- "filtersByParty" : {
879- CANTON_PROVIDER_PARTY : {
880- "filters" : [{
881- "templateFilter" : {
882- "templateId" : template_id
883- }
884- }]
885- }
886- }
887- },
888- "activeAtOffset" : offset
889- }
867+ # Query for existing ChargeManager via the paginated /v2/updates helper.
868+ # ChargeManager was intended to be a singleton, but historical deploys
869+ # with cold in-memory caches created multiple copies over time — on
870+ # mainnet the provider party now has >200 of them, which crossed the
871+ # /v2/state/active-contracts 200-element hard cap and surfaced as
872+ # "413 JSON_API_MAXIMUM_LIST_ELEMENTS_NUMBER_REACHED" inside
873+ # /billing/credit top-up failures. The paginated helper scans via
874+ # /v2/updates (no cap) and short-circuits on the first active match.
875+ def _extract_charge_manager (contract_id : str , _payload : dict , _created_at : str ) -> Optional [str ]:
876+ return contract_id or None
877+
878+ found = await _query_active_contracts_via_updates (
879+ template_suffix = ":MCP.Billing:ChargeManager" ,
880+ party = CANTON_PROVIDER_PARTY ,
881+ extract = _extract_charge_manager ,
882+ stop_when = lambda _cid : True , # return as soon as any active one is seen
890883 )
891884
892- # Handle different response formats (list or dict with activeContracts)
893- contracts = data if isinstance (data , list ) else data .get ("activeContracts" , [])
894-
895- if contracts :
896- # Handle different response formats
897- first = contracts [0 ]
898- contract_id = (
899- first .get ("contractId" ) or
900- first .get ("createdEvent" , {}).get ("contractId" ) or
901- first .get ("contractEntry" , {}).get ("activeContract" , {}).get ("createdEvent" , {}).get ("contractId" ) or
902- first .get ("contractEntry" , {}).get ("JsActiveContract" , {}).get ("createdEvent" , {}).get ("contractId" )
903- )
904- if not contract_id :
905- logger .error (f"Cannot find contractId in response: { first } " )
906- raise LedgerError ("No contractId found in ChargeManager query response" )
885+ if found :
886+ contract_id = found [0 ]
907887 logger .info (f"Found existing ChargeManager: { contract_id } " )
908888 _charge_manager_cache = {
909889 "contract_id" : contract_id ,
@@ -913,6 +893,7 @@ async def get_or_create_charge_manager() -> str:
913893
914894 # No ChargeManager exists, create one using submit-and-wait-for-transaction
915895 logger .info ("Creating new ChargeManager contract..." )
896+ template_id = f"{ BILLING_PACKAGE_ID } :MCP.Billing:ChargeManager"
916897
917898 create_data = await _make_ledger_request (
918899 "POST" ,
0 commit comments