Skip to main content

Overview

This guide walks through the full Newton Protocol integration flow end-to-end. By the time you finish, you will have:
  1. Written a data oracle that provides external data to policies
  2. Created a policy that evaluates transaction intents against oracle data, written in Rego
  3. Deployed policy files to IPFS and registered them on-chain with newton-cli
  4. Deployed an Newton Policy Client smart wallet on Sepolia that uses the policy to gate transactions
  5. Built a Next.js application with the Newton SDK that submits transactions through your policy-gated wallet
Every transaction your wallet executes must first pass through Newton’s decentralized policy evaluation network. The network runs your oracle, feeds its output into your Rego policy, and produces a cryptographic attestation that your on-chain contract verifies before executing.

Prerequisites

Before you begin, make sure you have the following tools and accounts ready.
RequirementUse
Rust + CargoBuilding and running newton-cli
Node.js >= 20 + npmRunning @bytecodealliance/jco and the Next.js demo app
Foundry (forge, cast, anvil)Compiling and deploying Solidity contracts
newton-cli 0.1.31Uploading policy files, generating CIDs, deploying policies
Pinata accountIPFS pinning (you will need a JWT and a gateway URL)
Sepolia ETHGas fees on the Ethereum Sepolia testnet
Newton API keyAuthenticate SDK requests — email [email protected] to request one

Install tooling

# Rust (includes cargo)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Node.js (macOS — or use your preferred method)
brew install node

# Foundry (forge / cast / anvil)
curl -L https://foundry.paradigm.xyz | bash
foundryup

# newton-cli
cargo install [email protected]

# jco (WASM componentization)
npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js
After installing Rust, Foundry, or Node via Homebrew, restart your terminal (or run source ~/.zshrc) so the new binaries are on your PATH.

Step 1: Write and Build the Data Oracle

Data oracles are WebAssembly components that fetch or compute external data at evaluation time. The Newton network executes your WASM, and the output is fed into your Rego policy as data.data. Create a working directory for the policy workspace:
mkdir policy-workspace && cd policy-workspace
mkdir -p policy-files

1.1 Define the WIT interface

Create newton-provider.wit. This file declares the shape of the WASM component — what it imports (an HTTP fetch capability) and what it exports (a run function).
package newton:[email protected];

interface http {
    record http-request {
        url: string,
        method: string,
        headers: list<tuple<string, string>>,
        body: option<list<u8>>,
    }

    record http-response {
        status: u16,
        headers: list<tuple<string, string>>,
        body: list<u8>,
    }

    fetch: func(request: http-request) -> result<http-response, string>;
}

world newton-provider {
    import http;
    export run: func(input: string) -> result<string, string>;
}

1.2 Implement the oracle in JavaScript

Create policy.js. The exported run function receives a JSON string of arguments (wasm_args) and must return a JSON string. The skeleton below always returns { "success": true }. Replace the body with your own data-fetching logic.
export function run(/* wasm_args */) {
  /*
  // Example: parse args and call an external API
  const wasmArgs = JSON.parse(wasm_args);

  const response = httpFetch({
    url: `https://api.example.com/check?param=${wasmArgs.param}`,
    method: "GET",
    headers: [],
    body: null
  });

  const body = JSON.parse(
    new TextDecoder().decode(new Uint8Array(response.body))
  );
  */

  return JSON.stringify({
    success: true
  });
}

1.3 Build the WASM component

Use jco componentize to compile the JavaScript into a WASM component that conforms to the WIT interface:
jco componentize -w newton-provider.wit -o policy.wasm policy.js -d stdio random clocks http fetch-event
This produces policy.wasm in the current directory.
If you installed jco locally (without -g), run it via npx jco componentize ... instead.

1.4 (Optional) Simulate locally

You can test your WASM before deploying it:
export CHAIN_ID=11155111
newton-cli policy-data simulate --wasm-file policy.wasm --input-json '{}'
If your WASM expects arguments:
newton-cli policy-data simulate --wasm-file policy.wasm --input-json '{"param": "foo"}'
See the CLI Reference for the full list of newton-cli commands.

Step 2: Write Rego Policy and Metadata

Every Newton policy consists of a Rego file, a parameter schema, and two metadata descriptors. This step creates all four files.

2.1 Create policy.rego

The Rego policy evaluates the JSON output from your WASM oracle. The result of the data.data path is whatever your run function returned.
package your_policy
default allow := false

is_success = data.data.success

allow if {
    is_success
}
For a full reference on supported Rego syntax, operators, and built-in functions, see the Rego Syntax Guide.

2.2 Create params_schema.json

This JSON Schema controls which policy parameters end users are allowed to set when they call setPolicy. Leave it empty if your policy has no configurable parameters.
{
  "type": "object",
  "description": "",
  "properties": {}
}

2.3 Create policy_data_metadata.json

Metadata describing the WASM data oracle:
{
  "name": "Your Policy Wasm",
  "version": "0.0.1",
  "author": "",
  "link": "",
  "description": "What the WASM does"
}

2.4 Create policy_metadata.json

Metadata describing the policy itself:
{
  "name": "Your Policy",
  "version": "0.0.1",
  "author": "",
  "link": "",
  "description": "Your policy description here"
}

