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.