# Frontend SDK Integration \[Build a Next.js frontend that signs Newton intents in the browser, evaluates them on the server, and executes attested transactions]

This guide uses a split architecture:

* The connected browser wallet signs the EIP-712 intent and submits the allowed transaction.
* A Next.js Route Handler calls `evaluateIntentDirect` with `NEWTON_API_KEY` on the server.
* The user's private key and the Newton API key never enter browser JavaScript.

## Prerequisites

* A deployed and configured PolicyClient (see [Smart Contract Integration](/developers/guides/smart-contract-integration))
* A Newton API key (see [Dashboard & API Keys](/developers/overview/dashboard-api-keys))
* The exact intent encoding expected by the policy and PolicyClient
* Node.js >= 20

## Step 1 — Create the project

```bash
npx create-next-app@latest newton-sdk-app \
  --typescript --eslint --app --src-dir --import-alias "@/*"
cd newton-sdk-app
npm install @newton-xyz/sdk viem wagmi @tanstack/react-query
```

## Step 2 — Configure server and public values

Create a gitignored `.env.local`:

```bash
# Server only. Never prefix this with NEXT_PUBLIC_.
NEWTON_API_KEY=your_newton_api_key

# Server RPC used by the Route Handler.
RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY

# Public configuration; addresses are not credentials.
NEXT_PUBLIC_POLICY_CLIENT_ADDRESS=0xYourPolicyClient
NEXT_PUBLIC_TARGET_ADDRESS=0xYourDownstreamTarget
NEXT_PUBLIC_CHAIN_ID=11155111
```

:::warning
`NEWTON_API_KEY` can authorize gateway evaluation and client-scoped secrets operations. Exposing it through a `NEXT_PUBLIC_*` variable, Client Component, or browser-side SDK client compromises more than a demo request. Keep it on the server.
:::

Do not add `SIGNER_PRIVATE_KEY` to the application. The connected wallet signs the intent and later sends the PolicyClient transaction.

## Step 3 — Keep one intent encoding

Policy simulation, the gateway request, the EIP-712 signature, and the PolicyClient checks must use the same six fields:

```typescript
import {
  encodeFunctionData,
  erc20Abi,
  stringToHex,
  type Address,
} from "viem";

const intent = {
  from: connectedAddress,
  to: tokenAddress,
  value: 0n,
  data: encodeFunctionData({
    abi: erc20Abi,
    functionName: "transfer",
    args: [recipient, amount],
  }),
  chainId: 11155111n,
  functionSignature: stringToHex(
    "function transfer(address recipient, uint256 amount)",
  ),
} as const;
```

`intent.to` is the downstream target the policy evaluates, which is not necessarily the PolicyClient. `functionSignature` is hex-encoded human-readable ABI text, not the four-byte selector. Match the exact named form used by the policy fixture and PolicyClient.

## Step 4 — Sign in the browser

Read the EIP-712 domain from the PolicyClient when it exposes EIP-5267 `eip712Domain()`. Otherwise use only the fallback domain documented by that PolicyClient, with `verifyingContract` set to the PolicyClient address.

```typescript
const INTENT_TYPES = {
  Intent: [
    { name: "from", type: "address" },
    { name: "to", type: "address" },
    { name: "value", type: "uint256" },
    { name: "data", type: "bytes" },
    { name: "chainId", type: "uint256" },
    { name: "functionSignature", type: "bytes" },
  ],
} as const;

const intentSignature = await walletClient.signTypedData({
  account: connectedAddress,
  domain,
  types: INTENT_TYPES,
  primaryType: "Intent",
  message: intent,
});
```

The signature must cover the exact intent submitted to the server. Do not sign with an application-owned private key or replace `intent.from` with a server account.

## Step 5 — Evaluate in a Route Handler

Create `src/app/api/evaluate/route.ts`:

