# Frontend SDK Integration \[Build a Next.js frontend that submits Newton Protocol evaluation requests and executes attested transactions]

This guide walks through building a Next.js application that uses the Newton SDK to evaluate transaction intents against your policy, then executes them through your deployed PolicyClient contract.

## Prerequisites

* A deployed PolicyClient contract (see [Smart Contract Integration](/developers/guides/smart-contract-integration))
* A Newton API key (see [Getting an API Key](/developers/overview/dashboard-api-keys))
* Node.js >= 20

## Step 1: Create the Project

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

## Step 2: Environment Variables

Create `.env.local`:

```bash
# Alchemy RPC URLs
NEXT_PUBLIC_SEPOLIA_ALCHEMY_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL=wss://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY

# Newton API key — server-side only (do NOT prefix with NEXT_PUBLIC_)
NEWTON_API_KEY=your_newton_api_key

# Newton Policy Contract address (fixed on Sepolia)
NEXT_PUBLIC_POLICY_CONTRACT_ADDRESS=0x698C687f86Bc2206AC7C06eA68AC513A2949abA6

# YOUR deployed wallet address
NEXT_PUBLIC_POLICY_WALLET_ADDRESS=0xYourDeployedWallet

# Signer private key — server-side only (do NOT prefix with NEXT_PUBLIC_)
SIGNER_PRIVATE_KEY=your_private_key_here
```

:::warning
Never use the `NEXT_PUBLIC_` prefix for private keys. Variables prefixed with `NEXT_PUBLIC_` are bundled into client-side JavaScript and visible to anyone inspecting your app. The `SIGNER_PRIVATE_KEY` above is intentionally server-side only — access it via an API route or server action, not directly in client components.
:::

:::info
`NEXT_PUBLIC_POLICY_CONTRACT_ADDRESS` is the Newton Policy contract (fixed on Sepolia). `NEXT_PUBLIC_POLICY_WALLET_ADDRESS` is YOUR deployed PolicyClient. These are different contracts.
:::

## Step 3: Create Configuration

Create `src/const/config.ts`:

```typescript
import { Hex } from "viem";

export const SEPOLIA_ALCHEMY_URL = process.env.NEXT_PUBLIC_SEPOLIA_ALCHEMY_URL!;
export const SEPOLIA_ALCHEMY_WS_URL = process.env.NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL!;
// Access NEWTON_API_KEY server-side only (via API route or server action)
// Do NOT expose API keys with NEXT_PUBLIC_ prefix
export const POLICY_WALLET_ADDRESS = process.env.NEXT_PUBLIC_POLICY_WALLET_ADDRESS as Hex;
export const POLICY_CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_POLICY_CONTRACT_ADDRESS as Hex;
// Access SIGNER_PRIVATE_KEY server-side only (via API route or server action)
// Do NOT expose private keys with NEXT_PUBLIC_ prefix
```

## Step 4: Create the Newton Client

Create `src/lib/use-newton-client.ts`:

```tsx twoslash
import { useMemo } from "react";
import { createPublicClient, createWalletClient, http, webSocket, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { newtonWalletClientActions, newtonPublicClientActions } from "@newton-xyz/sdk";
// ---cut---
export const useNewtonClient = (privateKey: Hex, apiKey: string) => {
  const SEPOLIA_ALCHEMY_URL = "https://eth-sepolia.g.alchemy.com/v2/demo";
  const SEPOLIA_ALCHEMY_WS_URL = "wss://eth-sepolia.g.alchemy.com/v2/demo";

  const client = useMemo(() => {
    const account = privateKeyToAccount(privateKey);
    
    const walletClient = createWalletClient({
      account,
      chain: sepolia,
      transport: webSocket(SEPOLIA_ALCHEMY_WS_URL),
    }).extend(newtonWalletClientActions({ apiKey }));

    const publicClient = createPublicClient({
      chain: sepolia,
      transport: http(SEPOLIA_ALCHEMY_URL),
    }).extend(newtonPublicClientActions());

    return { walletClient, publicClient, account };
  }, [privateKey, apiKey]);

  return client;
};
```

## Step 5: Submit an Evaluation Request

Create `src/lib/evaluation-request.ts`:

