Add policy-based transaction authorization to your Solidity contracts. Deploy a PolicyClient that validates BLS attestation proofs before executing transactions.
This guide shows how to integrate Newton attestation validation into your smart contracts. You will inherit from NewtonPolicyClient, configure validation, and manage your policy lifecycle.
Add Newton attestation validation to an existing contract by inheriting NewtonPolicyClient. Initialization is deferred to a post-deployment admin function.
// SPDX-License-Identifier: MITpragma solidity ^0.8.27;import {NewtonPolicyClient} from "newton-contracts/src/mixins/NewtonPolicyClient.sol";import {NewtonMessage} from "newton-contracts/src/core/NewtonMessage.sol";import {INewtonProverTaskManager} from "newton-contracts/src/interfaces/INewtonProverTaskManager.sol";import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";contract MyExistingVaultV2 is OwnableUpgradeable, NewtonPolicyClient { error InvalidAttestation(); error PolicyClientAlreadyInitialized(); bool private _newtonPolicyClientInitialized; // ... existing contract storage and functions remain unchanged ... /// @notice Initialize Newton policy client support (one-time, post-upgrade) function initializeNewtonPolicyClient( address policyTaskManager, address policyClientOwner ) external onlyOwner { require(!_newtonPolicyClientInitialized, PolicyClientAlreadyInitialized()); _initNewtonPolicyClient(policyTaskManager, policyClientOwner); _newtonPolicyClientInitialized = true; } // setPolicyAddress(address policy) is inherited from NewtonPolicyClient. // The policyClientOwner can call it once a policy contract is deployed. /// @notice Add policy-protected functionality function protectedWithdraw( NewtonMessage.Attestation calldata attestation ) external { require(_validateAttestation(attestation), InvalidAttestation()); // Execute withdrawal logic... } function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return super.supportsInterface(interfaceId); }}
When adding NewtonPolicyClient to an existing upgradeable contract:
Add new storage variables at the end to avoid layout conflicts
The _newtonPolicyClientInitialized flag prevents re-initialization
Test thoroughly on a fork before mainnet deployment
Consider using a timelock or multisig for the initialization call
Direct validation verifies BLS signatures on-chain without waiting for the aggregator to call respondToTask. It performs the same policy/sender/chain checks, plus on-chain BLS signature verification and quorum checks.
// SPDX-License-Identifier: MITpragma solidity ^0.8.27;import {Script, console} from "forge-std/Script.sol";import {NewtonPolicyWallet} from "../src/NewtonPolicyWallet.sol";contract DeployScript is Script { function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address owner = vm.addr(deployerPrivateKey); vm.startBroadcast(deployerPrivateKey); NewtonPolicyWallet wallet = new NewtonPolicyWallet(owner); console.log("Deployed at:", address(wallet)); vm.stopBroadcast(); }}
The Task Manager address must be 0xecb741F4875770f9A5F060cb30F6c9eb5966eD13 on Sepolia. BLS signatures are bound to this address — using any other causes InvalidAttestation.
_setPolicyAddress() vs setPolicy(): _setPolicyAddress() stores the policy address but does NOT register with the Policy contract and does NOT return a policyId. You must call setPolicy() (or _setPolicy()) in a separate transaction after deployment to get a policyId. If you forget this step, policyId == 0 and all attestation validations silently fail with no helpful error message.expireAfter must be non-zero: NewtonPolicyData.initialize() reverts with InvalidExpireAfter() if expireAfter == 0. A zero value creates attestations where expiration == reference_block, making on-chain execution impossible — every call reverts with AttestationExpired because block.number >= expiration is always true by the time the tx mines. Use at least 150 blocks (~5 minutes on Base) for testnets.Deploy:
The PolicyClientRegistry is a central on-chain directory of approved policy client contracts. Registration is required for policy clients that participate in the identity linking flow via IdentityRegistry.
// Deactivate a client (prevents new identity links)policyClientRegistry.deactivateClient(address(myPolicyClient));// ReactivatepolicyClientRegistry.activateClient(address(myPolicyClient));// Transfer ownership of the registry recordpolicyClientRegistry.setClientOwner(address(myPolicyClient), newOwnerAddress);
The IdentityRegistry requires a policyClientRegistry address at initialization. Every _linkIdentity() call checks IPolicyClientRegistry.isRegisteredClient() on the target policy client and reverts with PolicyClientNotRegistered if the client is not registered or has been deactivated.Deployment lifecycle:
Deploy PolicyClientRegistry and initialize it
Deploy IdentityRegistry and pass the registry address to initialize(admin, policyClientRegistry)
Register approved policy clients via registerClient()
Any identity linking attempt against an unregistered or deactivated client reverts
When you call setPolicyAddress(), the policy’s factory version is validated against the TaskManager’s runtime minimum. This ensures protocol upgrades do not break existing clients.
There is no compile-time gate — policy clients never need to be redeployed for version upgrades. Owners call setPolicyAddress(newPolicy) to point their client to a policy from a newer factory. The policy client address never changes, so identity links and user consent remain intact.