2.5 Organize into policy-files/

The newton-cli commands in the next step expect all five files in a single directory:
cp policy.wasm policy.rego params_schema.json policy_data_metadata.json policy_metadata.json policy-files/
Your policy-files/ directory should now contain:
  • policy.wasm
  • policy.rego
  • params_schema.json
  • policy_data_metadata.json
  • policy_metadata.json

Step 3: Upload to IPFS and Deploy Policy

3.1 Set up environment variables

Create a .env file (or .env.policy) with the values needed for policy deployment:
# .env
CHAIN_ID=11155111
PINATA_JWT=your_pinata_jwt
PINATA_GATEWAY=your_pinata_gateway
PRIVATE_KEY=0xYourDeploymentPK
RPC_URL=https://your-sepolia-rpc-url
Load the variables into your shell:
set -a
source .env
set +a

3.2 Generate CIDs

newton-cli policy-files generate-cids \
  --directory policy-files \
  --output policy_cids.json \
  --entrypoint "your_policy.allow"
The --entrypoint value must match your Rego package name combined with the rule name. For package your_policy with rule allow, the entrypoint is your_policy.allow.

3.3 Deploy Policy Data

This uploads your WASM oracle to IPFS and registers it on-chain:
newton-cli policy-data deploy --policy-cids policy_cids.json
The output will include a policy data address. Save it — you need it in the next command.
Policy data deployed successfully at address: 0xPolicy_Data_Address

3.4 Deploy Policy

This deploys the Rego policy on-chain, pointing it at the policy data you just deployed:
newton-cli policy deploy \
  --policy-cids policy_cids.json \
  --policy-data-address "0xPolicy_Data_Address"
Save the policy address. You will use it when deploying your Newton Policy Wallet in the next step.

Step 4: Deploy Policy Client Contract

This step deploys a smart contract wallet on Sepolia that requires Newton attestations before executing any transaction.

4.1 Create the Foundry project

mkdir newton-policy-wallet && cd newton-policy-wallet
forge init --no-git
git init
forge install newt-foundation/newton-contracts

4.2 Write the contract

The following is a sample smart wallet that uses a Newton policy. Create src/NewtonPolicyWallet.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {NewtonPolicyClient} from "newton-contracts/src/mixins/NewtonPolicyClient.sol";
import {INewtonProverTaskManager} from "newton-contracts/src/interfaces/INewtonProverTaskManager.sol";

contract NewtonPolicyWallet is NewtonPolicyClient {
    event Executed(address indexed to, uint256 value, bytes data, bytes32 taskId);
    error InvalidAttestation();
    error ExecutionFailed();

    constructor() {}

    function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
        // Support INewtonPolicyClient interface (expected by Newton Policy contract)
        // 0xdbdcaa9c is the interface ID expected by the deployed Policy contract
        return interfaceId == 0xdbdcaa9c || super.supportsInterface(interfaceId);
    }

    function initialize(
        address policyTaskManager,
        address policy,
        address owner
    ) external {
        _initNewtonPolicyClient(policyTaskManager, policy, owner);
    }

    // setPolicy is inherited from NewtonPolicyClient - no need to redefine!
    // Just call: wallet.setPolicy(INewtonPolicy.PolicyConfig({policyParams: "{}", expireAfter: 31536000}))

    function validateAndExecuteDirect(
        address to,
        uint256 value,
        bytes calldata data,
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        bytes calldata signatureData
    ) external returns (bytes memory) {
        require(_validateAttestationDirect(task, taskResponse, signatureData), InvalidAttestation());

        (bool success, bytes memory result) = to.call{value: value}(data);
        if (!success) revert ExecutionFailed();

        emit Executed(to, value, data, task.taskId);
        return result;
    }

    receive() external payable {}
}
Key design notes:
  • Inherits from NewtonPolicyClient, which handles policy registration with the Newton Policy contract.
  • Must override supportsInterface to include 0xdbdcaa9c (the interface ID expected by the deployed Newton Policy contract).
  • Uses an initialize() pattern instead of constructor arguments for proper policy client setup.
  • The setPolicy function is inherited from NewtonPolicyClient — no custom wrapper is needed.
  • Uses _validateAttestationDirect() for direct attestation verification (evaluates intent without waiting for on-chain task response confirmation).
  • Imports INewtonProverTaskManager for the Task and TaskResponse struct types used by the direct validation flow.

4.3 Configure foundry.toml

You must set via_ir = true. The newton-contracts library requires IR-based compilation due to stack depth. Without it, compilation will fail.
Replace the default foundry.toml with:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.27"
via_ir = true
optimizer = true
optimizer_runs = 200

remappings = [
    "newton-contracts/=lib/newton-contracts/",
    "forge-std/=lib/forge-std/src/"
]

fs_permissions = [{ access = "read", path = "./policy_params.json" }]

[rpc_endpoints]
sepolia = "${RPC_URL}"

4.4 Create the deployment script

Create script/Deploy.s.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Script, console} from "forge-std/Script.sol";
import {NewtonPolicyWallet} from "../src/NewtonPolicyWallet.sol";

