Skip to main content

Overview

Plugins extend trace functionality by adding namespaced methods and lifecycle hooks. The SDK ships with a built-in Web3Plugin for blockchain integration, and you can create custom plugins for your own needs.

Web3Plugin

The Web3Plugin provides all blockchain-related methods — EVM transaction hints, Safe multisig tracking, Solana transaction hints, Relay (relay.link) bridge tracking, Canton (Daml Ledger API) transaction hints, and provider-based transaction sending.

Setup

import { Client, Web3Plugin } from '@miradorlabs/web-sdk';

const client = new Client('api-key', {
  plugins: [Web3Plugin({ provider: window.ethereum })]
});

Namespaced Methods

Once installed, Web3 methods are available on every trace under the web3 namespace:
const trace = client.trace({ name: 'Swap' });

// EVM transaction methods
trace.web3.evm.addTxHint(txHash, 'ethereum');
trace.web3.evm.addTxHint(txHash, 'ethereum', { input: calldata, details: 'Swap' });
trace.web3.evm.addTxInputData(calldata);
trace.web3.evm.addTx(tx);
trace.web3.evm.addTx(tx, 'polygon');
const hash = await trace.web3.evm.sendTransaction(txParams);

// Safe multisig methods
trace.web3.safe.addMsgHint(msgHash, 'ethereum');
trace.web3.safe.addMsgHint(msgHash, 'ethereum', 'Approval');
trace.web3.safe.addTxHint(safeTxHash, 'ethereum');
trace.web3.safe.addTxHint(safeTxHash, 'ethereum', 'Execution');

// Solana transaction methods — no chain arg, signature is the identifier.
// See concepts/solana-transactions.
trace.web3.solana.addTxHint(solanaSignature);
trace.web3.solana.addTxHint(solanaSignature, 'Jupiter swap');

// Relay (relay.link) bridge methods — record once, before the user deposits.
// The backend processor resolves the full quote from requestId and emits
// deposit → solver-committed → fill (or refund / failed / not-found) for
// you. See concepts/relay-bridges.
trace.web3.relay.addQuoteHint('rly_request_123');
trace.web3.relay.addQuoteHint('rly_request_456', 'queued from swap modal');

// Canton (Daml Ledger API) methods — correlate by ledger updateId.
// partyId is optional (omit for observer co-hosts). See concepts/canton-transactions.
trace.web3.canton.addTxHint(updateId);
trace.web3.canton.addTxHint(updateId, 'Alice::1220...', 'IOU transfer');

Method Chaining

Web3 methods return the trace instance for chaining:
const trace = client.trace({ name: 'SafeTransfer' })
  .addAttribute('safeAddress', '0xSafe...')
  .addTags(['safe', 'multisig']);

trace.web3.safe.addMsgHint(msgHash, 'ethereum', 'Approval');
trace.web3.evm.addTxHint(execTxHash, 'ethereum', 'Execution');

Plugin Lifecycle Hooks

Plugins can hook into the trace lifecycle:
HookWhen CalledUse Case
onFlushBefore each flushInject plugin-managed data into the flush payload
onCloseWhen trace is closedClean up resources, finalize pending operations
hasPendingDataBefore close completesSignal that the plugin has unflushed data

Creating Custom Plugins

A plugin implements the MiradorPlugin interface:
import type { MiradorPlugin, TraceContext, PluginSetupResult, FlushBuilder } from '@miradorlabs/web-sdk';

interface MyMethods {
  doSomething(value: string): void;
}

function MyPlugin(config: MyConfig): MiradorPlugin<MyMethods> {
  return {
    name: 'my-plugin',
    setup(context: TraceContext): PluginSetupResult<MyMethods> {
      let pendingItems: string[] = [];

      return {
        // Methods merged onto the trace under the plugin name
        methods: {
          doSomething(value: string) {
            if (context.isClosed()) return;
            pendingItems.push(value);
            context.scheduleFlush();
          }
        },

        // Lifecycle hooks
        onFlush(builder: FlushBuilder) {
          for (const item of pendingItems) {
            builder.addEvent({
              name: 'my_plugin:action',
              details: item,
              timestamp: new Date()
            });
          }
          pendingItems = [];
        },

        onClose() {
          pendingItems = [];
        },

        hasPendingData() {
          return pendingItems.length > 0;
        }
      };
    }
  };
}

Using Custom Plugins

const client = new Client('api-key', {
  plugins: [MyPlugin({ option: 'value' })]
});

const trace = client.trace({ name: 'Example' });
trace.my_plugin.doSomething('hello');

TraceContext

The TraceContext object passed to setup() provides read/write access to the trace:
interface TraceContext {
  addEvent(name: string, details?: string | object, options?: AddEventOptions): void;
  addAttribute(key: string, value: string | number | boolean | object): void;
  addAttributes(attrs: Record<string, string | number | boolean | object>): void;
  addTag(tag: string): void;
  addTags(tags: string[]): void;
  getTraceId(): string;
  isClosed(): boolean;
  scheduleFlush(): void;
  logger: Logger;
}
Always check context.isClosed() before starting async work (timers, listeners, network calls) in your plugin — the trace may have been closed between setup and your callback firing.

FlushBuilder

The FlushBuilder passed to onFlush lets plugins contribute data to the outgoing flush:
interface FlushBuilder {
  addHint<T extends HintTypeName>(type: T, data: HintDataMap[T]): void;
  addHint(type: string, data: Record<string, unknown>): void;
  addEvent(event: { name: string; details?: string; timestamp: Date; severity?: Severity }): void;
  addAttribute(key: string, value: string): void;
  addTag(tag: string): void;
}

Next Steps

Transaction Hints

EVM transaction hints via the Web3 plugin

Solana Transactions

Solana signature hints — no chain argument

Safe Multisig

Safe message and transaction tracking

Relay Bridges

Intent-based cross-chain bridges