Skip to main content
Before getting started, ensure that your EOA address is allowlisted with Newton Protocol. Please reach out to [email protected] to learn more.

Deploy a policy template

The first step in using Newton Protocol is deploying a policy template to the protocol.
A policy template is a generic policy that can be customized by an end-user with their own parameters.
Policy templates consist of a combination of a Rego policy along with a WASM binary for calling data offchain.
Note: This step is optional — you may choose to leverage an existing policy template that’s already deployed.
See this example.
  1. Write a policy template in Rego. See this guide for what’s supported.
  2. Use the CLI tool to deploy your policy template:
    https://github.com/newt-foundation/developer-policy-cli
  3. At this point, a policy template address and IPFS hash will be returned.

Deploy a policy client

The next step in integration is using the policy template as part of a policy client, a Solidity contract. The policy client represents the consumer of the policy proofs — where transactions will be gated by Newton Protocol. Write a Solidity contract to extend NewtonPolicyClient.
Initialize for Newton Protocol with the policy template address you want to use (this can be in the constructor).
Steps:
  1. Call _initNewtonPolicyClient(policyTaskManager, policy, policyClientOwner); from NewtonPolicyClient in your implementation.
  2. Update your function to place the policy as a guard.
    For example, change
    function doPolicyGuaradedAction(address, uint256) external
    
    to
    function doPolicyGuaradedAction(NewtonMessage.Attestation attestation) external
    
    Inside this method, add your business logic.
  3. Deploy the contract.
  4. Call initialize with your policy address and owner.
    The proverTaskManager value is 0xae08eb5bb2d2debfa248ec6ec0355e6643791a91.
  5. Call setParameters with configuration parameters for the policy (via Etherscan or a client app). Note that this step is mandatory, even if the policy doesn’t require any parameters.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

// Find newton contracts here: https://github.com/newt-foundation/newton-contracts
// or forge install newt-foundation/newton-contracts
import {NewtonPolicyClient} from "newton-contracts/src/mixins/NewtonPolicyClient.sol";
import {INewtonPolicy} from "newton-contracts/src/interfaces/INewtonPolicy.sol";
import {NewtonMessage} from "newton-contracts/src/core/NewtonMessage.sol";

contract YourPolicyClient is NewtonPolicyClient {
    event Success();
    error InvalidAttestation();

    constructor() {}

    function initialize(
        address proverTaskManager,
        address policy, // refers to the policy template address
        address owner
    ) external {
        _initNewtonPolicyClient(policyTaskManager, policy, owner);
    }
	
	//User customization of the policy is set here
    function setParameters(INewtonPolicy.PolicyConfig memory _config) external {
        _setPolicy(_config);
    }
	
	//This method will validate the Newton proof and gate execution
    function validateAndExecuteIntent(NewtonMessage.Attestation memory attestation) external {
        require(_validateAttestation(attestation), InvalidAttestation());
        emit Success();
    }
}

Task submission

Before getting started with this step, ensure that your EOA address is allowlisted with Newton Protocol. The EOA signer is represented by the variable signer in the snippet below. Please reach out to [email protected] to get the wallet allowlisted.
Once a policy is configured by an end-user, a task can be submitted.
Here, an intent representing a transaction is constructed and sent to Newton Protocol along with the corresponding policy client address.
This portion of the SDK should be executed server-side (since it requires a private key to sign messages).
The EOA used must be allowlisted.
Steps:
  1. Install the Newton Protocol SDK
    @magicnewton/newton-protocol-sdk
  2. Sign up with Alchemy (or another RPC provider) for a WebSocket URL.
  3. Implement the SDK per the below example.
// Code to be run server side
import { createWalletClient } from 'newton-sdk'

const signer = await getSigner(); // signer for agent, loaded with an approved private key

const agentNewtonWalletClient = createWalletClient({
  chain: sepolia,
  transport: websocket('wss://alchemyURL?apikey'),
  account: signer
}).extend(newtonWalletClientActions);

//transaction intent
const intent = {
  from: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
  to: "0xb1ad5f82407bc0f19f42b2614fb9083035a36b69",
  value: "0x0",
  data: "0xdeadbeef",
  chainId: "0xaa36a7",
  functionSignature: stringToHex("transfer(address,uint256)")
};