contract DeployScript is Script {
    // Newton Task Manager on Sepolia (MUST match the SDK gateway's task manager)
    // BLS signatures are bound to this address - using the wrong one causes InvalidAttestation
    address constant NEWTON_TASK_MANAGER = 0xecb741F4875770f9A5F060cb30F6c9eb5966eD13;

    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        address policy = vm.envAddress("POLICY");
        address owner = vm.addr(deployerPrivateKey);  // Derive from private key

        vm.startBroadcast(deployerPrivateKey);

        // Deploy the wallet
        NewtonPolicyWallet wallet = new NewtonPolicyWallet();

        // Initialize with Newton Policy system
        wallet.initialize(NEWTON_TASK_MANAGER, policy, owner);

        console.log("NewtonPolicyWallet deployed at:", address(wallet));
        console.log("Initialized with:");
        console.log("  - Task Manager:", NEWTON_TASK_MANAGER);
        console.log("  - Policy:", policy);
        console.log("  - Owner:", owner);

        vm.stopBroadcast();
    }
}
The Task Manager address must be 0xecb741F4875770f9A5F060cb30F6c9eb5966eD13 on Sepolia. BLS signatures produced by the Newton network are bound to this specific address. Using any other task manager address will cause every attestation to fail with InvalidAttestation.

4.5 Create the set policy script

Create script/SetPolicy.s.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Script, console} from "forge-std/Script.sol";
import {NewtonPolicyWallet} from "../src/NewtonPolicyWallet.sol";
import {INewtonPolicy} from "newton-contracts/src/interfaces/INewtonPolicy.sol";

contract SetPolicyScript is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        address walletAddress = vm.envAddress("WALLET_ADDRESS");
        uint32 expireAfter = uint32(vm.envUint("EXPIRE_AFTER"));

        // Read policy params from file (empty JSON object by default)
        string memory paramsJson = vm.readFile("policy_params.json");
        bytes memory policyParams = bytes(paramsJson);

        vm.startBroadcast(deployerPrivateKey);

        NewtonPolicyWallet wallet = NewtonPolicyWallet(payable(walletAddress));

        // Call the inherited setPolicy function with PolicyConfig struct
        bytes32 newPolicyId = wallet.setPolicy(
            INewtonPolicy.PolicyConfig({
                policyParams: policyParams,
                expireAfter: expireAfter
            })
        );

        console.log("Policy set with ID:");
        console.logBytes32(newPolicyId);

        vm.stopBroadcast();
    }
}

4.6 Deploy the wallet

Create a .env file in the newton-policy-wallet directory:
PRIVATE_KEY=0xYourWalletDeployerPrivateKey
POLICY=<paste Policy address from Step 3.4>
RPC_URL=https://your-sepolia-rpc-url
The wallet owner is automatically derived from the PRIVATE_KEY, so you do not need a separate OWNER variable.
Deploy:
source .env
forge script script/Deploy.s.sol:DeployScript --rpc-url $RPC_URL --broadcast
Save the deployed wallet address from the console output. You will use it in Step 5.

4.7 Set the policy on the wallet

Create policy_params.json in the project root:
{}
Add these variables to your .env:
WALLET_ADDRESS=0xYourDeployedWalletAddress
EXPIRE_AFTER=31536000
Run the set policy script:
source .env
forge script script/SetPolicy.s.sol:SetPolicyScript --rpc-url $RPC_URL --broadcast
After this step:
  • Newton can generate attestations for your NewtonPolicyWallet.
  • The wallet will only execute transactions that carry a valid attestation.

Step 5: SDK Integration

Build a Next.js application that uses the Newton SDK to evaluate transaction intents against your policy and then execute them through your deployed wallet.

5.1 Create the project and install dependencies

npx create-next-app@latest newton-sdk-app --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd newton-sdk-app
npm install @magicnewton/[email protected] viem

5.2 Environment variables

Create .env.local in the newton-sdk-app directory:
# 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 (email [email protected] to request one)
NEXT_PUBLIC_NEWTON_API_KEY=your_newton_api_key

# Newton Policy Contract address (fixed on Sepolia)
# Note: This is the Newton Policy contract, NOT your wallet address
NEXT_PUBLIC_POLICY_CONTRACT_ADDRESS=0x698C687f86Bc2206AC7C06eA68AC513A2949abA6

# YOUR deployed wallet address from Step 4.6
NEXT_PUBLIC_POLICY_WALLET_ADDRESS=<paste wallet address from Step 4.6>

# Signer private key for client-side signing (same as PRIVATE_KEY from Step 4)
NEXT_PUBLIC_SIGNER_PRIVATE_KEY=<same as PRIVATE_KEY from Step 4>
NEXT_PUBLIC_POLICY_CONTRACT_ADDRESS is the Newton Policy contract (fixed address on Sepolia: 0x698C687f86Bc2206AC7C06eA68AC513A2949abA6). NEXT_PUBLIC_POLICY_WALLET_ADDRESS is YOUR deployed wallet contract. These are different contracts. The Policy contract manages policy registration; your wallet is a client of it.

5.3 Create the configuration file

Create src/const/config.ts:
import { Hex } from "viem";

// Environment variables
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!;
export const NEWTON_API_KEY = process.env.NEXT_PUBLIC_NEWTON_API_KEY!;
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;
export const SIGNER_PRIVATE_KEY = process.env.NEXT_PUBLIC_SIGNER_PRIVATE_KEY as Hex;

