Skip to content

Integrations

World ID, 1inch, Privy, ENS

Four partner integrations, each doing one job: World ID gates the operator, 1inch quotes swaps for the operator's EVM wallet, Privy signs clients in with a passkey, and ENS names addresses. For each one, here is what it does, the exact env vars, where the code is, and the limit we state rather than hide.

World ID

What it does. Proves the operator is one human before margin can leave the treasury. The browser runs IDKit v4 (IDKitRequestWidget with the ProofOfHuman preset, allow_legacy_proofs=false) against a server-signed request context; the raw result is verified server-side at POST https://developer.worldcoin.org/api/v4/verify/{rp_id}; then verifyOperator is relayed to the agency with the deployer key so the RP-scoped nullifier is burned on-chain. A second proof from the same human returns 409 NULLIFIER_ALREADY_USED. The full sequence is on The operator.

World ID environment variables
NEXT_PUBLIC_WORLD_ID_APP_IDThe app_... id from the Developer Portal. Public; shipped to the browser for IDKit. Without it /api/operator/verify answers 503 and the gate falls back to a labelled simulation.
NEXT_PUBLIC_WORLD_ID_ACTIONAction identifier created under the app's Actions tab. aetheris-operator; case-sensitive.
WORLD_ID_RP_IDRelying-party id (rp_...). Used to sign the request context and as the path of the v4 verify endpoint.
WORLD_ID_RP_SIGNING_KEYRelying-party signing key, hex. Server-only; app/api/worldid/rp-context/route.ts signs a nonce with it before the widget opens.
WORLD_ID_API_BASEVerifier origin; defaults to https://developer.worldcoin.org in lib/worldid.ts.
WORLD_ID_ROUTER_ADDRESSConstructor argument for the agency. Left unset on Hedera, which puts the contract in explicit bypass mode.
WORLD_ID_GROUP_IDCredential group id passed to the router; 1 is Orb.

Where in the code

  • components/worldid-gate.tsx - the widget, the request-context fetch, the relay call and the result card.
  • app/api/worldid/rp-context/route.ts - signs the IDKit v4 rp_context (300 s TTL).
  • lib/worldid.ts - verifyWorldIdV4, isIdKitResultV4, extractV4Nullifier; the legacy v3 verifyWorldIdProof is kept for older proofs.
  • app/api/operator/verify/route.ts - verify, check nullifierHashUsed, relay verifyOperator, map reverts to 409 / 400 / 502.
  • app/api/verify-worldid/route.ts - verification only, no relay; accepts a v4 result or a legacy {proof, signal}.
  • lib/operator-registry.ts, lib/worldid-status.ts - the on-chain reads and the cached readiness probe behind the dashboard badge.
  • contracts/AetherisAgency.sol - verifyOperator, nullifierHashUsed, isVerifiedOperator, worldIdVerificationBypassed, setWorldId.

1inch

What it does. Gives the operator swap quotes and unsigned swap calldata from the 1inch Swap API v6.0 through two server-side proxy routes, so ONEINCH_API_KEY never reaches the browser. POST /api/swap/quote takes {chainId, src, dst, amount} and returns a normalised quote (dstAmount, estimatedGas, protocols); POST /api/swap/build adds from and optional slippage (0 to 50, default 1) and returns {to, data, value, gas?}. The build route never signs or broadcasts. Both validate addresses and base-unit amounts, reject same-token pairs, and answer 400 for a chain outside the supported list.

supported chains (lib/oneinch.ts)
Ethereum   1
Arbitrum   42161
Base       8453
Optimism   10
Polygon    137
1inch environment variables
ONEINCH_API_KEYBearer token for api.1inch.dev. Server-only; lib/oneinch.ts calls assertServerOnly before every request.
NEXT_PUBLIC_ONEINCH_BASE_URLAPI origin; defaults to https://api.1inch.dev.

Where in the code

  • lib/oneinch.ts - getQuote, buildSwapTx, getTokenBalances; bounded retry with backoff on 429 and 5xx, 1inch's own error text re-thrown on 4xx.
  • app/api/swap/quote/route.ts, app/api/swap/build/route.ts - the proxies.
  • components/treasury-panel.tsx - the "Rebalance via 1inch v6.0" form at /dashboard#swap.
  • contracts/AetherisTreasury.sol - rebalance(...), an owner-only accounting entry for a swap executed off-chain; the app UI does not call it.

Privy

What it does. Signs users in and gives them a wallet that can sign on Hedera. lib/privy.ts configures login methods email, google, passkey and wallet; embedded Ethereum wallets are created on login for users without one (createOnLogin: "users-without-wallets") with Privy's wallet UIs kept on; the default chain is Hedera testnet (296) and the supported chains are Hedera plus the five 1inch venues from lib/chains.ts. The role context reads the connected wallet, switches it to 296 and hands its EIP-1193 provider to lib/write.ts for approve, createJob and refundJob. See The client.

Privy environment variables
NEXT_PUBLIC_PRIVY_APP_IDPrivy app id from the dashboard. Public by design. components/providers.tsx mounts PrivyProvider only when it looks like a real id (18 or more alphanumeric characters).
PRIVY_APP_SECRETPrivy server secret, reserved for server-side token verification. lib/privy.ts documents it and never references it; no route in this repository reads it today.

Where in the code

  • lib/privy.ts - privyConfig, privyLoginMethods, isPrivyConfigured.
  • lib/chains.ts - hederaTestnet (id 296, HBAR, Hashio relay, HashScan) and aetherisChains.
  • components/providers.tsx - mounts PrivyProvider conditionally; exports PRIVY_ENABLED.
  • components/connect-button.tsx - "Sign in with passkey", the wallet menu, and the labelled demo session when Privy is absent.
  • components/app/role-context.tsx - usePrivy / useWallets bridge, chain switch, provider hand-off.

ENS

What it does. Turns addresses into names and back. lib/ens.ts builds one viem public client on Ethereum mainnet with a fallback transport: NEXT_PUBLIC_ENS_RPC_URL first, then publicnode, drpc, ankr and cloudflare, each with an 8 s timeout and one retry. It exposes resolveEnsName, lookupEnsAddress, getEnsAvatar, getEnsTextRecord and getEnsIdentity, every one returning null on failure so a dead RPC can never break a render. GET /api/ens?name=... or ?address=... keeps the client on the server and returns {address, name, avatar, source}, where source is live when anything resolved and unconfigured otherwise, cached for five minutes.

In the UI: the wallet menu reverse-resolves the connected address ("Resolved via ENS" or "No reverse ENS record"); the agency page accepts /agency/<name> as well as an address; the Mission Control header shows the agency's ENS name from the subgraph when it has one; and verifyOperator stores an ensName alongside the verification, defaulting to aetheris.eth in the relay.

ENS environment variables
NEXT_PUBLIC_ENS_RPC_URLEthereum mainnet RPC tried first; defaults to https://ethereum-rpc.publicnode.com.
AETHERIS_ENS_NAMEThe name the agency publishes as its identity at deploy time (aetheris.eth).

Where in the code

  • lib/ens.ts - the mainnet client and resolvers.
  • app/api/ens/route.ts - the HTTP route, backed by resolveEnsProfile in components/aetheris-server.ts.
  • components/use-ens.ts, components/connect-button.tsx, app/(app)/agency/[id]/page.tsx - the consumers.