0xtrails
Version:
SDK for Trails
369 lines (322 loc) • 8.23 kB
text/typescript
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,
)
}