5.4 Create the ABI file

Create src/lib/abi.ts. This ABI covers the functions you will call from the frontend. The full compiled ABI will include additional inherited functions like policyClientOwner() and policyId().
export const newtonPolicyWalletAbi = [
  {
    type: "constructor",
    inputs: [],
    stateMutability: "nonpayable",
  },
  {
    type: "function",
    name: "initialize",
    inputs: [
      { name: "policyTaskManager", type: "address", internalType: "address" },
      { name: "policy", type: "address", internalType: "address" },
      { name: "owner", type: "address", internalType: "address" },
    ],
    outputs: [],
    stateMutability: "nonpayable",
  },
  {
    type: "function",
    name: "validateAndExecuteDirect",
    inputs: [
      { name: "to", type: "address", internalType: "address" },
      { name: "value", type: "uint256", internalType: "uint256" },
      { name: "data", type: "bytes", internalType: "bytes" },
      {
        name: "task",
        type: "tuple",
        internalType: "struct INewtonProverTaskManager.Task",
        components: [
          { name: "taskId", type: "bytes32", internalType: "bytes32" },
          { name: "policyClient", type: "address", internalType: "address" },
          { name: "taskCreatedBlock", type: "uint32", internalType: "uint32" },
          { name: "quorumThresholdPercentage", type: "uint32", internalType: "uint32" },
          {
            name: "intent",
            type: "tuple",
            internalType: "struct NewtonMessage.Intent",
            components: [
              { name: "from", type: "address", internalType: "address" },
              { name: "to", type: "address", internalType: "address" },
              { name: "value", type: "uint256", internalType: "uint256" },
              { name: "data", type: "bytes", internalType: "bytes" },
              { name: "chainId", type: "uint256", internalType: "uint256" },
              { name: "functionSignature", type: "bytes", internalType: "bytes" },
            ],
          },
          { name: "intentSignature", type: "bytes", internalType: "bytes" },
          { name: "wasmArgs", type: "bytes", internalType: "bytes" },
          { name: "quorumNumbers", type: "bytes", internalType: "bytes" },
        ],
      },
      {
        name: "taskResponse",
        type: "tuple",
        internalType: "struct INewtonProverTaskManager.TaskResponse",
        components: [
          { name: "taskId", type: "bytes32", internalType: "bytes32" },
          { name: "policyClient", type: "address", internalType: "address" },
          { name: "policyId", type: "bytes32", internalType: "bytes32" },
          { name: "policyAddress", type: "address", internalType: "address" },
          {
            name: "intent",
            type: "tuple",
            internalType: "struct NewtonMessage.Intent",
            components: [
              { name: "from", type: "address", internalType: "address" },
              { name: "to", type: "address", internalType: "address" },
              { name: "value", type: "uint256", internalType: "uint256" },
              { name: "data", type: "bytes", internalType: "bytes" },
              { name: "chainId", type: "uint256", internalType: "uint256" },
              { name: "functionSignature", type: "bytes", internalType: "bytes" },
            ],
          },
          { name: "intentSignature", type: "bytes", internalType: "bytes" },
          { name: "evaluationResult", type: "bytes", internalType: "bytes" },
          {
            name: "policyTaskData",
            type: "tuple",
            internalType: "struct NewtonMessage.PolicyTaskData",
            components: [
              { name: "policyId", type: "bytes32", internalType: "bytes32" },
              { name: "policyAddress", type: "address", internalType: "address" },
              { name: "policy", type: "bytes", internalType: "bytes" },
              {
                name: "policyData",
                type: "tuple[]",
                internalType: "struct NewtonMessage.PolicyData[]",
                components: [
                  { name: "wasmArgs", type: "bytes", internalType: "bytes" },
                  { name: "data", type: "bytes", internalType: "bytes" },
                  { name: "attestation", type: "bytes", internalType: "bytes" },
                  { name: "policyDataAddress", type: "address", internalType: "address" },
                  { name: "expireBlock", type: "uint32", internalType: "uint32" },
                ],
              },
            ],
          },
          {
            name: "policyConfig",
            type: "tuple",
            internalType: "struct INewtonPolicy.PolicyConfig",
            components: [
              { name: "policyParams", type: "bytes", internalType: "bytes" },
              { name: "expireAfter", type: "uint32", internalType: "uint32" },
            ],
          },
        ],
      },
      { name: "signatureData", type: "bytes", internalType: "bytes" },
    ],
    outputs: [{ name: "", type: "bytes", internalType: "bytes" }],
    stateMutability: "nonpayable",
  },
  {
    type: "function",
    name: "setPolicy",
    inputs: [
      {
        name: "policyConfig",
        type: "tuple",
        internalType: "struct INewtonPolicy.PolicyConfig",
        components: [
          { name: "policyParams", type: "bytes", internalType: "bytes" },
          { name: "expireAfter", type: "uint32", internalType: "uint32" },
        ],
      },
    ],
    outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
    stateMutability: "nonpayable",
  },
  {
    type: "event",
    name: "Executed",
    inputs: [
      { name: "to", type: "address", indexed: true, internalType: "address" },
      { name: "value", type: "uint256", indexed: false, internalType: "uint256" },
      { name: "data", type: "bytes", indexed: false, internalType: "bytes" },
      { name: "taskId", type: "bytes32", indexed: false, internalType: "bytes32" },
    ],
    anonymous: false,
  },
  { type: "error", name: "InvalidAttestation", inputs: [] },
  { type: "error", name: "ExecutionFailed", inputs: [] },
  { type: "receive", stateMutability: "payable" },
] as const;

