Endo
    Preparing search index...

    Module @endo/exo - v1.5.12

    @endo/exo

    Create defensive remotable objects by combining Far objects with InterfaceGuards.

    An Exo is a remotable object (created with Far from @endo/pass-style) protected by an InterfaceGuard (from @endo/patterns). The guard automatically validates all method arguments and return values, providing the first layer of defense against malformed input.

    This package provides three patterns for creating exos:

    • makeExo - Single instance with minimal state management
    • defineExoClass - Multiple instances with per-instance state
    • defineExoClassKit - Multiple facets (related objects) sharing state

    Far objects are remotable but don't validate inputs. Exos add automatic validation:

    import { Far } from '@endo/pass-style';
    import { makeExo } from '@endo/exo';
    import { M } from '@endo/patterns';

    // Far object - no validation
    let count = 0;
    const counter1 = Far('Counter', {
    increment(n) {
    count += n; // What if n is not a number? undefined? a string?
    return count;
    }
    });

    // Exo - automatic validation
    const CounterI = M.interface('Counter', {
    increment: M.call(M.number()).returns(M.number())
    });

    const counter2 = makeExo('Counter', CounterI, {
    increment(n) {
    count += n; // n is guaranteed to be a number by the guard
    return count;
    }
    });

    counter2.increment(5); // OK
    counter2.increment('5'); // throws: Must be a number

    The InterfaceGuard validates arguments before the method executes, catching errors at the boundary rather than deep in your logic.

    Use when you need one exo instance with no complex state management:

    import { makeExo } from '@endo/exo';
    import { M } from '@endo/patterns';

    const GreeterI = M.interface('Greeter', {
    greet: M.call(M.string()).returns(M.string())
    });

    const greeter = makeExo('Greeter', GreeterI, {
    greet(name) {
    return `Hello, ${name}!`;
    }
    });

    greeter.greet('World'); // 'Hello, World!'

    When to use:

    • Single, stateless service objects
    • Utility objects with no instance-specific state
    • Simple cases where you don't need class-like behavior

    Use when you need multiple exo instances, each with their own state:

    import { defineExoClass } from '@endo/exo';
    import { M } from '@endo/patterns';

    const CounterI = M.interface('Counter', {
    increment: M.call().optional(M.number()).returns(M.number()),
    getValue: M.call().returns(M.number())
    });

    const makeCounter = defineExoClass(
    'Counter',
    CounterI,

    // init function: creates initial state for each instance
    (initialValue = 0) => ({ count: initialValue }),

    // methods: have access to this.state and this.self
    {
    increment(delta = 1) {
    this.state.count += delta;
    return this.state.count;
    },
    getValue() {
    return this.state.count;
    }
    }
    );

    const counter1 = makeCounter(0);
    const counter2 = makeCounter(100);

    counter1.increment(); // 1
    counter2.increment(); // 101 (separate state)

    When to use:

    • Need multiple independent instances
    • Each instance has its own state

    State access:

    • this.state - The instance's state object
    • this.self - Reference to the exo itself (for return values or callbacks)

    Use when you need multiple related objects (facets) that share the same state. This is the key pattern for least authority: give each client only the facet they need.

    import { defineExoClassKit } from '@endo/exo';
    import { M } from '@endo/patterns';

    const CounterKitI = {
    up: M.interface('UpCounter', {
    increment: M.call(M.number()).returns(M.number())
    }),
    down: M.interface('DownCounter', {
    decrement: M.call(M.number()).returns(M.number())
    }),
    reader: M.interface('CounterReader', {
    getValue: M.call().returns(M.number())
    })
    };

    const makeCounterKit = defineExoClassKit(
    'Counter',
    CounterKitI,

    // init: shared state across all facets
    (initialValue = 0) => ({ count: initialValue }),

    // methods: one object per facet
    {
    up: {
    increment(delta) {
    this.state.count += delta;
    return this.state.count;
    }
    },
    down: {
    decrement(delta) {
    this.state.count -= delta;
    return this.state.count;
    }
    },
    reader: {
    getValue() {
    return this.state.count;
    }
    }
    }
    );

    const { up, down, reader } = makeCounterKit(50);

    // Give different facets to different clients
    // incrementer only gets `up`, decrementer only gets `down`
    // Everyone can have `reader`, it's read-only
    up.increment(10); // 60
    down.decrement(5); // 55
    reader.getValue(); // 55

    When to use:

    • Need to separate capabilities (least authority)
    • Multiple related objects that share state
    • Example: public/private interfaces, admin/user facets, mint/purse/payment patterns

    Context access:

    • this.state - Shared state across all facets
    • this.facets - Object containing all facets (for inter-facet communication)

    For methods that await promises, use M.callWhen() instead of M.call():

    import { makeExo } from '@endo/exo';
    import { M } from '@endo/patterns';
    import { E } from '@endo/eventual-send';

    const FetcherI = M.interface('Fetcher', {
    // Async method: validates and awaits arguments before calling method
    fetch: M.callWhen(M.string()).returns(M.string())
    });

    const fetcher = makeExo('Fetcher', FetcherI, {
    async fetch(url) {
    // url is validated, then awaited if it's a promise
    const response = await E(httpClient).get(url);
    return response.text();
    }
    });

    The M.callWhen() guard:

    1. Validates the argument pattern
    2. Awaits the argument if it's a promise
    3. Validates the resolved value against the pattern
    4. Then calls the method with the validated, resolved value

    This enables safe eventual-send semantics: remote calls can pass promises, and your method receives validated resolved values.

    Each exo pattern handles state differently:

    No built-in state management. Use closure variables:

    let count = 0;

    const counter = makeExo('Counter', CounterI, {
    increment() {
    count += 1;
    return count;
    }
    });

    this.state is per-instance:

    const makeCounter = defineExoClass(
    'Counter',
    CounterI,
    (initial) => ({ count: initial }), // state for this instance
    {
    increment() {
    this.state.count += 1; // each instance has separate state
    return this.state.count;
    }
    }
    );

    this.state is shared across all facets:

    const makeKit = defineExoClassKit(
    'Kit',
    { facet1: I1, facet2: I2 },
    () => ({ sharedData: [] }), // shared by both facets
    {
    facet1: {
    add(item) {
    this.state.sharedData.push(item); // modifies shared state
    }
    },
    facet2: {
    getAll() {
    return this.state.sharedData; // reads shared state
    }
    }
    }
    );

    Every exo with an InterfaceGuard has a meta-method to retrieve its interface at runtime:

    import { GET_INTERFACE_GUARD } from '@endo/exo';
    import { getInterfaceMethodKeys } from '@endo/patterns';

    const counter = makeCounter();

    // Get the interface guard (works with E() too)
    const interfaceGuard = await E(counter)[GET_INTERFACE_GUARD]();

    // Inspect available methods
    const methodNames = getInterfaceMethodKeys(interfaceGuard);
    console.log(methodNames); // ['increment', 'getValue']

    // Build dynamic clients, generate documentation, etc.

    This enables:

    • Runtime interface discovery
    • Dynamic client generation
    • Documentation generation
    • Protocol negotiation

    Note: The interface can change across vat upgrades, so clients caching it may become stale.

    This package provides heap-based exos that don't survive vat termination. For production systems with high cardinality or upgrade requirements, see:

    • @agoric/vat-data - Provides:

      • defineVirtualExoClass - Backed by virtual object storage (pageable)
      • defineDurableExoClass - Survives vat upgrades
      • prepareExoClass - Unified API for both
      • prepareExoClassKit - Durable/virtual kits
    • Exo Taxonomy - Complete reference of all exo creation patterns including virtual and durable variants

    The heap exos in this package are ideal for:

    • Development and testing
    • Low cardinality objects (< thousands)
    • Temporary session state
    • Non-critical services

    Complete Tutorial: See Message Passing for a comprehensive guide showing how exos work with pass-style, patterns, and eventual-send to enable safe distributed computing.

    • Exo Taxonomy - Complete API reference and make/define/prepare patterns

    Type Aliases

    Amplify
    ClassContext
    ClassContextProvider
    ContextProvider
    ExoClassInterfaceGuard
    ExoClassInterfaceGuardKit
    ExoClassKitMethods
    ExoClassMethods
    FacetName
    Farable
    FarClassOptions
    Guarded
    GuardedKit
    IsInstance
    KitContext
    KitContextProvider
    MatchConfig
    Methods
    ReceivePower
    StateShape

    Variables

    GET_INTERFACE_GUARD

    Functions

    defineExoClass
    defineExoClassKit
    initEmpty
    makeExo