Skip to content

Commit c9ee25e

Browse files
authored
improve: allow SVM events client to take in non-spoke IDLs (#982)
Signed-off-by: bennett <[email protected]>
1 parent 06e400d commit c9ee25e

File tree

4 files changed

+50
-66
lines changed

4 files changed

+50
-66
lines changed

src/arch/svm/eventsClient.ts

+39-31
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Idl } from "@coral-xyz/anchor";
12
import { getDeployedAddress, SvmSpokeIdl } from "@across-protocol/contracts";
23
import { getSolanaChainId } from "@across-protocol/contracts/dist/src/svm/web3-v1";
34
import web3, {
@@ -9,7 +10,7 @@ import web3, {
910
Signature,
1011
} from "@solana/kit";
1112
import { bs58 } from "../../utils";
12-
import { EventData, EventName, EventWithData } from "./types";
13+
import { EventWithData } from "./types";
1314
import { decodeEvent, isDevnet } from "./utils";
1415
import { getSlotForBlock } from "./SpokeUtils";
1516

@@ -26,39 +27,48 @@ type GetSignaturesForAddressConfig = Parameters<GetSignaturesForAddressApi["getS
2627
type GetSignaturesForAddressTransaction = ReturnType<GetSignaturesForAddressApi["getSignaturesForAddress"]>[number];
2728
type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransaction[];
2829

29-
export class SvmSpokeEventsClient {
30+
export class SvmCpiEventsClient {
3031
private rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>;
31-
private svmSpokeAddress: Address;
32-
private svmSpokeEventAuthority: Address;
32+
private programAddress: Address;
33+
private programEventAuthority: Address;
34+
private idl: Idl;
3335

3436
/**
3537
* Private constructor. Use the async create() method to instantiate.
3638
*/
3739
private constructor(
3840
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
39-
svmSpokeAddress: Address,
40-
eventAuthority: Address
41+
address: Address,
42+
eventAuthority: Address,
43+
idl: Idl
4144
) {
4245
this.rpc = rpc;
43-
this.svmSpokeAddress = svmSpokeAddress;
44-
this.svmSpokeEventAuthority = eventAuthority;
46+
this.programAddress = address;
47+
this.programEventAuthority = eventAuthority;
48+
this.idl = idl;
4549
}
4650

4751
/**
4852
* Factory method to asynchronously create an instance of SvmSpokeEventsClient.
4953
*/
50-
public static async create(
51-
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>
52-
): Promise<SvmSpokeEventsClient> {
54+
public static async create(rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>): Promise<SvmCpiEventsClient> {
5355
const isTestnet = await isDevnet(rpc);
5456
const programId = getDeployedAddress("SvmSpoke", getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString());
5557
if (!programId) throw new Error("Program not found");
56-
const svmSpokeAddress = web3.address(programId);
57-
const [svmSpokeEventAuthority] = await web3.getProgramDerivedAddress({
58-
programAddress: svmSpokeAddress,
58+
return this.createFor(rpc, programId, SvmSpokeIdl);
59+
}
60+
61+
public static async createFor(
62+
rpc: web3.Rpc<web3.SolanaRpcApiFromTransport<RpcTransport>>,
63+
programId: string,
64+
idl: Idl
65+
): Promise<SvmCpiEventsClient> {
66+
const address = web3.address(programId);
67+
const [eventAuthority] = await web3.getProgramDerivedAddress({
68+
programAddress: address,
5969
seeds: ["__event_authority"],
6070
});
61-
return new SvmSpokeEventsClient(rpc, svmSpokeAddress, svmSpokeEventAuthority);
71+
return new SvmCpiEventsClient(rpc, address, eventAuthority, idl);
6272
}
6373

6474
/**
@@ -70,14 +80,14 @@ export class SvmSpokeEventsClient {
7080
* @param options - Options for fetching signatures.
7181
* @returns A promise that resolves to an array of events matching the eventName.
7282
*/
73-
public async queryEvents<T extends EventData>(
74-
eventName: EventName,
83+
public async queryEvents(
84+
eventName: string,
7585
fromBlock?: bigint,
7686
toBlock?: bigint,
7787
options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }
78-
): Promise<EventWithData<T>[]> {
88+
): Promise<EventWithData[]> {
7989
const events = await this.queryAllEvents(fromBlock, toBlock, options);
80-
return events.filter((event) => event.name === eventName) as EventWithData<T>[];
90+
return events.filter((event) => event.name === eventName) as EventWithData[];
8191
}
8292

8393
/**
@@ -92,7 +102,7 @@ export class SvmSpokeEventsClient {
92102
fromBlock?: bigint,
93103
toBlock?: bigint,
94104
options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }
95-
): Promise<EventWithData<EventData>[]> {
105+
): Promise<EventWithData[]> {
96106
const allSignatures: GetSignaturesForAddressTransaction[] = [];
97107
let hasMoreSignatures = true;
98108
let currentOptions = options;
@@ -112,7 +122,7 @@ export class SvmSpokeEventsClient {
112122

113123
while (hasMoreSignatures) {
114124
const signatures: GetSignaturesForAddressApiResponse = await this.rpc
115-
.getSignaturesForAddress(this.svmSpokeAddress, currentOptions)
125+
.getSignaturesForAddress(this.programAddress, currentOptions)
116126
.send();
117127
// Signatures are sorted by slot in descending order.
118128
allSignatures.push(...signatures);
@@ -175,11 +185,9 @@ export class SvmSpokeEventsClient {
175185
* @param txResult - The transaction result.
176186
* @returns A promise that resolves to an array of events with their data and name.
177187
*/
178-
private processEventFromTx(
179-
txResult: GetTransactionReturnType
180-
): { program: Address; data: EventData; name: EventName }[] {
188+
private processEventFromTx(txResult: GetTransactionReturnType): { program: Address; data: unknown; name: string }[] {
181189
if (!txResult) return [];
182-
const events: { program: Address; data: EventData; name: EventName }[] = [];
190+
const events: { program: Address; data: unknown; name: string }[] = [];
183191

184192
const accountKeys = txResult.transaction.message.accountKeys;
185193
const messageAccountKeys = [...accountKeys];
@@ -195,22 +203,22 @@ export class SvmSpokeEventsClient {
195203
if (
196204
ixProgramId !== undefined &&
197205
singleIxAccount !== undefined &&
198-
this.svmSpokeAddress === ixProgramId &&
199-
this.svmSpokeEventAuthority === singleIxAccount
206+
this.programAddress === ixProgramId &&
207+
this.programEventAuthority === singleIxAccount
200208
) {
201209
const ixData = bs58.decode(ix.data);
202210
// Skip the first 8 bytes (assumed header) and encode the rest.
203211
const eventData = Buffer.from(ixData.slice(8)).toString("base64");
204-
const { name, data } = decodeEvent(SvmSpokeIdl, eventData);
205-
events.push({ program: this.svmSpokeAddress, name, data });
212+
const { name, data } = decodeEvent(this.idl, eventData);
213+
events.push({ program: this.programAddress, name, data });
206214
}
207215
}
208216
}
209217

210218
return events;
211219
}
212220

213-
public getSvmSpokeAddress(): Address {
214-
return this.svmSpokeAddress;
221+
public getProgramAddress(): Address {
222+
return this.programAddress;
215223
}
216224
}

src/arch/svm/types.ts

+3-20
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,4 @@
11
import { Signature, Address, UnixTimestamp } from "@solana/kit";
2-
import { SvmSpokeClient } from "@across-protocol/contracts";
3-
4-
export type EventData =
5-
| SvmSpokeClient.BridgedToHubPool
6-
| SvmSpokeClient.TokensBridged
7-
| SvmSpokeClient.ExecutedRelayerRefundRoot
8-
| SvmSpokeClient.RelayedRootBundle
9-
| SvmSpokeClient.PausedDeposits
10-
| SvmSpokeClient.PausedFills
11-
| SvmSpokeClient.SetXDomainAdmin
12-
| SvmSpokeClient.EnabledDepositRoute
13-
| SvmSpokeClient.FilledRelay
14-
| SvmSpokeClient.FundsDeposited
15-
| SvmSpokeClient.EmergencyDeletedRootBundle
16-
| SvmSpokeClient.RequestedSlowFill
17-
| SvmSpokeClient.ClaimedRelayerRefund
18-
| SvmSpokeClient.TransferredOwnership;
192

203
export enum SVMEventNames {
214
FilledRelay = "FilledRelay",
@@ -36,12 +19,12 @@ export enum SVMEventNames {
3619

3720
export type EventName = keyof typeof SVMEventNames;
3821

39-
export type EventWithData<T extends EventData> = {
22+
export type EventWithData = {
4023
confirmationStatus: string | null;
4124
blockTime: UnixTimestamp | null;
4225
signature: Signature;
4326
slot: bigint;
44-
name: EventName;
45-
data: T;
27+
name: string;
28+
data: unknown;
4629
program: Address;
4730
};

src/arch/svm/utils.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { BN, BorshEventCoder, Idl } from "@coral-xyz/anchor";
22
import web3, { address, getProgramDerivedAddress, getU64Encoder, Address, RpcTransport } from "@solana/kit";
3-
4-
import { EventName, EventData, SVMEventNames } from "./types";
3+
import { EventName, SVMEventNames } from "./types";
54

65
/**
76
* Helper to determine if the current RPC network is devnet.
@@ -42,11 +41,11 @@ export function parseEventData(eventData: any): any {
4241
/**
4342
* Decodes a raw event according to a supplied IDL.
4443
*/
45-
export function decodeEvent(idl: Idl, rawEvent: string): { data: EventData; name: EventName } {
44+
export function decodeEvent(idl: Idl, rawEvent: string): { data: unknown; name: string } {
4645
const event = new BorshEventCoder(idl).decode(rawEvent);
4746
if (!event) throw new Error(`Malformed rawEvent for IDL ${idl.address}: ${rawEvent}`);
4847
return {
49-
name: getEventName(event.name),
48+
name: event.name,
5049
data: parseEventData(event.data),
5150
};
5251
}

src/clients/SpokePoolClient/SVMSpokePoolClient.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import winston from "winston";
22
import { Address, Rpc, SolanaRpcApiFromTransport, RpcTransport } from "@solana/kit";
33

44
import { BigNumber, DepositSearchResult, EventSearchConfig, MakeOptional } from "../../utils";
5-
import {
6-
SvmSpokeEventsClient,
7-
SVMEventNames,
8-
getFillDeadline,
9-
getTimestampForBlock,
10-
getStatePda,
11-
} from "../../arch/svm";
5+
import { SvmCpiEventsClient, SVMEventNames, getFillDeadline, getTimestampForBlock, getStatePda } from "../../arch/svm";
126
import { HubPoolClient } from "../HubPoolClient";
137
import { knownEventNames, SpokePoolClient, SpokePoolUpdate } from "./SpokePoolClient";
148
import { RelayData, FillStatus } from "../../interfaces";
@@ -27,9 +21,9 @@ export class SvmSpokePoolClient extends SpokePoolClient {
2721
chainId: number,
2822
deploymentSlot: bigint, // Using slot instead of block number for SVM
2923
eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock">,
24+
protected svmEventsClient: SvmCpiEventsClient,
3025
protected programId: Address,
3126
protected statePda: Address,
32-
protected svmEventsClient: SvmSpokeEventsClient,
3327
protected rpc: Rpc<SolanaRpcApiFromTransport<RpcTransport>>
3428
) {
3529
// Convert deploymentSlot to number for base class, might need refinement
@@ -47,18 +41,18 @@ export class SvmSpokePoolClient extends SpokePoolClient {
4741
eventSearchConfig: MakeOptional<EventSearchConfig, "toBlock"> = { fromBlock: 0, maxBlockLookBack: 0 }, // Provide default
4842
rpc: Rpc<SolanaRpcApiFromTransport<RpcTransport>>
4943
): Promise<SvmSpokePoolClient> {
50-
const svmEventsClient = await SvmSpokeEventsClient.create(rpc);
51-
const programId = svmEventsClient.getSvmSpokeAddress();
44+
const svmEventsClient = await SvmCpiEventsClient.create(rpc);
45+
const programId = svmEventsClient.getProgramAddress();
5246
const statePda = await getStatePda(programId);
5347
return new SvmSpokePoolClient(
5448
logger,
5549
hubPoolClient,
5650
chainId,
5751
deploymentSlot,
5852
eventSearchConfig,
53+
svmEventsClient,
5954
programId,
6055
statePda,
61-
svmEventsClient,
6256
rpc
6357
);
6458
}

0 commit comments

Comments
 (0)