> ## Documentation Index
> Fetch the complete documentation index at: https://docs.terminal3.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 4. Invoke your TEE contract

Agents call your contract via the same `execute` transport as any other T3N contract. The only difference is the `contract_id` starts with `z:<tid>:`.

<Note> **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.</Note>

## 1. Set up the agent's identity

An agent is a separate authenticated session with its own key — it is not the tenant and not the user. Build it the same way you built `t3n` in [Quickstart](/developers/adk/get-started/quickstart), but with its own credential:

```typescript theme={null}
import {
  T3nClient,
  loadWasmComponent,
  fetchTrustedManifest,
  createEthAuthInput,
  eth_get_address,
  metamask_sign,
  getContractVersion,
  getNodeUrl,
  fetchTrustedManifest,
} from "@terminal3/t3n-sdk";

const trustAnchor = await fetchTrustedManifest("testnet"); // required — see Verify the trust anchor; reused below for every client in this file

const agentKey = process.env.AGENT_KEY!; // a separate credential — never reuse your tenant's T3N_API_KEY
const agentAddress = eth_get_address(agentKey);

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

await agentClient.handshake();
const agentAuth = await agentClient.authenticate(createEthAuthInput(agentAddress));
const agentDid = agentAuth.value; // reused below when the user authorizes this agent

const TENANT_CONTRACT = `z:${tenantDid.slice("did:t3n:".length)}:travel-contracts`;
const contractVersion = await getContractVersion(getNodeUrl(), TENANT_CONTRACT);
```

<Note>
  **Where `AGENT_KEY` comes from.** An agent's key is not derived from your tenant key —
  get it the same way you got your own, from the [claim page](/developers/adk/get-started/prerequisites/request-test-tokens).
  It needs its **own** test credits too: an agent DID's balance is separate from its
  tenant's and starts at zero, so a key generated any other way (or your tenant's key,
  reused) will fail metered calls with `InsufficientCreditError`. See
  [Register a Public Agent](/developers/agents/register-agent) for the full flow.
</Note>

## 2. Authorize the contract's egress (as the user)

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.

In this walkthrough you're standing in for the user yourself, so build a third session — `userClient` — with its own credential, exactly like `t3n` and `agentClient` above:

```typescript theme={null}
const userKey = process.env.USER_KEY!; // stands in for the real data owner's own key
const userAddress = eth_get_address(userKey);

const userClient = new T3nClient({
  trustAnchor: await fetchTrustedManifest("testnet"),
  wasmComponent,
  trustAnchor, // same anchor as agentClient above — one environment, one manifest
  handlers: {
    EthSign: metamask_sign(userAddress, undefined, userKey),
  },
});

await userClient.handshake();
await userClient.authenticate(createEthAuthInput(userAddress));
```

Now the user signs a `member-delegation-update` scoping the agent to your contract, its functions, and the hosts it may reach:

```typescript theme={null}
// Signed by the USER (data owner), not the agent.
const userContractVersion = await getContractVersion(getNodeUrl(), "tee:user/contracts");
await userClient.execute({
  contract_id: "tee:user/contracts",
  contract_version: userContractVersion,
  function_name: "member-delegation-update",
  input: {
    grants: [{
      grantee: agentDid,                                // from step 1
      contract_id: TENANT_CONTRACT,                       // z:<tid>:travel-contracts, from step 1
      version_req: contractVersion,
      functions: ["search-offers", "book-offer"],
      allowed_hosts: ["api.duffel.com"],                // hosts the contract may dial
    }],
  },
});
```

<Warning>
  `member-delegation-update` **replaces the member's whole delegation policy.** This raw call is fine here — it's the first grant — but a later single-grant write would drop the earlier ones. To add grants incrementally, call `userClient.updateMemberDelegation(grant)`, which read-merge-writes so existing grants survive. See [Member Delegation](/developers/adk/get-started/member-delegation).
</Warning>

For a **direct (self) call** — where the user invokes the contract themselves rather than through a separate agent — set `grantee` to the user's own DID (a self-grant) instead of building a separate `agentClient`. 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](/developers/adk/tips/outbound-http-auth-by-user).

## 3. Invoke your contract (as the agent)

With the grant in place, the agent can call the contract's functions:

```typescript theme={null}
// 1. Search for offers (no PII)
const search = await agentClient.executeAndDecode({
  contract_id: TENANT_CONTRACT,
  contract_version: contractVersion,
  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({
  contract_id: TENANT_CONTRACT,
  contract_version: contractVersion,
  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.
```
