Skip to main content
Agents call your contract via the same execute transport as any other T3N contract. The only difference is the script_name starts with z:<tid>:.
Agents authenticate as themselves, not as tenants. Like every T3N session, an agent reads its own DID back from the authenticated session — there’s nothing tenant-specific to set.

1. Authorize the contract’s egress

Before any function that makes an outbound HTTP call can run, the user (data owner) must authorize it. A tenant contract’s allowed hosts are resolved per-call from the user’s authorization grant — not from the contract. The user signs an agent-auth-update scoping the agent to your contract, its functions, and the hosts it may reach:
// Signed by the USER (data owner), not the agent.
const userContractVersion = await getScriptVersion(getNodeUrl(), "tee:user/contracts");
await userClient.execute({
  script_name: "tee:user/contracts",
  script_version: userContractVersion,
  function_name: "agent-auth-update",
  input: {
    agents: [{
      agentDid: agentDid,                               // the agent being authorized
      scripts: [{
        scriptName: TENANT_SCRIPT,                      // z:<tid>:travel/contracts
        versionReq: scriptVersion,
        functions: ["search-offers", "book-offer"],
        allowedHosts: ["api.duffel.com"],               // hosts the contract may dial
      }],
    }],
  },
});
For a direct (self) call — where the user invokes the contract themselves rather than through a separate agent — set agentDid to the user’s own DID (a self-grant). Without a matching grant the contract still runs, but any outbound call is denied with host/http.egress_denied. See Outbound HTTP is authorized by the user, not the contract.

2. Invoke your contract

import {
  T3nClient,
  loadWasmComponent,
  createEthAuthInput,
  eth_get_address,
  metamask_sign,
  getScriptVersion,
  getNodeUrl,
} from "@terminal3/t3n-sdk";

const agentKey = process.env.AGENT_KEY!;
const agentAddress = eth_get_address(agentKey);

const agentClient = new T3nClient({
  wasmComponent,   // node URL resolved from setEnvironment() — see set-up-dev-env
  handlers: {
    EthSign: metamask_sign(agentAddress, undefined, agentKey),
  },
});

await agentClient.handshake();
await agentClient.authenticate(createEthAuthInput(agentAddress));

const TENANT_SCRIPT = `z:${tenantDid.slice("did:t3n:".length)}:travel/contracts`;
const scriptVersion = await getScriptVersion(getNodeUrl(), TENANT_SCRIPT);

// 1. Search for offers (no PII)
const search = await agentClient.executeAndDecode({
  script_name: TENANT_SCRIPT,
  script_version: scriptVersion,
  function_name: "search-offers",
  input: { origin: "LHR", destination: "JFK", departure_date: "2026-07-15", cabin_class: "economy", adult_count: 1 },
});
const offer = search.offers[0];

// 2. Book the chosen offer. No PII in the input — name, DOB and email are
//    resolved host-side from the user's profile via http-with-placeholders,
//    and only when the user's grant authorizes this agent (see the grant above).
const booking = await agentClient.executeAndDecode({
  script_name: TENANT_SCRIPT,
  script_version: scriptVersion,
  function_name: "book-offer",
  input: {
    offer_id:       offer.id,
    passenger_id:   offer.passenger_ids[0],  // opaque Duffel id from search — not PII
    total_amount:   offer.total_amount,
    total_currency: offer.total_currency,
  },
});
// booking.pnr → the flight booking reference. The passenger's name never left the enclave.