```typescript
import { newtonWalletClientActions } from "@newton-xyz/sdk";
import { NextResponse } from "next/server";
import { createWalletClient, http, type Address, type Hex } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";

export const runtime = "nodejs";

type EvaluateBody = {
  policyClient: Address;
  intent: {
    from: Address;
    to: Address;
    value: Hex;
    data: Hex;
    chainId: number;
    functionSignature: Hex;
  };
  intentSignature: Hex;
  wasmArgs?: Hex;
};

export async function POST(request: Request) {
  const apiKey = process.env.NEWTON_API_KEY?.trim();
  const rpcUrl = process.env.RPC_URL?.trim();
  if (!apiKey || !rpcUrl) {
    return NextResponse.json(
      { error: "NEWTON_API_KEY and RPC_URL must be set on the server" },
      { status: 500 },
    );
  }

  const body = (await request.json()) as EvaluateBody;
  if (
    !body.policyClient ||
    !body.intent ||
    !body.intentSignature ||
    body.intent.chainId !== sepolia.id
  ) {
    return NextResponse.json({ error: "Invalid evaluation request" }, { status: 400 });
  }

  // The SDK attaches actions to a WalletClient. This throwaway account never
  // signs, never holds funds, and is not used as intent.from.
  const account = privateKeyToAccount(generatePrivateKey());
  const newtonClient = createWalletClient({
    account,
    chain: sepolia,
    transport: http(rpcUrl),
  }).extend(newtonWalletClientActions({ apiKey }));

  try {
    const { result } = await newtonClient.evaluateIntentDirect({
      policyClient: body.policyClient,
      intent: body.intent,
      intentSignature: body.intentSignature,
      ...(body.wasmArgs ? { wasmArgs: body.wasmArgs } : {}),
      timeout: 30, // seconds
    });

    return NextResponse.json(result);
  } catch (error) {
    const message = error instanceof Error ? error.message : "Evaluation failed";
    return NextResponse.json({ error: message }, { status: 502 });
  }
}
```

For production, validate `policyClient`, `intent.to`, chain, calldata, and request size against server-owned configuration before calling the gateway. Add authentication and rate limiting appropriate to your application.

## Step 6 — Submit only on allow

Send the signed intent to the Route Handler. Hex-encode integer fields before JSON serialization because `JSON.stringify` cannot serialize `bigint`:

```typescript
const response = await fetch("/api/evaluate", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    policyClient,
    intent: {
      ...intent,
      value: `0x${intent.value.toString(16)}`,
      chainId: Number(intent.chainId),
    },
    intentSignature,
  }),
});

const evaluation = await response.json();
if (!response.ok) throw new Error(evaluation.error ?? "Evaluation failed");

if (!evaluation.evaluationResult) {
  // A deny is a completed policy decision, not a transport failure.
  showDeniedState();
  return;
}

await walletClient.writeContract({
  address: policyClient,
  abi: policyClientAbi,
  functionName: "transferWithAttestation",
  args: [
    recipient,
    amount,
    evaluation.task,
    evaluation.taskResponse,
    evaluation.blsSignature,
  ],
});
```

The protected function name and arguments depend on your PolicyClient. Bind every executed value to the attested intent in Solidity; never accept extra, unchecked execution arguments.

:::note
`status: "success"` from the gateway means evaluation and aggregation completed. The policy decision is `evaluationResult`. Never submit the application transaction when it is false.
:::

## Raw gateway responses

The SDK normalizes the response used above. If you call the JSON-RPC gateway directly, byte fields may arrive as number arrays and field names may use snake case. See the [RPC API Reference](/developers/reference/rpc-api) for the wire format instead of copying SDK response handling into a raw client.

## Security checklist

* `NEWTON_API_KEY` is server-only and absent from browser bundles.
* The connected wallet signs the EIP-712 intent; there is no app signer private key.
* The server validates the PolicyClient, target, chain, and request shape.
* `functionSignature` matches the policy and Solidity client byte-for-byte.
* The client submits no transaction on deny.
* The Solidity function checks sender, chain, target, selector, value, arguments, freshness, and replay protection.

## Troubleshooting

| Issue | Likely cause | Fix |
|---|---|---|
| `401` or gateway error | Missing, invalid, or server-inaccessible API key | Verify `NEWTON_API_KEY` is present without printing it |
| EIP-712 signature rejected | Wrong domain or changed intent fields | Read `eip712Domain()` and submit the exact signed intent |
| Policy unexpectedly denies | Incorrect `functionSignature`, calldata, params, or WASM args | Compare with the policy's local allow fixture |
| `InvalidAttestation` | PolicyClient, TaskManager, policy ID, or bound arguments differ | Verify deployed getters and Solidity intent checks |
| Attested call reverts | Downstream call failed or approval is missing | Inspect the PolicyClient and target revert reason |
| Timeout | Gateway did not finish within the requested seconds | Increase `timeout` cautiously and inspect operator/oracle health |

## Next steps

<Card icon="terminal" to="/developers/overview/agent-quickstart" title="Generate a local demo with Agent Skills">
  Scaffold this server/client split from a brief and existing handoff files
</Card>

<Card icon="code" to="/developers/reference/sdk-reference" title="SDK Reference">
  Review `evaluateIntentDirect` and response types
</Card>

<Card icon="plug" to="/developers/reference/rpc-api" title="RPC API">
  Review the underlying gateway wire format
</Card>