5.5 Create the evaluation request helper

Create src/lib/evaluation-request.ts:
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;
  wasmArgs: Record<string, unknown>;
};

export const createEvaluationRequest = ({
  signerAddress,
  targetAddress,
  value,
  data,
  wasmArgs,
}: EvaluationRequestParams) => {
  // Function signature for validateAndExecuteDirect
  const functionSignature = stringToHexBytes(
    "validateAndExecuteDirect(address,uint256,bytes,(bytes32,address,uint32,uint32,(address,address,uint256,bytes,uint256,bytes),bytes,bytes,bytes),(bytes32,address,bytes32,address,(address,address,uint256,bytes,uint256,bytes),bytes,bytes,(bytes32,address,bytes,(bytes,bytes,bytes,address,uint32)[]),(bytes,uint32)),bytes)"
  );

  return {
    policyClient: POLICY_WALLET_ADDRESS,
    intent: {
      from: signerAddress,
      to: targetAddress,
      value: `0x${value.toString(16)}` as Hex,
      data: data,
      chainId: sepolia.id,
      functionSignature: functionSignature,
    },
    wasmArgs: stringToHexBytes(JSON.stringify(wasmArgs)),
    timeout: 60,
  };
};

5.6 Create the transaction execution helper

Create src/lib/execute-with-attestation.ts:
import { createPublicClient, encodeFunctionData, Hex, http } from "viem";
import { sepolia } from "viem/chains";
import { SEPOLIA_ALCHEMY_URL, POLICY_WALLET_ADDRESS } from "@/const/config";
import { newtonPolicyWalletAbi } from "./abi";

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

export type ExecuteDirectParams = {
  to: Hex;
  value: bigint;
  data: Hex;
  task: any;
  taskResponse: any;
  signatureData: Hex;
  signerAddress: Hex;
  walletClient: {
    signTransaction: (tx: {
      to: Hex;
      data: Hex;
      nonce: number;
      gas: bigint;
      gasPrice: bigint;
    }) => Promise<Hex>;
  };
};

export const executeWithAttestationDirect = async ({
  to,
  value,
  data,
  task,
  taskResponse,
  signatureData,
  signerAddress,
  walletClient,
}: ExecuteDirectParams): Promise<Hex> => {
  const functionData = encodeFunctionData({
    abi: newtonPolicyWalletAbi,
    functionName: "validateAndExecuteDirect",
    args: [to, value, data, task, taskResponse, signatureData],
  });

  const [nonce, gas, baseGasPrice] = await Promise.all([
    publicClient.getTransactionCount({ address: signerAddress }),
    publicClient.estimateGas({
      to: POLICY_WALLET_ADDRESS,
      data: functionData,
      account: signerAddress,
    }),
    publicClient.getGasPrice(),
  ]);

  // Bump gas price by 20% to ensure replacement transactions are accepted
  const gasPrice = (baseGasPrice * BigInt(120)) / BigInt(100);

  const signedTx = await walletClient.signTransaction({
    to: POLICY_WALLET_ADDRESS,
    data: functionData,
    nonce,
    gas,
    gasPrice,
  });

  const txHash = await publicClient.sendRawTransaction({ serializedTransaction: signedTx });

  return txHash;
};

5.7 Create the Newton client hook

Create src/lib/use-newton-client.ts:
"use client";