//This code submits an intent to be evaluated
const pending: PendingTaskBuilder = await agentNewtonWalletClient.submitEvaluationRequest({
  policyClient: policyClientAddress,
  intent,
  timeout: 60
});

//A task ID can be queried in the newton explorer
const taskID = pending.taskId;

// This line of code provides the result of the task once its complete
const response = await pending.waitForTaskResponse({ timeoutMs: 20_000 });

// Check evaluation result. It will either be compliant or non-compliant
const evaluationResult = response.taskResponse.evaluationResult;

// Get attestation proof. This is used to submit a transaction
const attestation: Attestation = response.attestation;
Note:
In order to generate the functionSignature and data fields, you can refer to the sample code below.
// functionSignature

const functionSignature = stringToHexBytes(
  'function createTokenAndManager(uint256,uint256,address,(bytes,uint32))'
);

function stringToHexBytes(str) {
  // Convert string to UTF-8 bytes using TextEncoder
  const encoder = new TextEncoder();
  const bytes = encoder.encode(str);

  // Convert bytes to hex string without '0x' prefix
  return Array.from(bytes)
    .map(byte => byte.toString(16).padStart(2, '0'))
    .join('');
}

// intentData
// Calculate the function selector for createTokenAndManager
const functionSelector = ethers.id(functionSignature).slice(0, 10); // Get first 4 bytes (0x + 8 hex chars)

// Encode the intent data (baseFee, slope, rewardToken, config)
const encodedParams = ethers.AbiCoder.defaultAbiCoder().encode(
  ['uint256', 'uint256', 'address', 'tuple(bytes,uint32)'],
  [BASE_FEE, SLOPE, REWARD_TOKEN_ADDRESS, [policyConfig.policyParams, policyConfig.expireAfter]]
);

// Combine function selector with encoded parameters (this is the full calldata)
const intentDataHex = functionSelector + encodedParams.slice(2); // Remove 0x from encodedParams before concatenating

Transaction submission

Finally, the proof from the previous step can be used in the policy client to perform a transaction.
//This code submits a transaction to the policy client, calling the method validateAndExecuteIntent with the proof from the previous step 
newtonWalletClient.writeContract({ 
  address: policyClientAddress,
  abi,
  functionName: 'validateAndExecuteIntent',
  args: [attestation]
})

Additional resources

If your app requires multiple users to have their own policy clients (e.g. trading vaults or guarded accounts), you can use the factory pattern to deploy a smart contract per user. Solidity example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {NewtonPolicyClient} from "../../mixins/NewtonPolicyClient.sol";
import {INewtonPolicy} from "../../interfaces/INewtonPolicy.sol";
import {NewtonMessage} from "../../core/NewtonMessage.sol";

contract YourPolicyClient is NewtonPolicyClient {
    constructor(
        address proverTaskManager,
        address policy,
        address policyClientOwner
    ) {
        _initNewtonPolicyClient(policyTaskManager, policy, policyClientOwner);
    }

    function setParameters(INewtonPolicy.PolicyConfig memory _config) external {
        _setPolicy(_config);
    }

    function swap(NewtonMessage.Attestation attestation) external {
        require(_validateAttestation(attestation), "Not authorized");
        // Business logic here
    }
}
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 "../../interfaces/INewtonPolicy.sol";

contract NewtonClientFactory {
    YourPolicyClient public immutable clientImpl;

    constructor(YourPolicyClient _impl) {
        clientImpl = _impl;
    }

    function createClient(INewtonPolicy.PolicyConfig memory config)
        external
        returns (YourPolicyClient client)
    {
        client = YourPolicyClient(Clones.clone(client));
        client.setParameters(config);
    }
}
Frontend example
import { createWalletClient, encodeFunctionData } from 'viem'
import { newtonWalletClientActions } from 'newton-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: PolicyConfig = {
  policyParams: policyParamsHexEncoded,
  expireAfter
};

const factoryAbi = { ... };
const factoryAddress = "0x...";

const hash = await walletClient.sendTransaction({
  data: encodeFunctionData({
    abi: factoryAbi,
    functionName: "createClient",
    args: [policyConfig]
  }),
  to: factoryAddress
});