๐ src/web3/subscription/contract.ts
D-OPEN SOVEREIGN
Documentation & Insights
Batched read of getSubscriptions(userId) for many userIds in one RPC call.
Falls back to sequential readContract calls if multicall3 isn't reachable
on this chain or the batch call otherwise fails.Batched read of getSubscriptionCount(userId) for many userIds โ the cheap
per-node change signal driving incremental sync (spec ยง8 / ยง15.5).1import type { Address } from 'viem';
2import type { EvmClient } from '../client/EvmClient';
3import { addressLoader } from '../registry';
4
5// Canonical Multicall3 deployment address โ deployed deterministically (via a
6// keyless transaction) on effectively every EVM chain, including Core
7// mainnet/testnet. Passed explicitly since EvmClient.chain (defineChain(...))
8// doesn't declare a `contracts.multicall3` entry the way viem/chains does.
9const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as Address;
10
11interface ContractBinding {
12 address: Address;
13 abi: any;
14}
15
16function subscriptionManagerBinding(client: EvmClient): ContractBinding | null {
17 const address = addressLoader.getAddress(client.chainId, 'subscriptionManager', client.environment) as
18 | Address
19 | undefined;
20 const abi = addressLoader.getAbi(client.chainId, 'SubscriptionManager', client.environment);
21 if (!address || !abi) return null;
22 return { address, abi };
23}
24
25/**
26 * Batched read of getSubscriptions(userId) for many userIds in one RPC call.
27 * Falls back to sequential readContract calls if multicall3 isn't reachable
28 * on this chain or the batch call otherwise fails.
29 */
30export async function multicallGetSubscriptions(client: EvmClient, userIds: number[]): Promise<Map<number, number[]>> {
31 const result = new Map<number, number[]>();
32 if (userIds.length === 0) return result;
33
34 const binding = subscriptionManagerBinding(client);
35 if (binding) {
36 try {
37 const raw = await client.publicClient.multicall({
38 multicallAddress: MULTICALL3_ADDRESS,
39 allowFailure: true,
40 contracts: userIds.map((userId) => ({ ...binding, functionName: 'getSubscriptions', args: [userId] })),
41 });
42 userIds.forEach((userId, i) => {
43 const r = raw[i];
44 result.set(userId, r.status === 'success' ? ((r.result as readonly number[]) ?? []).map(Number) : []);
45 });
46 return result;
47 } catch (e) {
48 console.warn('[subscription/contract] multicall getSubscriptions failed, falling back to sequential reads', e);
49 }
50 }
51
52 await Promise.all(
53 userIds.map(async (userId) => {
54 try {
55 result.set(userId, await client.subscriptionManager.getSubscriptions(userId));
56 } catch {
57 result.set(userId, []);
58 }
59 })
60 );
61 return result;
62}
63
64/**
65 * Batched read of getSubscriptionCount(userId) for many userIds โ the cheap
66 * per-node change signal driving incremental sync (spec ยง8 / ยง15.5).
67 */
68export async function multicallGetSubscriptionCounts(client: EvmClient, userIds: number[]): Promise<Map<number, number>> {
69 const result = new Map<number, number>();
70 if (userIds.length === 0) return result;
71
72 const binding = subscriptionManagerBinding(client);
73 if (binding) {
74 try {
75 const raw = await client.publicClient.multicall({
76 multicallAddress: MULTICALL3_ADDRESS,
77 allowFailure: true,
78 contracts: userIds.map((userId) => ({ ...binding, functionName: 'getSubscriptionCount', args: [userId] })),
79 });
80 userIds.forEach((userId, i) => {
81 const r = raw[i];
82 result.set(userId, r.status === 'success' ? Number(r.result) : 0);
83 });
84 return result;
85 } catch (e) {
86 console.warn('[subscription/contract] multicall getSubscriptionCount failed, falling back to sequential reads', e);
87 }
88 }
89
90 await Promise.all(
91 userIds.map(async (userId) => {
92 try {
93 result.set(userId, Number(await client.subscriptionManager.getSubscriptionCount(userId)));
94 } catch {
95 result.set(userId, 0);
96 }
97 })
98 );
99 return result;
100}
101