@@ -88,6 +88,12 @@ impl EscrowAccounts {
8888
8989pub type EscrowAccountsWatcher = Receiver < EscrowAccounts > ;
9090
91+ pub fn empty_escrow_accounts_watcher ( ) -> EscrowAccountsWatcher {
92+ let ( _, receiver) =
93+ tokio:: sync:: watch:: channel ( EscrowAccounts :: new ( HashMap :: new ( ) , HashMap :: new ( ) ) ) ;
94+ receiver
95+ }
96+
9197pub async fn escrow_accounts_v1 (
9298 escrow_subgraph : & ' static SubgraphClient ,
9399 indexer_address : Address ,
@@ -112,13 +118,83 @@ pub async fn escrow_accounts_v2(
112118 . await
113119}
114120
115- // TODO implement escrow accounts v2 query
116121async fn get_escrow_accounts_v2 (
117- _escrow_subgraph : & ' static SubgraphClient ,
118- _indexer_address : Address ,
119- _reject_thawing_signers : bool ,
122+ escrow_subgraph : & ' static SubgraphClient ,
123+ indexer_address : Address ,
124+ reject_thawing_signers : bool ,
120125) -> anyhow:: Result < EscrowAccounts > {
121- Ok ( EscrowAccounts :: new ( HashMap :: new ( ) , HashMap :: new ( ) ) )
126+ // Query V2 escrow accounts from the network subgraph which tracks PaymentsEscrow
127+ // and GraphTallyCollector contract events.
128+
129+ use indexer_query:: network_escrow_account_v2:: {
130+ self as network_escrow_account_v2, NetworkEscrowAccountQueryV2 ,
131+ } ;
132+
133+ let response = escrow_subgraph
134+ . query :: < NetworkEscrowAccountQueryV2 , _ > ( network_escrow_account_v2:: Variables {
135+ receiver : format ! ( "{indexer_address:x?}" ) ,
136+ thaw_end_timestamp : if reject_thawing_signers {
137+ U256 :: ZERO . to_string ( )
138+ } else {
139+ U256 :: MAX . to_string ( )
140+ } ,
141+ } )
142+ . await ?;
143+
144+ let response = response?;
145+
146+ tracing:: trace!( "Network V2 Escrow accounts response: {:?}" , response) ;
147+
148+ // V2 TAP receipts use different field names (payer/service_provider) but the underlying
149+ // escrow account model is identical to V1. Both V1 and V2 receipts reference the same
150+ // sender addresses and the same escrow relationships.
151+ //
152+ // V1 queries the TAP subgraph while V2 queries the network subgraph, but both return
153+ // the same escrow account structure for processing.
154+ //
155+ // V2 receipt flow:
156+ // 1. V2 receipt contains payer address (equivalent to V1 sender)
157+ // 2. Receipt is signed by a signer authorized by the payer
158+ // 3. Escrow accounts map: signer -> payer (sender) -> balance
159+ // 4. Service provider (indexer) receives payments from payer's escrow
160+
161+ let senders_balances: HashMap < Address , U256 > = response
162+ . payments_escrow_accounts
163+ . iter ( )
164+ . map ( |account| {
165+ let balance = U256 :: checked_sub (
166+ U256 :: from_str ( & account. balance ) ?,
167+ U256 :: from_str ( & account. total_amount_thawing ) ?,
168+ )
169+ . unwrap_or_else ( || {
170+ tracing:: warn!(
171+ "Balance minus total amount thawing underflowed for V2 account {}. \
172+ Setting balance to 0, no V2 queries will be served for this payer.",
173+ account. payer. id
174+ ) ;
175+ U256 :: from ( 0 )
176+ } ) ;
177+
178+ Ok ( ( Address :: from_str ( & account. payer . id ) ?, balance) )
179+ } )
180+ . collect :: < Result < HashMap < _ , _ > , anyhow:: Error > > ( ) ?;
181+
182+ let senders_to_signers = response
183+ . payments_escrow_accounts
184+ . into_iter ( )
185+ . map ( |account| {
186+ let payer = Address :: from_str ( & account. payer . id ) ?;
187+ let signers = account
188+ . payer
189+ . signers
190+ . iter ( )
191+ . map ( |signer| Address :: from_str ( & signer. id ) )
192+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
193+ Ok ( ( payer, signers) )
194+ } )
195+ . collect :: < Result < HashMap < _ , _ > , anyhow:: Error > > ( ) ?;
196+
197+ Ok ( EscrowAccounts :: new ( senders_balances, senders_to_signers) )
122198}
123199
124200async fn get_escrow_accounts_v1 (
@@ -262,4 +338,54 @@ mod tests {
262338 )
263339 ) ;
264340 }
341+
342+ #[ test( tokio:: test) ]
343+ async fn test_current_accounts_v2 ( ) {
344+ // Set up a mock escrow subgraph for V2 with payer fields
345+ let mock_server = MockServer :: start ( ) . await ;
346+ let escrow_subgraph = Box :: leak ( Box :: new (
347+ SubgraphClient :: new (
348+ reqwest:: Client :: new ( ) ,
349+ None ,
350+ DeploymentDetails :: for_query_url ( & format ! (
351+ "{}/subgraphs/id/{}" ,
352+ & mock_server. uri( ) ,
353+ test_assets:: ESCROW_SUBGRAPH_DEPLOYMENT
354+ ) )
355+ . unwrap ( ) ,
356+ )
357+ . await ,
358+ ) ) ;
359+
360+ let mock = Mock :: given ( method ( "POST" ) )
361+ . and ( path ( format ! (
362+ "/subgraphs/id/{}" ,
363+ test_assets:: ESCROW_SUBGRAPH_DEPLOYMENT
364+ ) ) )
365+ . respond_with (
366+ ResponseTemplate :: new ( 200 )
367+ . set_body_raw ( test_assets:: ESCROW_QUERY_RESPONSE_V2 , "application/json" ) ,
368+ ) ;
369+ mock_server. register ( mock) . await ;
370+
371+ // Test V2 escrow accounts watcher
372+ let mut accounts = escrow_accounts_v2 (
373+ escrow_subgraph,
374+ test_assets:: INDEXER_ADDRESS ,
375+ Duration :: from_secs ( 60 ) ,
376+ true ,
377+ )
378+ . await
379+ . unwrap ( ) ;
380+ accounts. changed ( ) . await . unwrap ( ) ;
381+
382+ // V2 should produce identical results to V1 since they query the same data
383+ assert_eq ! (
384+ accounts. borrow( ) . clone( ) ,
385+ EscrowAccounts :: new(
386+ ESCROW_ACCOUNTS_BALANCES . to_owned( ) ,
387+ ESCROW_ACCOUNTS_SENDERS_TO_SIGNERS . to_owned( ) ,
388+ )
389+ ) ;
390+ }
265391}
0 commit comments