import { useMemo } from "react";
import { createWalletClient, webSocket, Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { newtonWalletClientActions } from "@magicnewton/newton-protocol-sdk";
import { SEPOLIA_ALCHEMY_WS_URL, NEWTON_API_KEY } from "@/const/config";

export const useNewtonClient = (privateKey: Hex) => {
  const client = useMemo(() => {
    const account = privateKeyToAccount(privateKey);

    // Initialize the wallet client with Newton SDK actions
    // The apiKey is required for SDK initialization
    const walletClient = createWalletClient({
      account,
      chain: sepolia,
      transport: webSocket(SEPOLIA_ALCHEMY_WS_URL),
    }).extend(newtonWalletClientActions({ apiKey: NEWTON_API_KEY }));

    return {
      walletClient,
      account,
      signer: account,
    };
  }, [privateKey]);

  return client;
};

5.8 Create the main page

Replace src/app/page.tsx with the complete evaluation and execution flow:
"use client";

import { useState } from "react";
import { Hex } from "viem";
import { createWalletClient, webSocket } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { newtonWalletClientActions } from "@magicnewton/newton-protocol-sdk";
import {
  SEPOLIA_ALCHEMY_WS_URL,
  NEWTON_API_KEY,
  POLICY_WALLET_ADDRESS,
  SIGNER_PRIVATE_KEY,
} from "@/const/config";
import { createEvaluationRequest } from "@/lib/evaluation-request";
import { executeWithAttestationDirect } from "@/lib/execute-with-attestation";

type Status = "idle" | "evaluating" | "executing" | "success" | "error";

export default function Home() {
  const [targetAddress, setTargetAddress] = useState<string>("0x31386C6a234AbF509579bDBA4854e9925fac1Ffa");
  const [value, setValue] = useState<string>("0");
  const [data, setData] = useState<string>("0x");
  const [wasmArgs, setWasmArgs] = useState<string>("{}");
  const [status, setStatus] = useState<Status>("idle");
  const [taskId, setTaskId] = useState<string>("");
  const [evaluationResult, setEvaluationResult] = useState<boolean | null>(null);
  const [txHash, setTxHash] = useState<string>("");
  const [error, setError] = useState<string>("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setStatus("evaluating");
    setError("");
    setTaskId("");
    setEvaluationResult(null);
    setTxHash("");

    try {
      // Create the wallet client with Newton SDK
      // Initialize with apiKey for SDK authentication
      const account = privateKeyToAccount(SIGNER_PRIVATE_KEY);
      const walletClient = createWalletClient({
        account,
        chain: sepolia,
        transport: webSocket(SEPOLIA_ALCHEMY_WS_URL),
      }).extend(newtonWalletClientActions({ apiKey: NEWTON_API_KEY }));

      // Create the evaluation request
      const evalRequest = createEvaluationRequest({
        signerAddress: account.address,
        targetAddress: targetAddress as Hex,
        value: BigInt(value),
        data: data as Hex,
        wasmArgs: JSON.parse(wasmArgs),
      });

      // Evaluate intent directly (no on-chain task submission wait)
      const evalResponse = await walletClient.evaluateIntentDirect(evalRequest);
      const { evaluationResult: allowed, task, taskResponse: evalTaskResponse, blsSignature } = evalResponse.result;
      setTaskId(task.taskId);
      setEvaluationResult(allowed);

      if (!allowed) {
        setStatus("error");
        setError("Policy evaluation failed - transaction blocked");
        return;
      }

      // Execute the transaction with the direct attestation
      setStatus("executing");
      const hash = await executeWithAttestationDirect({
        to: targetAddress as Hex,
        value: BigInt(value),
        data: data as Hex,
        task,
        taskResponse: evalTaskResponse,
        signatureData: blsSignature,
        signerAddress: account.address,
        walletClient,
      });

      setTxHash(hash);
      setStatus("success");
    } catch (err) {
      setStatus("error");
      setError(err instanceof Error ? err.message : "An unknown error occurred");
    }
  };

  return (
    <main className="min-h-screen p-8 max-w-2xl mx-auto">
      <h1 className="text-3xl font-bold mb-2">Newton Policy Wallet Demo</h1>
      <p className="text-gray-600 mb-8">
        Execute transactions through your Newton Policy Wallet with attestation verification.
      </p>

      <div className="mb-6 p-4 bg-gray-100 rounded-lg">
        <p className="text-sm">
          <strong>Policy Wallet:</strong>{" "}
          <code className="bg-gray-200 px-2 py-1 rounded">{POLICY_WALLET_ADDRESS}</code>
        </p>
      </div>

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="block text-sm font-medium mb-1">Target Address</label>
          <input
            type="text"
            value={targetAddress}
            onChange={(e) => setTargetAddress(e.target.value)}
            placeholder="0x..."
            className="w-full p-2 border rounded"
            required
          />
        </div>

        <div>
          <label className="block text-sm font-medium mb-1">Value (wei)</label>
          <input
            type="text"
            value={value}
            onChange={(e) => setValue(e.target.value)}
            placeholder="0"
            className="w-full p-2 border rounded"
          />
        </div>

        <div>
          <label className="block text-sm font-medium mb-1">Data (hex)</label>
          <input
            type="text"
            value={data}
            onChange={(e) => setData(e.target.value)}
            placeholder="0x"
            className="w-full p-2 border rounded"
          />
        </div>

        <div>
          <label className="block text-sm font-medium mb-1">WASM Args (JSON)</label>
          <textarea
            value={wasmArgs}
            onChange={(e) => setWasmArgs(e.target.value)}
            placeholder="{}"
            className="w-full p-2 border rounded font-mono text-sm"
            rows={3}
          />
          <p className="text-xs text-gray-500 mt-1">
            Arguments passed to your policy WASM component
          </p>
        </div>

        <button
          type="submit"
          disabled={status === "evaluating" || status === "executing"}
          className="w-full py-3 bg-blue-600 text-white rounded font-medium hover:bg-blue-700 disabled:bg-gray-400"
        >
          {status === "evaluating"
            ? "Evaluating Policy..."
            : status === "executing"
            ? "Executing Transaction..."
            : "Submit Transaction"}
        </button>
      </form>

      {/* Status Display */}
      {status !== "idle" && (
        <div className="mt-8 space-y-4">
          {taskId && (
            <div className="p-4 bg-gray-100 rounded">
              <p className="text-sm font-medium">Task ID:</p>
              <code className="text-xs break-all">{taskId}</code>
            </div>
          )}

          {evaluationResult !== null && (
            <div
              className={`p-4 rounded ${
                evaluationResult ? "bg-green-100" : "bg-red-100"
              }`}
            >
              <p className="font-medium">
                Evaluation Result:{" "}
                <span className={evaluationResult ? "text-green-700" : "text-red-700"}>
                  {evaluationResult ? "Allowed" : "Blocked"}
                </span>
              </p>
            </div>
          )}

          {txHash && (
            <div className="p-4 bg-green-100 rounded">
              <p className="font-medium text-green-700">Transaction Successful!</p>
              <p className="text-sm mt-2">
                <a
                  href={`https://sepolia.etherscan.io/tx/${txHash}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="text-blue-600 hover:underline break-all"
                >
                  View on Etherscan: {txHash}
                </a>
              </p>
            </div>
          )}

          {error && (
            <div className="p-4 bg-red-100 rounded">
              <p className="font-medium text-red-700">Error</p>
              <p className="text-sm text-red-600 mt-1">{error}</p>
            </div>
          )}
        </div>
      )}
    </main>
  );
}

5.9 Run the application

npm run dev
Open http://localhost:3000 in your browser.

Step 6: Verification

Verify that your end-to-end setup works correctly.
1

Run the app

Start the Next.js dev server with npm run dev inside the newton-sdk-app directory and open http://localhost:3000.
2

Submit an evaluation request

Fill in the form fields:
  • Target Address — any valid Ethereum address for testing
  • Value0 (or a small amount if sending ETH, making sure the wallet contract is funded)
  • Data0x (or encoded calldata for the target)
  • WASM Args{} (or JSON matching your oracle’s expected input)
Click Submit Transaction.
3

Observe the flow

Watch the UI update through each stage:
  1. Task ID appears — the Newton network has received your evaluation request
  2. Evaluation Result — shows “Allowed” or “Blocked” based on your policy logic.
  3. Transaction execution — if allowed, the transaction is signed and submitted with the attestation
  4. Transaction hash — a link to view the confirmed transaction on Sepolia Etherscan
4

Verify the task on Newton Explorer

Copy the Task ID from the UI and look it up on the Newton Explorer. Confirm that the task status shows as completed and the evaluation result matches what the UI displayed.
5

Verify on Sepolia Etherscan

Click the Etherscan link and confirm:
  • The transaction was sent to your Newton Policy Wallet address
  • The Executed event appears in the transaction logs
  • The taskId in the event matches the one shown in the UI
6

Test policy rejection

Modify your WASM args (or change your policy logic) to trigger a failure. Submit the transaction and verify:
  • The evaluation returns “Blocked”
  • No transaction is submitted to the chain

Troubleshooting

”command not found” after installing tools

Restart your shell after installing Rust (rustup), Node via Homebrew, or Foundry. Alternatively, re-source your shell configuration:
source ~/.zshrc
# or
source ~/.bashrc

jco componentize fails

If you installed @bytecodealliance/jco locally (without -g), you must run it via npx:
npx jco componentize -w newton-provider.wit -o policy.wasm policy.js -d stdio random clocks http fetch-event
You also need @bytecodealliance/componentize-js as a peer dependency. Install both together:
npm install --save-dev @bytecodealliance/jco @bytecodealliance/componentize-js

WebSocket connection fails

Make sure your NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL uses the wss:// protocol, not https://. The Newton SDK requires a WebSocket transport for the wallet client.
# Correct
NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL=wss://eth-sepolia.g.alchemy.com/v2/YOUR_KEY

# Wrong - will fail
NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY

Invalid attestation error (0xbd8ba84d)

This is the InvalidAttestation() revert. The most common causes are:
  1. Wrong Task Manager address (most common): The wallet was initialized with a task manager address that does not match the one the Newton SDK gateway signs against. The wallet must use 0xecb741F4875770f9A5F060cb30F6c9eb5966eD13 on Sepolia. BLS signatures are cryptographically bound to this specific address — using any other address will always fail.
  2. Intent parameter mismatch: The to, value, data, or chainId fields in the evaluation request do not match what was signed.
  3. Policy ID mismatch: The policyId does not match the wallet’s configured policy.
  4. Incorrect struct passthrough: The task or taskResponse structs from evaluateIntentDirect were not forwarded correctly to validateAndExecuteDirect.

ExecutionFailed error (0xacfdb444)

This means the attestation passed (the policy allowed the transaction) but the inner to.call{value: value}(data) reverted. Common causes:
  • Wallet contract has no ETH: If you are sending value > 0, the wallet contract itself needs ETH. Fund the wallet contract address directly — it is not enough for only the signer EOA to have ETH.
  • Target contract reverted: The target’s own logic rejected the call.
  • Malformed calldata: The data field does not match the target function’s expected ABI encoding.
You can decode the revert reason with cast:
cast call <WALLET_ADDRESS> "0x<calldata>" --from <SIGNER> --rpc-url <RPC_URL>
Match error selectors: cast sig "ExecutionFailed()" returns 0xacfdb444, cast sig "InvalidAttestation()" returns 0xbd8ba84d.

Environment variables not loading

For Next.js:
  • Client-side variables must be prefixed with NEXT_PUBLIC_.
  • Server-side-only variables should not have this prefix.
  • You must restart the dev server after modifying .env.local.

Gas estimation fails

The estimateGas call simulates the transaction on-chain. If it reverts, the viem error message is often unhelpful (“execution reverted for an unknown reason”). To debug:
  1. Extract the calldata from the error message.
  2. Run cast call <wallet> "0x<calldata>" --from <signer> --rpc-url <rpc> to see the revert selector.
  3. Match the selector: 0xacfdb444 = ExecutionFailed(), 0xbd8ba84d = InvalidAttestation().
Common causes:
  • The signer does not have sufficient Sepolia ETH for gas.
  • The wallet contract has no ETH but value > 0 is being sent.
  • Wrong task manager address (see “Invalid attestation” above).
  • The attestation has expired.

Policy evaluation times out

  • Check that your policy WASM builds correctly by running the local simulation.
  • Verify that wasmArgs are in the format your WASM expects.
  • Increase the timeout value in the evaluation request if needed.
  • If your WASM calls external APIs, make sure those APIs are responding.

Foundry deployment fails

  • Verify PRIVATE_KEY is prefixed with 0x.
  • Ensure the deployer wallet has sufficient Sepolia ETH.
  • Check that RPC_URL is valid and accessible.
  • Run forge build first to catch compilation errors before deploying.
  • If forge init --no-commit fails, use forge init --no-git instead (the flag name varies by Foundry version).
  • forge install requires a git repository. Run git init first if you used --no-git.
  • Compilation requires via_ir = true in foundry.toml due to the stack depth of newton-contracts. Without it, you will get a “stack too deep” error.

Additional Resources: Factory Pattern

If your application needs to deploy a separate policy client per user (for example, per-user trading vaults or guarded accounts), use the factory pattern with OpenZeppelin’s Clones library. This deploys minimal proxy contracts that are much cheaper than full contract deployments.

Factory Solidity contracts

Policy client implementation (used as the clone template):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {NewtonPolicyClient} from "newton-contracts/src/mixins/NewtonPolicyClient.sol";
import {INewtonPolicy} from "newton-contracts/src/interfaces/INewtonPolicy.sol";
import {INewtonProverTaskManager} from "newton-contracts/src/interfaces/INewtonProverTaskManager.sol";

contract YourPolicyClient is NewtonPolicyClient {
    event Executed(address indexed to, uint256 value, bytes data, bytes32 taskId);
    error InvalidAttestation();
    error ExecutionFailed();

    constructor() {}

    function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
        return interfaceId == 0xdbdcaa9c || super.supportsInterface(interfaceId);
    }

    function initialize(
        address policyTaskManager,
        address policy,
        address owner
    ) external {
        _initNewtonPolicyClient(policyTaskManager, policy, owner);
    }

    function validateAndExecuteDirect(
        address to,
        uint256 value,
        bytes calldata data,
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        bytes calldata signatureData
    ) external returns (bytes memory) {
        require(_validateAttestationDirect(task, taskResponse, signatureData), InvalidAttestation());

        (bool success, bytes memory result) = to.call{value: value}(data);
        if (!success) revert ExecutionFailed();

        emit Executed(to, value, data, task.taskId);
        return result;
    }

    receive() external payable {}
}
Factory contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {YourPolicyClient} from "./YourPolicyClient.sol";
import {INewtonPolicy} from "newton-contracts/src/interfaces/INewtonPolicy.sol";

contract NewtonClientFactory {
    address public immutable taskManager;
    address public immutable policy;
    YourPolicyClient public immutable clientImpl;

    event ClientCreated(address indexed owner, address client);

    constructor(YourPolicyClient _impl, address _taskManager, address _policy) {
        clientImpl = _impl;
        taskManager = _taskManager;
        policy = _policy;
    }

    function createClient(INewtonPolicy.PolicyConfig memory config)
        external
        returns (YourPolicyClient client)
    {
        client = YourPolicyClient(payable(Clones.clone(address(clientImpl))));
        client.initialize(taskManager, policy, msg.sender);
        client.setPolicy(config);
        emit ClientCreated(msg.sender, address(client));
    }
}

Frontend example

import { createWalletClient, encodeFunctionData } from "viem";
import { newtonWalletClientActions } from "@magicnewton/newton-protocol-sdk";

const signer = Signer(); // e.g. from wagmi or another wallet library

const walletClient = createWalletClient({
  chain: sepolia,
  transport: custom(window.ethereum),
  account: signer,
});

const policyParamsJson = { spendLimitUsd: 1_000, allow: ["0xRouter"] };
const policyParamsHexEncoded = toHex(JSON.stringify(policyParamsJson));
const expireAfter = 7200;

const policyConfig = {
  policyParams: policyParamsHexEncoded,
  expireAfter,
};

const factoryAbi = [/* ... factory ABI ... */];
const factoryAddress = "0x...";

const hash = await walletClient.sendTransaction({
  data: encodeFunctionData({
    abi: factoryAbi,
    functionName: "createClient",
    args: [policyConfig],
  }),
  to: factoryAddress,
});
Each user gets their own policy client contract, and you can set per-user policy parameters (spend limits, allowed targets, etc.) at creation time.

Next Steps

  • Rego Syntax Guide — Full reference for supported Rego syntax, operators, and built-in functions.
  • SDK Reference — Complete API documentation for @magicnewton/newton-protocol-sdk.
  • CLI Reference — All newton-cli commands and options.