```typescript
import { Hex } from "viem";
import { sepolia } from "viem/chains";
import { POLICY_WALLET_ADDRESS } from "@/const/config";

function stringToHexBytes(str: string): Hex {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(str);
  return ("0x" + Array.from(bytes)
    .map((byte) => byte.toString(16).padStart(2, "0"))
    .join("")) as Hex;
}

export type EvaluationRequestParams = {
  signerAddress: Hex;
  targetAddress: Hex;
  value: bigint;
  data: Hex;
  functionSignature?: Hex;
  wasmArgs: Record<string, unknown>;
};

export const createEvaluationRequest = ({
  signerAddress,
  targetAddress,
  value,
  data,
  functionSignature,
  wasmArgs,
}: EvaluationRequestParams) => {
  return {
    policyClient: POLICY_WALLET_ADDRESS,
    intent: {
      from: signerAddress,
      to: targetAddress,
      value: `0x${value.toString(16)}` as Hex,
      data: data,
      chainId: sepolia.id,
      // Use "0x" for a native ETH transfer or any intent with no calldata.
      // For a contract call, pass stringToHexBytes("transfer(address,uint256)").
      functionSignature: functionSignature ?? "0x",
    },
    wasmArgs: stringToHexBytes(JSON.stringify(wasmArgs)),
    timeout: 60,
  };
};
```

The `functionSignature` value is for the intended transaction in `intent.data`, not for the later Newton wallet execution helper. For example:

```typescript
const nativeEthIntent = createEvaluationRequest({
  signerAddress,
  targetAddress: recipient,
  value: 1n,
  data: "0x",
  wasmArgs,
});

const erc20TransferIntent = createEvaluationRequest({
  signerAddress,
  targetAddress: tokenAddress,
  value: 0n,
  data: encodeFunctionData({
    abi: erc20Abi,
    functionName: "transfer",
    args: [recipient, amount],
  }),
  functionSignature: stringToHexBytes("transfer(address,uint256)"),
  wasmArgs,
});
```

## Step 6: Execute an Attested Transaction

Use `evaluateIntentDirect` to get an attestation, then submit the attested transaction on-chain:

```typescript
const domain = await readEip712Domain(evalRequest.policyClient, publicClient);
if (!domain) {
  throw new Error(
    "This policy client does not expose an EIP-712 domain, so the app cannot sign an intent for it.",
  );
}

const intentSignature = await walletClient.signTypedData({
  account,
  domain,
  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" },
    ],
  },
  primaryType: "Intent",
  message: {
    ...evalRequest.intent,
    value: BigInt(evalRequest.intent.value),
    chainId: BigInt(evalRequest.intent.chainId),
  },
});

// Evaluate intent — returns attestation data
const evalResponse = await walletClient.evaluateIntentDirect({
  ...evalRequest,
  intentSignature,
});
const { evaluationResult, task, taskResponse, blsSignature } = evalResponse.result;

if (!evaluationResult) {
  throw new Error("Policy evaluation failed — transaction blocked");
}

// Execute on-chain with the attestation
const functionData = encodeFunctionData({
  abi: newtonPolicyWalletAbi,
  functionName: "validateAndExecuteDirect",
  args: [to, value, data, task, taskResponse, blsSignature],
});

const txHash = await walletClient.sendTransaction({
  to: POLICY_WALLET_ADDRESS,
  data: functionData,
});
```

Add this EIP-5267 helper near your wallet/public client setup:

```typescript
import { Address } from "viem";

const EIP712_DOMAIN_ABI = [
  {
    type: "function",
    name: "eip712Domain",
    stateMutability: "view",
    inputs: [],
    outputs: [
      { name: "fields", type: "bytes1" },
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" },
      { name: "salt", type: "bytes32" },
      { name: "extensions", type: "uint256[]" },
    ],
  },
] as const;

export async function readEip712Domain(policyClient: Address, publicClient) {
  try {
    const [, name, version, chainId, verifyingContract] =
      await publicClient.readContract({
        address: policyClient,
        abi: EIP712_DOMAIN_ABI,
        functionName: "eip712Domain",
      });

    return {
      name,
      version,
      chainId: Number(chainId),
      verifyingContract,
    };
  } catch {
    return null;
  }
}
```

