UNPKG

0xtrails

Version:

SDK for Trails

369 lines (322 loc) 8.23 kB
import type { TransactionReceipt, Log } from "viem" import { decodeEventLog, parseAbiItem } from "viem" // Corrected ABI items const CALL_SUCCEEDED_ABI = parseAbiItem( "event CallSucceeded(bytes32 _opHash, uint256 _index)", ) const CALL_FAILED_ABI = parseAbiItem( "event CallFailed(bytes32 _opHash, uint256 _index, bytes _returnData)", ) const CALL_ABORTED_ABI = parseAbiItem( "event CallAborted(bytes32 _opHash, uint256 _index, bytes _returnData)", ) // only `deploymentId` is indexed const CALL_SKIPPED_ABI = parseAbiItem( "event CallSkipped(bytes32 indexed deploymentId, uint256 actionIndex)", ) // Refund / Sweep events come from the Trails Token Sweeper contract. // Contract source and event definitions: // - https://github.com/0xsequence/trails-contracts/pull/28 // - https://github.com/0xsequence/stack/pull/1249 const REFUND_ABI = parseAbiItem( "event Refund(address indexed token, address indexed recipient, uint256 amount)", ) const SWEEP_ABI = parseAbiItem( "event Sweep(address indexed token, address indexed recipient, uint256 amount)", ) const REFUND_AND_SWEEP_ABI = parseAbiItem( "event RefundAndSweep(address indexed token, address indexed refundRecipient, uint256 refundAmount, address indexed sweepRecipient, uint256 actualRefund, uint256 remaining)", ) // Event types export interface CallSucceededEvent { type: "CallSucceeded" opHash: string index: bigint log: Log } export interface CallFailedEvent { type: "CallFailed" opHash: string index: bigint returnData: string log: Log } export interface CallAbortedEvent { type: "CallAborted" opHash: string index: bigint returnData: string log: Log } export interface CallSkippedEvent { type: "CallSkipped" deploymentId: string actionIndex: bigint log: Log } export interface RefundEvent { type: "Refund" token: string recipient: string amount: bigint log: Log } export interface SweepEvent { type: "Sweep" token: string recipient: string amount: bigint log: Log } export interface RefundAndSweepEvent { type: "RefundAndSweep" token: string refundRecipient: string refundAmount: bigint sweepRecipient: string actualRefund: bigint remaining: bigint log: Log } export type GuestModuleEvent = | CallSucceededEvent | CallFailedEvent | CallAbortedEvent | CallSkippedEvent // Trails Token Sweeper events: see PR references above for canonical specs. export type TrailsTokenSweeperEvent = | RefundEvent | SweepEvent | RefundAndSweepEvent // === Decoders === export function decodeCallSucceeded(log: Log): CallSucceededEvent | null { try { const decoded = decodeEventLog({ abi: [CALL_SUCCEEDED_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "CallSucceeded" args: { _opHash: string; _index: bigint } } return { type: "CallSucceeded", opHash: decoded.args._opHash, index: decoded.args._index, log, } } catch { return null } } export function decodeCallFailed(log: Log): CallFailedEvent | null { try { const decoded = decodeEventLog({ abi: [CALL_FAILED_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "CallFailed" args: { _opHash: string; _index: bigint; _returnData: string } } return { type: "CallFailed", opHash: decoded.args._opHash, index: decoded.args._index, returnData: decoded.args._returnData, log, } } catch { return null } } export function decodeCallAborted(log: Log): CallAbortedEvent | null { try { const decoded = decodeEventLog({ abi: [CALL_ABORTED_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "CallAborted" args: { _opHash: string; _index: bigint; _returnData: string } } return { type: "CallAborted", opHash: decoded.args._opHash, index: decoded.args._index, returnData: decoded.args._returnData, log, } } catch { return null } } export function decodeCallSkipped(log: Log): CallSkippedEvent | null { try { const decoded = decodeEventLog({ abi: [CALL_SKIPPED_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "CallSkipped" args: { deploymentId: string; actionIndex: bigint } } return { type: "CallSkipped", deploymentId: decoded.args.deploymentId, actionIndex: decoded.args.actionIndex, log, } } catch { return null } } export function decodeRefund(log: Log): RefundEvent | null { try { const decoded = decodeEventLog({ abi: [REFUND_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "Refund" args: { token: string; recipient: string; amount: bigint } } return { type: "Refund", token: decoded.args.token, recipient: decoded.args.recipient, amount: decoded.args.amount, log, } } catch { return null } } export function decodeRefundAndSweep(log: Log): RefundAndSweepEvent | null { try { const decoded = decodeEventLog({ abi: [REFUND_AND_SWEEP_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "RefundAndSweep" args: { token: string refundRecipient: string refundAmount: bigint sweepRecipient: string actualRefund: bigint remaining: bigint } } return { type: "RefundAndSweep", token: decoded.args.token, refundRecipient: decoded.args.refundRecipient, refundAmount: decoded.args.refundAmount, sweepRecipient: decoded.args.sweepRecipient, actualRefund: decoded.args.actualRefund, remaining: decoded.args.remaining, log, } } catch { return null } } export function decodeSweep(log: Log): SweepEvent | null { try { const decoded = decodeEventLog({ abi: [SWEEP_ABI], data: log.data, topics: log.topics, strict: false, }) as { eventName: "Sweep" args: { token: string; recipient: string; amount: bigint } } return { type: "Sweep", token: decoded.args.token, recipient: decoded.args.recipient, amount: decoded.args.amount, log, } } catch { return null } } // === Main decoder === export function decodeTrailsTokenSweeperEvents( receipt: TransactionReceipt, ): TrailsTokenSweeperEvent[] { const events: TrailsTokenSweeperEvent[] = [] for (const log of receipt.logs) { const refund = decodeRefund(log) if (refund) { events.push(refund) continue } const refundAndSweep = decodeRefundAndSweep(log) if (refundAndSweep) { events.push(refundAndSweep) continue } const sweep = decodeSweep(log) if (sweep) { events.push(sweep) } } return events } export function decodeGuestModuleEvents( receipt: TransactionReceipt, ): GuestModuleEvent[] { const events: GuestModuleEvent[] = [] for (const log of receipt.logs) { const succeeded = decodeCallSucceeded(log) if (succeeded) { events.push(succeeded) continue } const failed = decodeCallFailed(log) if (failed) { events.push(failed) continue } const aborted = decodeCallAborted(log) if (aborted) { events.push(aborted) continue } const skipped = decodeCallSkipped(log) if (skipped) { events.push(skipped) } } return events } // === Utilities === export function getEventsByType<T extends GuestModuleEvent["type"]>( events: GuestModuleEvent[], type: T, ): Extract<GuestModuleEvent, { type: T }>[] { return events.filter( (event): event is Extract<GuestModuleEvent, { type: T }> => event.type === type, ) } export function getFirstEventByType<T extends GuestModuleEvent["type"]>( events: GuestModuleEvent[], type: T, ): Extract<GuestModuleEvent, { type: T }> | undefined { return events.find( (event): event is Extract<GuestModuleEvent, { type: T }> => event.type === type, ) }