## Gateway Response Handling

When working with the gateway directly (outside the SDK), be aware of these serialization details.

### Field Name Mapping

The gateway uses different casing for `task` (camelCase) and `task_response` (snake\_case):

```typescript
// task fields: taskId, policyClient, quorumThresholdPercentage
// task_response fields: task_id, policy_client, evaluation_result

// Map with fallback pattern:
const mappedTaskResponse = {
  taskId: toHex(tr.task_id ?? tr.taskId),
  policyClient: tr.policy_client ?? tr.policyClient,
  policyId: toHex(tr.policy_id ?? tr.policyId),
  // ... etc
};
```

### Byte Array Conversion

The gateway returns all Solidity `bytes` fields as JSON number arrays (`[0,0,...,0,1]`), not hex strings. Viem expects hex strings and fails with `x.replace is not a function` if given arrays.

```typescript
function toHex(val: unknown): string {
  if (typeof val === "string") return val.startsWith("0x") ? val : `0x${val}`;
  if (Array.isArray(val)) {
    return `0x${val.map((b: number) => b.toString(16).padStart(2, "0")).join("")}`;
  }
  if (val == null) return "0x";
  return String(val);
}
```

Apply `toHex()` to these fields before ABI encoding: `evaluationResult`, `intentSignature`, `wasmArgs`, `quorumNumbers`, `policy`, `policyData[].data`, `policyData[].attestation`, `policyConfig.policyParams`, `taskId`, and `policyId`.

### Decoding `evaluationResult`

`evaluationResult` is an ABI-encoded boolean (32 bytes). The gateway returns it as a JSON byte array:

```typescript
function decodePolicyAllowed(taskResponse: Record<string, unknown>): boolean {
  const evalResult = taskResponse.evaluation_result ?? taskResponse.evaluationResult;
  if (evalResult == null) return false;
  if (typeof evalResult === "boolean") return evalResult;

  // Gateway returns byte arrays: [0,0,...,0,1] for true, [0,0,...,0,0] for false
  if (Array.isArray(evalResult)) {
    return evalResult.some((b: number) => b !== 0);
  }

  // Hex string fallback
  const str = String(evalResult);
  if (!str || str === "0x") return false;
  const hex = str.startsWith("0x") ? str.slice(2) : str;
  return BigInt(`0x${hex || "0"}`) !== 0n;
}
```

:::note
`status: "success"` in the gateway response means BLS aggregation completed, **not** that the policy approved the intent. Operators sign the evaluation result regardless of allow/deny. The actual policy decision is in `evaluationResult`.
:::

### Function Signature Encoding

:::warning
The `functionSignature` field in the intent is the **hex-encoded human-readable ABI**, not the 4-byte selector. Use `"0x"` when `data` is `"0x"`. For contract calls, the gateway needs the full signature to decode calldata into `decoded_function_arguments` for Rego. Using the 4-byte selector (`0xa9059cbb`) instead causes decoding to fail silently.

```typescript
// Correct: hex-encode the full human-readable signature
const functionSig = "transfer(address,uint256)";
const functionSigHex = `0x${Buffer.from(functionSig).toString("hex")}`;

// Correct for native ETH transfer or no calldata
const noCalldataFunctionSig = "0x";

// Wrong: using the 4-byte selector
const wrong = "0xa9059cbb";
```
:::

## Step 7: Run the Application

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) in your browser.

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| `GATEWAY_ERROR` | Invalid API key or network issue | Verify `NEWTON_API_KEY` and network connectivity |
| `InvalidAttestation` on-chain | Task Manager mismatch or expired attestation | Verify contract was initialized with correct Task Manager address |
| WebSocket connection failed | Wrong WS URL or Alchemy plan limits | Verify `SEPOLIA_ALCHEMY_WS_URL` format (`wss://...`) |
| `TIMEOUT` | Operator network slow or unavailable | Increase timeout or retry |

## Next Steps

<Card icon="code" to="/developers/reference/sdk-reference" title="SDK Reference">
  Full TypeScript SDK documentation
</Card>

<Card icon="plug" to="/developers/reference/rpc-api" title="RPC API">
  Underlying JSON-RPC methods
</Card>
