# Newton Protocol Docs
Newton Protocol is a decentralized policy engine for onchain transaction authorization, built as an EigenLayer AVS.
# Overview \[Use Newton Protocol to add verifiable risk, compliance, and privacy guardrails to DeFi vault operations.]
Vaults are DeFi primitives that let depositors place capital under a strategy managed by a curator, allocator, or automated system. They are increasingly used by institutions, exchanges, and DeFi applications to give users access to differentiated yield products without requiring every user to manage the strategy directly.
Newton adds a verifiable policy layer to vaults. Instead of relying only on trust in a curator, frontend checks, or static onchain limits, a vault can require policy approval before sensitive actions are executed.
## Why Vaults Need Policy Enforcement
Vault depositors trust that curators and allocators will make thoughtful decisions and will not misuse the authority granted to them. Existing vault infrastructure can restrict some operations onchain, but many risk decisions depend on changing offchain context:
* Is the depositor address associated with sanctions, exploits, or high-risk activity?
* Has a vault's APY, TVL, allocation, or risk score changed in a concerning way?
* Is an oracle feed stale or diverging from an expected reference?
* Does the action fit the vault's stated mandate and risk profile?
Without verifiable policy enforcement, these checks often live in dashboards, runbooks, or centralized services. That makes them easy to bypass, difficult to audit, and hard for depositors to rely on.
## How Newton Helps
Newton is a decentralized policy engine for transaction authorization. A vault action is represented as an intent, evaluated against a policy, and approved only when the Newton operator network returns a valid attestation.
```mermaid
sequenceDiagram
participant Curator as Curator or Allocator
participant Vault as Vault / Shield
participant Newton as Newton Policy Engine
participant Data as Data Oracles
participant Protocol as Vault Protocol
Curator->>Vault: Submit vault action
Vault->>Newton: Request policy evaluation
Newton->>Data: Fetch offchain risk, compliance, or market data
Data-->>Newton: Return policy inputs
Newton-->>Vault: Return policy result and attestation
alt Policy approved
Vault->>Protocol: Execute action
else Policy denied
Vault-->>Curator: Reject action
end
```
This gives vault participants a stronger operating model:
* **Depositors** can see the rules that govern the vault and understand the risk controls before depositing.
* **Curators** can enforce their own mandates consistently, reducing operational mistakes across many vaults.
* **Institutions** can review verifiable guardrails and audit evidence before allocating capital.
* **Protocols** can expose higher-trust vault products without replacing their existing vault infrastructure.
## Common Vault Policy Areas
### Security
Security policies help prevent unsafe or unauthorized vault changes. They can restrict curator actions, check vault health, compare oracle feeds, and make sure decisions reflect the risk profile depositors agreed to up front.
### Compliance
Compliance policies help vaults control who can deposit and whether funds meet the vault's requirements. Policies can incorporate KYC status, sanctions screening, address reputation, and AML signals from external providers.
### Privacy
Privacy policies help curators protect sensitive curation decisions and proprietary strategy inputs. Newton's privacy layer can keep sensitive policy data away from public chain state while still producing an authorization result that a smart contract can enforce.
## Where Newton Fits
Newton does not replace the vault, the curator, or the DeFi protocol. It adds a policy enforcement layer between the requested action and execution.
For vaults, that layer can be used in two ways:
* **Policy packs** provide ready-made data inputs and Rego guardrails for vault risk, address screening, price divergence, and depeg risk. See [Policy Packs](/developers/vaults/policy-packs).
* **Newton VaultKit** provides the SDK and Shield contracts for curator-facing vault integrations, starting with Morpho and generic call support for other protocols. See [Vault SDK](/developers/vaults/sdk/overview).
Review the policy packs Newton makes available for vault risk, compliance, and oracle checks.
Integrate Newton VaultKit into curator workflows.
Understand intents, attestations, tasks, and operator evaluation.
# Policy Packs \[Use Newton policy packs to enforce risk, compliance, and oracle checks before vault actions execute.]
Newton vault policy packs combine Rego policy logic with policy data oracles. The oracle gathers the external data needed for a decision, then the Rego policy evaluates that data against the vault's configured parameters.
In VaultKit 2.1, each published pack implements the `PolicyPack` contract from `@newton-xyz/policy-core`. Declare packs with `definePolicy(...).with(pack)`; use `defineOracle(...)` when authoring your own compatible oracle.
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
```
In the Newton dashboard, use **Explore Data Oracles** to browse available data oracles, inspect the data they provide, and add the relevant oracle to a policy. Vault policy packs can combine multiple oracles, so a single vault action can be checked against risk, compliance, and market data before it is approved.
The open-source policy packs live in [newt-foundation/newton-policy-packs](https://github.com/newt-foundation/newton-policy-packs). Data providers can follow the repo's [CONTRIBUTING guide](https://github.com/newt-foundation/newton-policy-packs/blob/main/CONTRIBUTING.md) to add a pack that other developers can reuse in Newton-powered applications.
For more detail on how packs work, how they are implemented, and how to build policies from them, see [Policy Packs](/developers/guides/policy-packs).
:::note
The canonical deployment source is [`newton-policy-packs/deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json). The table below is a production-address snapshot for common curator workflows; use `deployments.json` for stage environments, WASM CIDs, deploy dates, and future redeploys.
:::
## Available Packs
Grouped by data provider.
| Provider | Policy pack | Use it for |
| --- | --- | --- |
| [Arkham](https://arkm.com/api) | [Entity-Aware Spending Gate](/developers/vaults/policies/arkham-entity) | Tiering a spending limit by who the destination actually is |
| [Arkham](https://arkm.com/api) | [Adaptive Counterparty Gate](/developers/vaults/policies/arkham-counterparty) | Judging a payment against the sending wallet's own history and outflow baseline |
| [Arkham](https://arkm.com/api) | [Explainable Risk Exposure](/developers/vaults/policies/arkham-risk) | Denying exposure you can trace to a specific transaction route |
| [Balancer](https://balancer.fi) | Data Checks | Adding keyless Balancer pool or market data |
| [Blockaid](https://www.blockaid.io) | Transaction Screening | Screening transaction and address risk |
| [Chainalysis](https://www.chainalysis.com) | [Address Screening](/developers/vaults/policies/chainalysis) | Screening addresses for sanctions, high-risk categories, and configured blocklists |
| [Guardrail](https://guardrail.so) | Risk Checks | Adding a Guardrail risk signal |
| [Persona](https://withpersona.com) | Identity Checks | Adding identity and verification checks |
| [Pharos](https://pharos.watch) | [Risk-Adjusted Treasury Gate](/developers/vaults/policies/pharos-treasury) | Admitting a stablecoin only when peg, stress, redemption, and exit capacity are all healthy |
| [Pharos](https://pharos.watch) | [Stablecoin Safe Mode](/developers/vaults/policies/pharos-safe-mode) | Blocking exposure-increasing actions under stress while leaving withdrawals open |
| [Pharos](https://pharos.watch) | [Redemption-Backed Asset Gate](/developers/vaults/policies/pharos-redemption) | Admitting a stablecoin only when it can actually be redeemed at par |
| [RedStone](https://redstone.finance) | [Oracle Divergence](/developers/vaults/policies/redstone) | Stopping actions when market data is stale, divergent, or persistently outside thresholds |
| [Sumsub](https://sumsub.com) | Verification Checks | Adding KYC and verification checks |
| [Vaults.fyi](https://vaults.fyi) | [Vault Risk Rating](/developers/vaults/policies/vaultsfyi) | Gating actions on APY, TVL, risk score, allocation changes, critical flags, and corruption status |
| [Webacy](https://developers.webacy.co/) | [Depeg Risk](/developers/vaults/policies/webacy) | Monitoring pegged-token depeg risk for manager-action gates |
| [Xerberus](https://xerberus.io) | [Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit) | Denying a position that can't be unwound quickly at a bounded market impact |
| [Xerberus](https://xerberus.io) | [Portfolio What-If](/developers/vaults/policies/xerberus-what-if) | Denying a trade that concentrates the book or makes it harder to exit |
| [Xerberus](https://xerberus.io) | [Common Cause](/developers/vaults/policies/xerberus-common-cause) | Denying false diversification across venues that share an oracle, collateral, or missing safeguard |
## Deployed PolicyData Addresses
Production `PolicyData` addresses for each pack:
:::note
The Arkham, Pharos and Xerberus packs are documented but **not yet deployed**, so they have no row below. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth and will carry their addresses first.
:::
| Pack | Ethereum (`1`) | Base (`8453`) | Ethereum Sepolia (`11155111`) | Base Sepolia (`84532`) |
| --- | --- | --- | --- | --- |
| Chainalysis | `0xCacbdEDA32C0048bbE0Ae0d3081Ced3490699049` | `0xceB2027a5fA390740971c7cbb2523841C19CC2f9` | `0x226d196d565b92952669701Fb6cb85B586706996` | `0xdC2e9d30B8e415F907e39f0bfe22974B9894740F` |
| RedStone | `0xc81371B54F5d71E8aA92a3601f30f3520c34A5a9` | `0xa5aDDdcf0d37C50A338B3C7F8067FB175AFAf6Bf` | `0x6621D7F739a3682054ac974D772AEEBb8548b1BF` | `0xDbfE2fea061e71109e65e511CD11D070f49A187d` |
| Vaults.fyi | `0xd0947ba40e526E7cdD9d2f9C276cED1B2e089c49` | `0x763800149600164edBd19d0eeE9f858d7ac27836` | `0x90ef32c7D103D5Af2A546f94BdA994BC37372017` | `0x443487D506eEc522e5fb5075eBDbD7496DB454dF` |
| Webacy | `0xc127976860eEF5FF5d610548C456ABFEE66CD620` | `0x031afeF4c77774960Eb33fb446F9Ce3612C56b5c` | `0xBFf38a5A77262ADfC10667004A550337A97EFB36` | `0x01206E5302794Fba1B71b761594a5e8b4111Be60` |
## Composing Policy Packs
Vault policy packs are most useful when combined. For example, a regulated yield vault might require:
* Webacy depeg-risk checks for pegged-token exposure
* Chainalysis sanctions screening for the depositor address
* Vaults.fyi risk checks before accepting additional capital
* RedStone oracle divergence checks before curator reallocations
The result is a vault whose rules are explicit, inspectable, and enforced before execution.
The three Xerberus packs are built to run together as one gate for exposure-increasing actions: an exit quote for the asset, a what-if for the portfolio, and a common-cause check across venues. Pass the same `window` to all three so every result comes from one Xerberus snapshot.
For custom oracles, policy-level `_policy` params, bare policies, manifest introspection, and provenance classification, see [Policies](/developers/vaults/sdk/policies) and [Custom Oracles](/developers/vaults/sdk/custom-oracles).
Learn how policy packs are structured, implemented, and used in Newton policies.
Check vault APY, TVL drawdown, risk score, allocation changes, flags, and corruption status.
Screen addresses against sanctions and risk categories.
Monitor pegged-token depeg risk before manager actions execute.
Compare RedStone price data against onchain oracle data before allowing actions.
Tier a spending limit by the destination's real-world entity attribution.
Judge a payment against the sending wallet's own counterparty history.
Deny risk exposure traced to a specific transaction route.
Admit a stablecoin only when peg, stress, redemption, and exit capacity all hold.
Block exposure-increasing actions under stress while leaving the exits open.
Admit a stablecoin only when it can actually be redeemed at par.
Deny a position that can't be unwound quickly at a bounded market impact.
Deny a trade that concentrates the book or makes it harder to exit.
Deny false diversification across venues that share a failure mechanism.
# Protocol Concepts \[Core Newton Policy Protocol types and actors used by VaultKit.]
VaultKit is a Newton policy client. Every protected vault action becomes an `Intent`, every `Intent` is evaluated by Newton operators, and every approved action is bound to an attestation before the Shield forwards it.
## Actors
| Actor | Role |
| --- | --- |
| Curator | Builds and signs the manager action. This may be an EOA, application signer, or Safe-controlled workflow. |
| SDK | Encodes calldata, prepares policy inputs, submits tasks, polls for results, and submits the Shield transaction. |
| Gateway | Receives tasks from the SDK and coordinates offchain policy evaluation. |
| Operators | Run policy WASM against the intent, params, and `wasmArgs`, then sign allow or deny results. |
| Aggregator | Collects operator signatures until quorum and commits the result. |
| Shield | Onchain policy client that validates attestations and forwards approved calls. |
| Vault protocol | The underlying protocol contract, such as a MetaMorpho or Euler vault. |
## `Intent`
An `Intent` is the exact delegated call the curator wants the Shield to execute:
```solidity
struct Intent {
address from;
address to;
uint256 value;
bytes data;
uint256 chainId;
bytes functionSignature;
}
```
The attestation binds to all fields. `from` must match the caller of `Shield.execute(...)`, `chainId` must match the execution chain, and `data` must match the exact calldata operators approved.
## `Task`
A `Task` is the offchain evaluation request sent to Newton Gateway. It carries the intent, policy reference, pack-specific `wasmArgs`, expiration metadata, and the curator's signature over the intent.
## `TaskResponse`
A `TaskResponse` is the operator quorum result for a task. Operators sign the result they evaluated; they do not rewrite the intent. A denial response becomes a typed SDK error. An approval response can become an attestation for onchain execution.
## `Attestation`
An `Attestation` is the approval object consumed by the Shield in standard mode:
```solidity
struct Attestation {
bytes32 taskId;
bytes32 policyId;
address policyClient;
uint32 expiration;
Intent intent;
bytes intentSignature;
}
```
The aggregated BLS signature data is recorded through the AVS task manager. The Shield validates the task id, policy id, client address, intent signature, chain, expiration, and replay status before forwarding the call.
## Policy and Params
A Newton policy pins a policy template and a `PolicyConfig`. The config includes `policyParams`, a typed byte blob produced by the selected policy pack. Updating params changes future policy evaluations; it does not rewrite old attestations.
## `wasmArgs`
`wasmArgs` are per-call inputs for the policy WASM. They are not global configuration. They usually include the vault, network, asset, market id, external-data query fields, and freshness snapshots required for the policy to make a decision.
## Fail-Closed Behavior
If Newton operators deny the task, quorum is not reached, the attestation expires, or the Shield validation fails, the vault action is not forwarded. This fail-closed property is the core operating assumption for VaultKit.
## Policy-set clients
For policy-set-capable deployments, a Task freezes the ordered specs, set ID, and revision. The response retains ordered per-policy evidence and aggregate `allowed`; all policies must allow. Preserve order and repeats when forwarding signed responses. Verify the Shield and Vault SDK release before adopting the core SDK schema.
# Attestation Flow \[How a VaultKit manager action becomes a Newton policy attestation and an onchain Shield execution.]
This is the lifecycle from "a curator wants to call `reallocate(...)`" to "the vault contract receives the call."
```mermaid
sequenceDiagram
participant Curator as Curator Wallet
participant SDK as VaultKit SDK
participant Gateway as Newton Gateway
participant Operators as Operators + Aggregator
participant Shield as Shield Clone
participant Vault as Vault Contract
Curator->>SDK: Typed manager action
SDK->>SDK: Build Intent and wasmArgs
SDK->>Curator: Request EIP-712 intent signature
SDK->>Gateway: newt_createTask(intent, wasmArgs, signature)
Gateway->>Operators: Dispatch task
Operators->>Operators: Evaluate policy WASM
Operators-->>Gateway: Allow or deny quorum result
Gateway-->>SDK: TaskResponse or attestation data
SDK->>Shield: execute(...) or executeDirect(...)
Shield->>Shield: Validate policy, signature, chain, expiration, replay
Shield->>Vault: Forward approved calldata
```
## 1. Intent Construction
The vendor module or `sendCall(...)` path produces calldata for the target vault action. The policy pack prepares `wasmArgs`. The SDK assembles an `Intent` that binds:
* The curator signer.
* The target contract.
* The calldata and value.
* The chain id.
* The human-readable function signature encoded as bytes.
The curator signs the intent with EIP-712. That signature lets operators and the Shield verify that the approved call originated from the expected curator address.
## 2. Task Submission
The SDK sends the signed intent, `wasmArgs`, and policy reference to Newton Gateway. The gateway returns a task id and coordinates policy evaluation.
## 3. Operator Evaluation
Operators load the policy WASM and evaluate it against:
* The intent.
* The Shield's current policy params.
* The per-call `wasmArgs`.
* Any allowed external data or encrypted secrets the pack needs.
If the policy denies, the SDK raises `PolicyDeniedError`. The Shield never receives a valid approval for that action.
## 4. Attestation Commit
In standard mode, the aggregator commits the quorum response through the AVS task manager. The SDK then calls `Shield.execute(attestation)`.
In direct mode, the SDK receives the raw `Task`, `TaskResponse`, and signature data, then calls `Shield.executeDirect(...)`. Direct mode avoids waiting for aggregator commit but costs more gas because the Shield verifies signatures inline.
## 5. Onchain Validation and Forwarding
The Shield validates:
* The attestation policy id matches the Shield's current policy id.
* The intent `from` matches the caller.
* The intent chain id matches `block.chainid`.
* The task is approved, unexpired, and not already spent.
* The BLS quorum is valid, either through the task manager or inline direct validation.
If validation passes, the Shield forwards `intent.data` and `intent.value` to `intent.to`. If the vault reverts, the Shield bubbles the target revert reason where possible.
## Replay, Expiration, and Freshness
Task ids are one-shot. Reusing the same attestation reverts. Attestations also expire after their configured block window.
Freshness comes from the policy pack. Packs commonly include hashes or snapshots in `wasmArgs`, then deny if chain state or external data moved beyond the accepted window during evaluation.
## Standard vs Direct
| Trait | Standard `execute` | Direct `executeDirect` |
| --- | --- | --- |
| Aggregator commit | Required | Skipped |
| BLS verification | AVS task manager | Inline in Shield |
| Gas | Lower | Higher |
| Latency | Higher | Lower |
| SDK argument | `shield.sendCall(args, 'ATTESTATION')` | `shield.sendCall(args, 'DIRECT')` |
# Guarantees \[The enforcement guarantees VaultKit relies on when routing vault actions through a Shield.]
VaultKit's guarantees come from combining typed calldata, signed intents, operator quorum, onchain Shield validation, and a timelocked owner bypass.
## 1. Typed Calldata Only
Curators should use typed vendor overlays such as `shield.morpho.reallocate(vault, allocations, options?)`. The overlay builds calldata through the vendor's own SDK. `shield.sendCall(...)` remains an escape hatch for unwrapped vendors; when using it, the curator owns calldata integrity.
## 2. Exact-Tx Attestation Binding
The attestation binds to the full `Intent`: signer, target, value, calldata, chain, and function signature. A curator cannot swap calldata after approval, and an attacker cannot reuse an approval for a different vault action.
## 3. Onchain Policy Parameters
Policy params live onchain in `NewtonPolicy.PolicyConfig.policyParams`. For composite packs, those bytes are a manifest envelope with `_manifest`, `modules`, and `params`; depositors can decode it and compare the modules against `getPolicyData()` and each oracle's `getWasmCid()`.
## 4. Manager Actions Only
The Shield holds vendor manager roles, such as a MetaMorpho allocator or curator role. End-user deposits and withdrawals do not route through Shield. Vendor-side role grants determine which forwarded manager actions can succeed.
## 5. Fail Closed; Bypass Via Timelock
If the gateway is unavailable, operators do not reach quorum, policy evaluation denies, delegate validation fails, or onchain attestation validation fails, Shield does not forward the vault call. The only emergency escape is the owner-queued bypass path, which waits at least the configured delay before execution and emits observable events.
## Supporting Checks
| Check | What enforces it |
| --- | --- |
| Chain isolation | `Intent.chainId` is checked against `block.chainid`. |
| Replay protection | The AVS task manager tracks consumed task ids. |
| Expiration | Attestations are valid only for the configured block window. |
| Delegate gate | `execute` and `executeDirect` require `onlyApprovedDelegate`. |
| Policy binding | Shield validates the attestation's `policyId` against the currently bound policy id. |
## Non-Upgradeable Clones
Shield clones are EIP-1167 minimal proxies to a fixed implementation. There is no proxy-admin upgrade path. A new implementation requires a new factory and curator opt-in through a new clone.
# Vaults.fyi Vault Risk Rating \[Use Vaults.fyi data in Newton policies to gate vault actions on APY, TVL, risk score, allocation changes, flags, and corruption status.]
The Vaults.fyi policy checks whether a vault still fits the risk profile configured by the vault owner or curator. It is useful before deposits, cap changes, reallocations, or other actions that should stop when the vault's health changes.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `vaultsfyi` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `VAULTS_FYI_API_KEY` | Required | [vaults.fyi](https://vaults.fyi) |
## Data Inputs
The Vaults.fyi data oracle normalizes vault performance, TVL, reputation score, flags, capacity, and historical time-series data from the [Vaults.fyi vault data API](https://docs.vaults.fyi/concepts/vault-data).
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `apy_current` | The vault's current 1 day total APY from `apy.1day.total`. Vaults.fyi also exposes 1 hour, 7 day, and 30 day APY windows. | Used as the short-window yield signal for APY anomaly detection. |
| `apy_30d` | The vault's 30 day total APY from `apy.30day.total`. Vaults.fyi recommends longer windows, such as 7 day or 30 day, for user-facing yield displays because shorter windows are noisier. | Used as the baseline for detecting whether current APY moved unusually far from recent history. |
| `apy_z_score` | A normalized measure of how far the current APY is from the 30 day APY baseline. In this policy oracle, it is computed from current APY and 30 day APY. | Denies actions when yield changes look anomalous enough to require review. |
| `tvl_usd` | Total value locked in the vault, denominated in USD. Vaults.fyi also exposes native-asset TVL. | Used to calculate drawdowns and understand vault scale. |
| `tvl_drawdown_24h_pct` | The percentage drop from the historical TVL point roughly 24 hours ago to current `tvl_usd`. | Denies actions when the vault has lost too much TVL over the last day. |
| `tvl_drawdown_7d_pct` | The percentage drop from the historical TVL point roughly 7 days ago to current `tvl_usd`. | Denies actions when the vault has had a larger medium-term TVL drawdown. |
| `risk_score` | Vaults.fyi's composite vault reputation score, after component scores and flag penalties are applied. Higher is better. | Denies actions when the vault score falls below the configured minimum. |
| `flags` | Active vault flags from Vaults.fyi, including severity and content. Flags can describe warnings such as audits, migrations, paused deposits, or incidents. | Used to detect critical or high-severity warnings that should pause vault actions. |
| `has_critical_flag` | Boolean derived from `flags` when any flag has `critical` or `high` severity. | Denies actions when severe active warnings are present and `deny_on_critical_flag` is enabled. |
| `allocation_changed_since_last` | Boolean derived by hashing vault metadata such as protocol, tags, fees, and child vaults, then comparing it to the last known allocation hash. | Denies actions when the vault's observed allocation metadata changed unexpectedly. |
| `capacity_remaining` / `capacity_max` | Deposit capacity data when the vault has a cap. Vaults.fyi exposes maximum capacity and remaining capacity where applicable. | Useful for display and future policy extensions around deposits near capacity. |
| `is_corrupted` | Vaults.fyi marks vaults as corrupted when indexed data is known to be unreliable. | Denies actions when the vault data should not be trusted. |
## Rego Checks
Use these exact Rego checks to enforce each guardrail.
### APY Spike
This check compares `apy_z_score` to the configured `apy_z_max`. Use it to stop actions when short-window APY moves too far from the 30 day baseline, which can indicate abnormal rewards, stale data, manipulation, or a strategy regime change.
```rego
deny contains "apy_spike" if v.apy_z_score > t.apy_z_max
```
### 24 Hour TVL Drawdown
This check blocks actions when current TVL has fallen by more than `tvl_drawdown_24h_max_pct` compared with the recent 24 hour historical point. It is useful for catching sudden withdrawals, migrations, or confidence shocks.
```rego
deny contains "tvl_drawdown_24h" if v.tvl_drawdown_24h_pct > t.tvl_drawdown_24h_max_pct
```
### 7 Day TVL Drawdown
This check blocks actions when current TVL has fallen by more than `tvl_drawdown_7d_max_pct` compared with the 7 day historical point. The 7 day window is less noisy than a daily check and helps catch sustained capital flight.
```rego
deny contains "tvl_drawdown_7d" if v.tvl_drawdown_7d_pct > t.tvl_drawdown_7d_max_pct
```
### Risk Score Floor
This check blocks actions when Vaults.fyi returns a non-null reputation score below the configured `risk_score_floor`. Because the score includes multiple components and can include flag penalties, this gives the policy a compact vault-health threshold.
```rego
deny contains "risk_score_below_floor" if {
v.risk_score != null
v.risk_score < t.risk_score_floor
}
```
### Allocation Change
This check blocks actions when the oracle detects a change from the last known allocation hash and the policy is configured to deny on allocation change. Use it when a vault should pause until an allocator or risk team reviews changed vault metadata.
```rego
deny contains "allocation_changed" if {
v.allocation_changed_since_last
t.deny_on_allocation_change
}
```
### Critical Flag
This check blocks actions when Vaults.fyi reports a high or critical flag and the policy is configured to deny on critical flags. Use it to pause actions during severe active warnings.
```rego
deny contains "critical_flag" if {
v.has_critical_flag
t.deny_on_critical_flag
}
```
### Corrupted Vault
This check blocks actions when Vaults.fyi marks the vault's indexed data as corrupted and the policy is configured to deny corrupted vaults. Use it when policy decisions should not proceed on unreliable vault data.
```rego
deny contains "vault_corrupted" if {
v.is_corrupted
t.deny_on_corrupted
}
```
### Final Allow Rule
```rego
allow if count(deny) == 0
```
## Complete Policy
```rego
package vault_risk_rating
import future.keywords
default allow := false
t := data.params.vaultsfyi
v := data.wasm.vaultsfyi
deny contains "apy_spike" if v.apy_z_score > t.apy_z_max
deny contains "tvl_drawdown_24h" if v.tvl_drawdown_24h_pct > t.tvl_drawdown_24h_max_pct
deny contains "tvl_drawdown_7d" if v.tvl_drawdown_7d_pct > t.tvl_drawdown_7d_max_pct
deny contains "risk_score_below_floor" if {
v.risk_score != null
v.risk_score < t.risk_score_floor
}
deny contains "allocation_changed" if {
v.allocation_changed_since_last
t.deny_on_allocation_change
}
deny contains "critical_flag" if {
v.has_critical_flag
t.deny_on_critical_flag
}
deny contains "vault_corrupted" if {
v.is_corrupted
t.deny_on_corrupted
}
allow if count(deny) == 0
```
# Chainalysis Address Screening \[Use Chainalysis data in Newton policies to screen addresses for sanctions and high-risk categories before vault actions execute.]
The Chainalysis policy screens an address for sanctions and risk categories. It is useful for vault deposits, withdrawals, curator operations, or any flow where the vault needs address-level compliance and AML controls.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `chainalysis` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secrets
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `CHAINALYSIS_SANCTIONS_KEY` | Required by schema | [chainalysis.com](https://www.chainalysis.com) |
| `CHAINALYSIS_SCREENING_KEY` | Required only for paid risk screening APIs | [chainalysis.com](https://www.chainalysis.com) |
The sanctions endpoint is public/keyless, so a placeholder can satisfy schemas that require `CHAINALYSIS_SANCTIONS_KEY`. Use `CHAINALYSIS_SCREENING_KEY` only when your deployed policy calls the paid risk API.
## Data Inputs
The Chainalysis data oracle returns values such as:
* `sanctioned`
* `is_high_risk`
* `risk_categories`
## Rego Checks
Use these exact Rego checks to enforce each guardrail.
### Sanctioned Address
```rego
deny contains "chainalysis_sanctioned" if {
t.deny_on_sanctioned
v.sanctioned
}
```
### High-Risk Address
```rego
deny contains "high_risk_address" if {
t.deny_on_high_risk_category
v.is_high_risk
}
```
### Blocklisted Risk Category
```rego
deny contains "risk_category_blocklisted" if {
some cat in v.risk_categories
cat in t.risk_categories_blocklist
}
```
### Final Allow Rule
```rego
allow if count(deny) == 0
```
## Complete Policy
```rego
package chainalysis_address_screening
import future.keywords
default allow := false
t := data.params.chainalysis
v := data.wasm.chainalysis
deny contains "chainalysis_sanctioned" if {
t.deny_on_sanctioned
v.sanctioned
}
deny contains "high_risk_address" if {
t.deny_on_high_risk_category
v.is_high_risk
}
deny contains "risk_category_blocklisted" if {
some cat in v.risk_categories
cat in t.risk_categories_blocklist
}
allow if count(deny) == 0
```
# RedStone Oracle Divergence \[Use RedStone data in Newton policies to stop vault actions when price feeds are stale, divergent, or persistently outside thresholds.]
The RedStone policy compares a RedStone price feed against an onchain oracle and checks whether the divergence is acceptable. It is useful when vault operations depend on fresh and reliable market data.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `redstone` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| None | Keyless | [redstone.finance](https://redstone.finance) |
## Data Inputs
The RedStone data oracle returns values such as:
* `redstone_feed_age_seconds`
* `divergence_bp`
* `prev_snapshot_present`
* `prev_divergence_bp`
* `sustained_seconds`
## Rego Checks
Use these exact Rego checks to enforce each guardrail.
### Stale Feed
```rego
deny contains "redstone_feed_stale" if v.redstone_feed_age_seconds > t.max_feed_age_seconds
```
### Hard Divergence Cap
```rego
deny contains "divergence_above_hard_cap" if v.divergence_bp >= t.deny_bp
```
### Sustained Divergence
```rego
deny contains "divergence_sustained" if {
t.enable_sustained_check
v.prev_snapshot_present
v.divergence_bp >= t.warn_bp
v.prev_divergence_bp >= t.warn_bp
v.sustained_seconds >= t.deny_sustained_seconds
}
```
### Final Allow Rule
```rego
allow if count(deny) == 0
```
## Complete Policy
```rego
package redstone_oracle_divergence
import future.keywords
default allow := false
t := data.params.redstone
v := data.wasm.redstone
deny contains "redstone_feed_stale" if v.redstone_feed_age_seconds > t.max_feed_age_seconds
deny contains "divergence_above_hard_cap" if v.divergence_bp >= t.deny_bp
deny contains "divergence_sustained" if {
t.enable_sustained_check
v.prev_snapshot_present
v.divergence_bp >= t.warn_bp
v.prev_divergence_bp >= t.warn_bp
v.sustained_seconds >= t.deny_sustained_seconds
}
allow if count(deny) == 0
```
# Webacy Depeg Risk \[Use Webacy data in Newton policies to monitor pegged-token depeg risk before manager actions execute.]
The Webacy vault policy monitors pegged-token depeg risk. It is for manager-action gates, such as curator or allocator operations, not end-user deposit gating.
The policy data oracle calls Webacy's `/rwa/{address}` endpoint and returns depeg-risk signals for the asset or token address the policy checks.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `webacy` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `WEBACY_API_KEY` | Required | [developers.webacy.co](https://developers.webacy.co/) |
## Data Inputs
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `is_collapsed` | Whether Webacy reports the pegged asset as collapsed. | Denies manager actions when the asset is already in a collapsed state. |
| `depeg_event_count` | Number of detected depeg events in the relevant lookback. | Lets policies stop actions after repeated peg failures. |
| `consecutive_days_below_peg` | Number of consecutive days the asset has remained below peg. | Captures sustained, not just momentary, peg stress. |
| `address` | The checked token or RWA address. | Binds the Webacy result to the asset used by the policy. |
## Example Checks
```rego
deny contains "webacy_collapsed" if {
t.deny_on_collapsed
v.is_collapsed
}
deny contains "webacy_depeg_events" if {
v.depeg_event_count > t.max_depeg_event_count
}
deny contains "webacy_sustained_depeg" if {
v.consecutive_days_below_peg > t.max_consecutive_days_below_peg
}
```
## Notes
Webacy depeg risk is usually composed with other manager-action packs. For example, a curator can combine Webacy with Vaults.fyi and RedStone so a reallocation stops when the vault is unhealthy, the pegged asset is stressed, or price feeds diverge.
# Arkham Entity-Aware Spending Gate \[Use Arkham entity attribution in Newton policies to tier a spending limit by who the destination actually is.]
This policy tiers a spending limit by **who the destination actually is**, using [Arkham Intelligence](https://arkm.com/api). Arkham connects on-chain addresses to real-world entities, so instead of a flat allow/deny an autonomous wallet gets graduated authority: a verified, approved-category destination (exchange, custodian, established protocol) gets the full tier, an unlabelled one gets a small introductory cap, a low-confidence attribution denies so a human can approve it out of band, and a prohibited risk tag denies outright at any size.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `arkham_entity` |
| Rego package | `arkham_entity_wallet` (the `--entrypoint` is `arkham_entity_wallet.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `ARKHAM_API_KEY` | Required | [arkm.com](https://arkm.com/api) |
Sent by the oracle as the `API-Key` request header.
## Data Inputs
Two GET requests against `https://api.arkm.com`: `/intelligence/address_enriched/{address}/all` for entity attribution, labels, tags and predictions, and `/risk/address/{address}` for the headline risk level and score.
Arkham returns **per-chain** results, because the same address can carry different labels and activity on different networks. When `chain` is supplied the oracle takes that slice; otherwise it takes the first populated one and reports which, so the decision stays traceable to a specific network rather than silently blending several.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `address` | The queried destination address | Echoed for traceability |
| `chain` | The chain slice actually used, which may differ from the request when `chain` was omitted | Makes the decision traceable to one network |
| `has_attribution` | True when Arkham has either a verified entity or a prediction | Selects the verified tier vs. the introductory cap |
| `entity_name` | Real-world entity behind the address, or `null` | Reviewer context |
| `entity_category` | Arkham entity category (`cex`, `custodian`, `defi`, …), or `null` | Checked against `approved_entity_categories` |
| `address_role` | From `arkhamLabel.name` — e.g. `Hot Wallet`, `Deposit` | Reviewer context |
| `is_contract` | Whether Arkham marks the address as a contract | Reviewer context |
| `tags` | From `populatedTags[].id` — the stable machine name (`cex`), lowercased and deduplicated, **not** the display label | Matched against `prohibited_tags` |
| `attribution_type` | `verified`, `predicted`, or `none` | Reviewer context |
| `attribution_confidence` | `1` for a verified `arkhamEntity` (Arkham asserts these and attaches no confidence field), the prediction's own value for a predicted match, `null` when unattributed | Gated by `min_attribution_confidence` |
| `risk_level` | Arkham headline risk level | Reviewer context |
| `max_risk_score` | Highest category score driving the risk level, or `null` | Gated by `max_risk_score` |
| `transaction_amount_usd` | Echoed from `wasmArgs` — **not attested**, see Notes | Every tier comparison rests on it |
| `data_age_seconds` | Age of the Arkham observation, or `null` | Gated by `max_data_age_seconds` |
| `timestamp` | When this snapshot was taken | Audit |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: the address it screens is not something the SDK can read off the vault, so the curator supplies it per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `address` | Required | The destination being judged |
| `chain` | Optional | Arkham chain slug (`ethereum`, `base`, `arbitrum_one`, …). Arkham indexes production networks only |
| `transaction_amount_usd` | Optional, defaults to `0` | USD notional, for the tier rules |
From the CLI or dashboard, these go in the simulate payload's `wasm_args`. From the SDK they go in `sendCall`'s `wasmArgs` bag — keyed by pack id, and typed per module, so a misspelled field is a compile error rather than a fail-closed evaluation at run time:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
arkham_entity: {
address: destination,
chain: 'ethereum',
transaction_amount_usd: 5_000,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs) for why this is a separate bag from `prepareQueryOptions`.
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `prohibited_tags` | `string[]` | Tags that deny outright regardless of amount. Matched case-sensitively against the lowercased form the oracle emits, so configure in lowercase |
| `approved_entity_categories` | `string[]` | Categories qualifying for the full tier |
| `tier_verified_max_usd` | `number` | Hard per-transaction ceiling in USD, applied even to trusted destinations |
| `tier_unlabelled_max_usd` | `number` | Introductory cap for unattributed or unapproved destinations |
| `min_attribution_confidence` | `number` | Minimum confidence 0-1 to act on an entity match |
| `deny_on_no_attribution` | `boolean` | Refuse unattributed destinations outright |
| `max_risk_score` | `number` | Maximum tolerated headline risk score 0-100 |
| `max_data_age_seconds` | `number` | Freshness ceiling |
| `deny_on_missing_fields` | `string[]` | Field names whose unreported (`null`) value denies rather than passes. Accepts `attribution_confidence`, `max_risk_score`, `data_age_seconds`. An empty list opts out |
## Rego Checks
Every rule this policy enforces:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `prohibited_tag` | any tag in `prohibited_tags` | Sanctioned, hacker, scam or mixer destinations, at any size |
| `no_attribution` | `deny_on_no_attribution` and no attribution | Unlabelled destinations, when the curator refuses them outright |
| `no_attribution_over_limit` | unattributed and amount over `tier_unlabelled_max_usd` | An unknown address used for more than an introductory amount |
| `unapproved_entity_category` | attributed, category not approved, amount over the intro cap | A known but out-of-policy venue used at scale |
| `low_attribution_confidence` | confidence below `min_attribution_confidence` | Weak entity matches that need human review |
| `amount_over_verified_tier` | amount over `tier_verified_max_usd` | The hard ceiling |
| `risk_score_above_max` | score over `max_risk_score` | Elevated headline risk |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale intelligence |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A configured threshold quietly doing nothing |
### Prohibited Tag
A prohibited tag denies at any size — there is no amount below which a sanctioned or mixer destination becomes acceptable. `prohibited_tag_hits` is a set rather than a bare boolean so the offending tag stays traceable.
```rego
prohibited_tag_hits contains tag if {
some tag in v.tags
tag in t.prohibited_tags
}
deny contains "prohibited_tag" if count(prohibited_tag_hits) > 0
```
### Unattributed Destination
Two rules, so the curator picks the posture. `deny_on_no_attribution` refuses an unknown address outright; leave it off and an unknown address is instead capped at the introductory tier.
```rego
deny contains "no_attribution" if {
t.deny_on_no_attribution
v.has_attribution == false
}
deny contains "no_attribution_over_limit" if {
v.has_attribution == false
amount > t.tier_unlabelled_max_usd
}
```
### Unapproved Entity Category
Note the `has_attribution == true` guard: an unattributed address has no category to judge, and is handled by the rules above instead.
```rego
deny contains "unapproved_entity_category" if {
v.has_attribution == true
not v.entity_category in t.approved_entity_categories
amount > t.tier_unlabelled_max_usd
}
```
### Missing Fields
A threshold the curator configured is worth nothing if the oracle never reports the value it applies to. This is opt-in **per field** rather than one blanket switch, so a provider that never populates a given field cannot force a choice between requiring the field you care about and denying every transaction.
```rego
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
```
### Final Allow Rule
The groundedness probes are load-bearing, not decoration. Every deny rule silent-skips on an undefined field, so an error envelope yields an **empty** deny set — a bare `count(deny) == 0` would fail *open* on exactly the payload that most needs to fail closed.
```rego
allow if {
not v.error
is_boolean(v.has_attribution)
is_number(amount)
count(deny) == 0
}
```
## Complete Policy
```rego
package arkham_entity_wallet
import future.keywords
default allow := false
t := data.params.arkham_entity
v := data.wasm.arkham_entity
amount := v.transaction_amount_usd
nullable_fields := {
"attribution_confidence": v.attribution_confidence,
"max_risk_score": v.max_risk_score,
"data_age_seconds": v.data_age_seconds,
}
prohibited_tag_hits contains tag if {
some tag in v.tags
tag in t.prohibited_tags
}
deny contains "prohibited_tag" if count(prohibited_tag_hits) > 0
deny contains "no_attribution" if {
t.deny_on_no_attribution
v.has_attribution == false
}
deny contains "no_attribution_over_limit" if {
v.has_attribution == false
amount > t.tier_unlabelled_max_usd
}
deny contains "unapproved_entity_category" if {
v.has_attribution == true
not v.entity_category in t.approved_entity_categories
amount > t.tier_unlabelled_max_usd
}
deny contains "low_attribution_confidence" if {
v.attribution_confidence != null
v.attribution_confidence < t.min_attribution_confidence
}
deny contains "amount_over_verified_tier" if amount > t.tier_verified_max_usd
deny contains "risk_score_above_max" if {
v.max_risk_score != null
v.max_risk_score > t.max_risk_score
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
allow if {
not v.error
is_boolean(v.has_attribution)
is_number(amount)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.arkham_entity`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **`transaction_amount_usd` is caller-supplied and NOT attested.** It arrives through `wasmArgs`, so a caller controls it, and every tier rule rests on it. The attested alternative — `input.value` — is native-token wei rather than USD, and arrives as a *string*. A curator who needs a tamper-proof ceiling should pair this pack with a native-value cap in a composite rather than relying on the USD tiers alone.
* `null` is the oracle's "Arkham did not report this", deliberately distinct from `0`: a null risk score means "not reported", a zero score would be a genuine clean verdict. Null optional fields fail soft. A **missing** key rather than an explicit null leaves the groundedness probes undefined and correctly blocks `allow`.
* **Verified attribution carries no confidence field.** Arkham asserts `arkhamEntity` rather than inferring it, so the oracle scores it `1`. Only `arkhamEntityPrediction` has a real confidence value, which is what `min_attribution_confidence` meaningfully gates.
* Arkham populates all three `deny_on_missing_fields` candidates for an attributed address, so listing them is safe for curators who would rather an unreported value block than pass.
* Tags are lowercased by the oracle and matched case-sensitively against that normalised form, so configure `prohibited_tags` in lowercase.
* The lookup chain is **not** the execution chain. Arkham indexes production networks only, so on a testnet `chain` must name a real mainnet or the oracle has no data and the policy fails closed.
# Arkham Adaptive Counterparty Gate \[Use Arkham counterparty history in Newton policies to judge a payment against the sending wallet's own baseline.]
This policy judges a payment against **the sending wallet's own history**, using [Arkham Intelligence](https://arkm.com/api). Routine payments to established counterparties clear automatically; anything that breaks the wallet's established pattern stops for human approval. A new recipient is capped at an introductory amount, an unusually large payment relative to prior transactions with that counterparty denies, a concentration spike denies, and total outflow running well above the wallet's normal daily level denies.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `arkham_counterparty` |
| Rego package | `arkham_counterparty_activity` (the `--entrypoint` is `arkham_counterparty_activity.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `ARKHAM_API_KEY` | Required | [arkm.com](https://arkm.com/api) |
Sent by the oracle as the `API-Key` request header.
## Data Inputs
Two GET requests against `https://api.arkm.com`: `/counterparties/address/{sender}` for counterparties aggregated by USD volume with transaction counts and labels, and `/flow/address/{sender}` for the inflow/outflow time series used to derive the outflow baseline.
The baseline deliberately **excludes the most recent bucket**: that bucket is what is being judged, and including it would let a single anomalous day inflate its own baseline and hide itself.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `sender_address` / `destination_address` | Echoed from `wasmArgs` | Audit |
| `chains` | Chain scope used, or `null` for all | Audit |
| `is_known_counterparty` | Whether the destination appears in the sender's history | Selects the new-recipient cap vs. the established path |
| `counterparty_transaction_count` | Prior transactions with this counterparty | Gated by `min_counterparty_transactions` |
| `counterparty_total_usd` | Cumulative USD transacted with them | Reviewer context |
| `counterparty_avg_usd` | Average prior transaction size, or `null` when there is no history to average | Basis for the anomaly multiple |
| `counterparty_last_seen_days` | **Always `null`** — Arkham's counterparties endpoint returns no per-relationship timestamp | Would gate `stale_relationship`; inert today |
| `counterparty_concentration_pct` | This counterparty's share (0-100) of recent activity | Gated by `max_counterparty_concentration_pct` |
| `normal_daily_outflow_usd` | Mean daily outflow across the window, excluding the latest bucket | Baseline denominator |
| `recent_daily_outflow_usd` | Latest bucket's outflow | Baseline numerator |
| `outflow_ratio` | `recent / normal`, or `null` when no baseline exists | Gated by `max_outflow_vs_baseline_multiple` |
| `transaction_amount_usd` | Echoed from `wasmArgs` — **not attested**, see Notes | The anomaly and cap rules rest on it |
| `timestamp` | When this snapshot was taken | Audit |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: the sender and destination are not something the SDK can read off the vault, so the curator supplies them per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `sender_address` | Required | The wallet whose history is read |
| `destination_address` | Required | The counterparty being judged |
| `chains` | Required | Comma-separated chain scope. **Not optional** — see Notes |
| `transaction_amount_usd` | Optional, defaults to `0` | USD notional |
| `history_window_days` | Optional, 1-365, defaults to `90` | Materially changes the counterparty set — see Notes |
From the CLI or dashboard these go in the simulate payload's `wasm_args`. From the SDK they go in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
arkham_counterparty: {
sender_address: shieldClone,
destination_address: destination,
chains: 'ethereum',
transaction_amount_usd: 12_000,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `require_known_counterparty` | `boolean` | Refuse any destination absent from history |
| `max_new_counterparty_usd` | `number` | Introductory cap for a first payment |
| `min_counterparty_transactions` | `number` | Prior transactions before a relationship counts as established |
| `max_counterparty_last_seen_days` | `number` | Recency ceiling for the relationship. **No effect today** — see Notes |
| `max_amount_vs_avg_multiple` | `number` | Amount ceiling as a multiple of the historical average. Must be greater than zero |
| `max_counterparty_concentration_pct` | `number` | Concentration ceiling 0-100 |
| `max_outflow_vs_baseline_multiple` | `number` | Outflow ceiling as a multiple of normal |
| `max_data_age_seconds` | `number` | Freshness ceiling |
| `deny_on_missing_fields` | `string[]` | Field names whose unreported (`null`) value denies. **Two entries deny everything today** — see Notes |
## Rego Checks
Every rule this policy enforces:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `unknown_counterparty` | `require_known_counterparty` and unknown | New recipients, when the curator forbids them outright |
| `new_counterparty_over_limit` | unknown and amount over `max_new_counterparty_usd` | A first payment larger than the introductory cap |
| `counterparty_too_new` | known but fewer than `min_counterparty_transactions` prior txs | A relationship too thin to count as established |
| `stale_relationship` | last seen over `max_counterparty_last_seen_days` ago | A dormant counterparty reactivating. **Currently inert** |
| `amount_anomaly` | amount over `max_amount_vs_avg_multiple` × the historical average | A payment wildly out of scale with the relationship |
| `misconfigured_max_amount_vs_avg_multiple` | the multiple is absent or not greater than `0` | A multiplier that would silently disable the anomaly rule |
| `concentration_spike` | share over `max_counterparty_concentration_pct` | One destination suddenly dominating outflow |
| `outflow_above_baseline` | ratio over `max_outflow_vs_baseline_multiple` | The wallet draining faster than it normally does |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale intelligence |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A configured threshold quietly doing nothing |
### Amount Anomaly
A zero-or-null historical average **skips** this rule: multiplying by zero would make every payment infinitely anomalous. That leaves a genuine gap — a counterparty whose average is a true `0` admits any amount here, gated only by the other rules. Closing it needs a notion of "expected size" this API does not provide, so lean on `max_new_counterparty_usd` and `max_outflow_vs_baseline_multiple` instead.
```rego
deny contains "amount_anomaly" if {
v.counterparty_avg_usd != null
v.counterparty_avg_usd > 0
amount > v.counterparty_avg_usd * t.max_amount_vs_avg_multiple
}
```
### Misconfigured Multiplier
Fail closed on a bad multiplier: a `0` would silently *disable* the anomaly rule rather than tighten it. The check routes through a named helper because OPA hoists `t.max_amount_vs_avg_multiple` out of a `not ... > 0` into its own conjunct, so an **absent** param would make the whole rule body undefined instead of negating to true. Negating a named rule catches the absent case as well as the zero one.
```rego
deny contains "misconfigured_max_amount_vs_avg_multiple" if not valid_avg_multiple
valid_avg_multiple if t.max_amount_vs_avg_multiple > 0
```
### Missing Fields
Opt-in **per field**, which matters more here than in most packs: two of the five candidates are always `null` today, so a blanket switch would force a choice between requiring the fields you care about and denying every transaction.
```rego
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
```
### Final Allow Rule
The groundedness probes are load-bearing: every deny rule silent-skips on an undefined field, so an error envelope or a partial payload yields an **empty** deny set and a bare `count(deny) == 0` would fail *open*.
```rego
allow if {
not v.error
is_boolean(v.is_known_counterparty)
is_number(amount)
count(deny) == 0
}
```
## Complete Policy
```rego
package arkham_counterparty_activity
import future.keywords
default allow := false
t := data.params.arkham_counterparty
v := data.wasm.arkham_counterparty
amount := v.transaction_amount_usd
nullable_fields := {
"counterparty_last_seen_days": v.counterparty_last_seen_days,
"counterparty_avg_usd": v.counterparty_avg_usd,
"counterparty_concentration_pct": v.counterparty_concentration_pct,
"outflow_ratio": v.outflow_ratio,
"data_age_seconds": v.data_age_seconds,
}
deny contains "unknown_counterparty" if {
t.require_known_counterparty
v.is_known_counterparty == false
}
deny contains "new_counterparty_over_limit" if {
v.is_known_counterparty == false
amount > t.max_new_counterparty_usd
}
deny contains "counterparty_too_new" if {
v.is_known_counterparty == true
v.counterparty_transaction_count < t.min_counterparty_transactions
}
deny contains "stale_relationship" if {
v.counterparty_last_seen_days != null
v.counterparty_last_seen_days > t.max_counterparty_last_seen_days
}
deny contains "amount_anomaly" if {
v.counterparty_avg_usd != null
v.counterparty_avg_usd > 0
amount > v.counterparty_avg_usd * t.max_amount_vs_avg_multiple
}
deny contains "misconfigured_max_amount_vs_avg_multiple" if not valid_avg_multiple
valid_avg_multiple if t.max_amount_vs_avg_multiple > 0
deny contains "concentration_spike" if {
v.counterparty_concentration_pct != null
v.counterparty_concentration_pct > t.max_counterparty_concentration_pct
}
deny contains "outflow_above_baseline" if {
v.outflow_ratio != null
v.outflow_ratio > t.max_outflow_vs_baseline_multiple
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
allow if {
not v.error
is_boolean(v.is_known_counterparty)
is_number(amount)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.arkham_counterparty`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **`transaction_amount_usd` is caller-supplied and NOT attested.** It arrives through `wasmArgs`, so a caller controls it. The attested alternative — `input.value` — is native-token wei rather than USD, and arrives as a *string*. A curator needing a tamper-proof ceiling should pair this pack with a native-value cap in a composite.
* **Two rules are inert today.** Arkham's counterparties and flow endpoints expose no per-relationship timestamp and no observation timestamp, so `counterparty_last_seen_days` and `data_age_seconds` are always `null`; `stale_relationship` and `stale_data` fail soft. Both fields and rules are kept so they activate automatically once Arkham exposes the data.
* ⚠️ **Consequently, listing `counterparty_last_seen_days` or `data_age_seconds` in `deny_on_missing_fields` denies *every* transaction.** The list is per field precisely so that does not hold the others hostage: require `outflow_ratio` and `counterparty_avg_usd` and leave the two inert fields out.
* **`chains` is required.** Unscoped, `/flow/address` returns every chain's full daily history — over 1 MB for an active wallet, which exhausts the WASM heap. Scoping to one chain brings it to roughly 270 KB.
* **`history_window_days` materially changes the counterparty set.** A 90-day window on a wallet whose large relationships are older returns only small, recent ones — so an "established" counterparty can read as new. Widen the window to match the relationships you intend to recognise.
* **`max_amount_vs_avg_multiple` must be greater than zero.** The check lives in Rego rather than the params schema because `exclusiveMinimum` is outside the regorus-clean keyword set the AVS-side schema sticks to.
* `null` is the oracle's "not reported", deliberately distinct from `0`. Null optional fields fail soft; a **missing** key leaves the groundedness probes undefined and correctly blocks `allow`.
# Arkham Explainable Risk Exposure \[Use Arkham exposure paths in Newton policies to deny risk you can trace to a specific transaction route.]
This policy denies **risk exposure you can explain**, using [Arkham Intelligence](https://arkm.com/api). Rather than collapsing everything into one score, it reasons about the actual transaction routes connecting an address to a risky source: direct or near-hop exposure to a severe category denies on presence alone, more distant exposure denies only when materially large or recent, dust is ignored outright so a dusting attack cannot brick a wallet, and the deny set names the offending route so a reviewer can see *why*.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `arkham_risk` |
| Rego package | `arkham_risk_exposure` (the `--entrypoint` is `arkham_risk_exposure.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `ARKHAM_API_KEY` | Required | [arkm.com](https://arkm.com/api) |
Sent by the oracle as the `API-Key` request header.
## Data Inputs
Two GET requests against `https://api.arkm.com`: `/risk/address/{address}` for risk level, per-category scores and seed status, and `/risk/address/{address}/paths` for up to ten paths to risky sources, ranked by contributed USD.
The oracle deliberately emits Arkham's **raw** paths rather than a precomputed verdict. "Severe" is a curator notion that lives in `data.params` and the Rego filters on it — anything collapsed in the WASM would be a decision the curator can no longer see or tune.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `address` / `chain` | Echoed from `wasmArgs` | Audit |
| `risk_level` | Arkham headline risk level | Reviewer context |
| `max_score` | Highest category score, or `null` | Gated by `max_risk_score` |
| `category_scores` | `{category: score}` map, harvested from Arkham's flat `_score` sibling keys (`hacker_score`, `sanctioned_1hop_score`, …). Aggregate `max_score*` keys are excluded | Reviewer context |
| `top_risk_category` | Category driving the headline score | Reviewer context |
| `is_seed` | Whether the address **is** a known risky source, not merely exposed to one | Gated by `deny_on_seed` |
| `hop_distance` / `risk_weighted_incoming_usd` / `risk_weighted_outgoing_usd` | Additional Arkham risk context | Reviewer context |
| `paths[]` | `category`, `direction`, `hop_distance`, `seed_address`, `score`, `contributed_usd`, `nodes[]`. `contributed_pct`, `first_seen_days` and `last_seen_days` are **always `null`** — Arkham does not return them | The three path rules filter on these |
| `data_age_seconds` | Age of the Arkham observation, or `null` | Gated by `max_data_age_seconds` |
| `timestamp` | When this snapshot was taken | Audit |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: the address it screens is not something the SDK can read off the vault, so the curator supplies it per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `address` | Required | The address whose exposure is judged |
| `chain` | Optional | Arkham chain slug. Arkham indexes production networks only |
From the CLI or dashboard this goes in the simulate payload's `wasm_args`. From the SDK it goes in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
arkham_risk: { address: destination, chain: 'ethereum' },
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `severe_categories` | `string[]` | Categories treated as severe (e.g. `sanctions`, `hacker`, `mixer`). Configure in lowercase |
| `max_severe_hop_distance` | `number` | Hops within which severe exposure denies outright |
| `material_exposure_usd` | `number` | USD threshold for distant exposure |
| `recent_exposure_days` | `number` | Recency window for distant exposure. **No effect today** — see Notes |
| `dust_tolerance_usd` | `number` | Paths at or below this are ignored entirely. Strict `>`, so `0` considers every path |
| `deny_on_seed` | `boolean` | Deny when the address is itself a risky source |
| `max_risk_score` | `number` | Maximum tolerated headline score 0-100 |
| `max_data_age_seconds` | `number` | Freshness ceiling |
| `deny_on_missing_fields` | `string[]` | Field names whose unreported (`null`) value denies. `path_last_seen_days` is its own entry, covering undated exposure paths |
## Rego Checks
Paths are filtered in three stages — dust floor, then severity, then distance:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `seed_address` | `deny_on_seed` and the address is a seed | The address **is** the risky source |
| `severe_exposure_within_hops` | severe category within `max_severe_hop_distance` | Direct and near-hop exposure, at any size above dust |
| `material_distant_exposure` | severe, beyond the hop limit, over `material_exposure_usd` | Large exposure further out in the graph |
| `recent_distant_exposure` | severe, beyond the hop limit, within `recent_exposure_days` | Fresh exposure further out. **Currently inert** |
| `risk_score_above_max` | score over `max_risk_score` | Elevated headline risk, independent of paths |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale intelligence |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A configured threshold quietly doing nothing |
| `missing_path_last_seen_days` | `path_last_seen_days` is listed and a severe distant path has no date | Undated exposure slipping past the recency rule entirely |
### Path Classification
Dust is filtered **first**, so a dusting attack cannot manufacture exposure. Severity then narrows to the curator's categories, and distance splits the remainder into "denies on presence" versus "denies only if large or recent".
```rego
material_paths contains p if {
some p in v.paths
p.contributed_usd > t.dust_tolerance_usd
}
severe_paths contains p if {
some p in material_paths
p.category in t.severe_categories
}
severe_near_paths contains p if {
some p in severe_paths
p.hop_distance <= t.max_severe_hop_distance
}
```
### Distant Exposure
Two rules on value and recency, plus a third for the strict-mode case where Arkham reports no date at all — an undated path slips past the recency rule, so the missing date is itself the signal a curator asked to act on.
```rego
material_distant_paths contains p if {
some p in severe_paths
p.hop_distance > t.max_severe_hop_distance
p.contributed_usd > t.material_exposure_usd
}
undated_distant_paths contains p if {
some p in severe_paths
p.hop_distance > t.max_severe_hop_distance
p.last_seen_days == null
}
deny contains "missing_path_last_seen_days" if {
"path_last_seen_days" in t.deny_on_missing_fields
count(undated_distant_paths) > 0
}
```
### Explainability
A separate `risk_paths` rule renders each offending route as `" exposure via at hop(s), $ contributed"`. This is **not** evaluated on-chain — the AVS entrypoint is `arkham_risk_exposure.allow` and nothing else. It exists for `opa eval`, local simulation, and composites that want to surface a reason to an operator.
```rego
risk_paths contains detail if {
some p in offending_paths
detail := sprintf(
"%v exposure via %v at %v hop(s), $%v contributed",
[p.category, p.seed_address, p.hop_distance, p.contributed_usd],
)
}
```
### Final Allow Rule
`is_array(v.paths)` is the load-bearing probe here: an error envelope has no `paths` key, every path rule then yields an empty set, and a bare `count(deny) == 0` would fail *open* on exactly that payload.
```rego
allow if {
not v.error
is_boolean(v.is_seed)
is_array(v.paths)
count(deny) == 0
}
```
## Complete Policy
```rego
package arkham_risk_exposure
import future.keywords
default allow := false
t := data.params.arkham_risk
v := data.wasm.arkham_risk
nullable_fields := {
"max_score": v.max_score,
"data_age_seconds": v.data_age_seconds,
}
material_paths contains p if {
some p in v.paths
p.contributed_usd > t.dust_tolerance_usd
}
severe_paths contains p if {
some p in material_paths
p.category in t.severe_categories
}
severe_near_paths contains p if {
some p in severe_paths
p.hop_distance <= t.max_severe_hop_distance
}
material_distant_paths contains p if {
some p in severe_paths
p.hop_distance > t.max_severe_hop_distance
p.contributed_usd > t.material_exposure_usd
}
recent_distant_paths contains p if {
some p in severe_paths
p.hop_distance > t.max_severe_hop_distance
p.last_seen_days != null
p.last_seen_days <= t.recent_exposure_days
}
undated_distant_paths contains p if {
some p in severe_paths
p.hop_distance > t.max_severe_hop_distance
p.last_seen_days == null
}
offending_paths := (severe_near_paths | material_distant_paths) | recent_distant_paths
risk_paths contains detail if {
some p in offending_paths
detail := sprintf(
"%v exposure via %v at %v hop(s), $%v contributed",
[p.category, p.seed_address, p.hop_distance, p.contributed_usd],
)
}
deny contains "seed_address" if {
t.deny_on_seed
v.is_seed == true
}
deny contains "severe_exposure_within_hops" if count(severe_near_paths) > 0
deny contains "material_distant_exposure" if count(material_distant_paths) > 0
deny contains "recent_distant_exposure" if count(recent_distant_paths) > 0
deny contains "risk_score_above_max" if {
v.max_score != null
v.max_score > t.max_risk_score
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
deny contains "missing_path_last_seen_days" if {
"path_last_seen_days" in t.deny_on_missing_fields
count(undated_distant_paths) > 0
}
allow if {
not v.error
is_boolean(v.is_seed)
is_array(v.paths)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.arkham_risk`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **"Severe" is yours, not Arkham's.** The oracle emits raw exposure paths; `severe_categories` decides what counts. That is what makes the denial explainable, and what lets you tighten it without a redeploy.
* **The `recent_distant_exposure` rule is currently inert.** Arkham's paths endpoint returns no first/last transaction timestamps, so `last_seen_days` is always `null` and the rule fails soft; `recent_exposure_days` has no effect today. Distant exposure is still caught by `material_distant_exposure` on value.
* ⚠️ **Consequently, listing `path_last_seen_days` in `deny_on_missing_fields` denies any address with a severe path beyond the hop limit.** Because the list is per field, you can require `max_score` without taking that on.
* **`contributed_pct` is also unavailable**, so size-relative thresholds must use `contributed_usd`.
* **Dust tolerance is a strict `>` comparison**, so a path contributing exactly `dust_tolerance_usd` counts as dust and is ignored. Set it to `0` to consider every path.
* A category outside `severe_categories` never trips the hop, materiality or recency rules, however large or direct. Only `max_risk_score` constrains it.
* Path categories and seed addresses are lowercased by the oracle, so configure `severe_categories` in lowercase.
* The lookup chain is **not** the execution chain. Arkham indexes production networks only, so on a testnet `chain` must name a real mainnet or the oracle has no data and the policy fails closed.
# Pharos Risk-Adjusted Treasury Gate \[Use Pharos stablecoin data in Newton policies to admit an asset only when peg, stress, redemption, and exit capacity are all healthy.]
This policy admits a stablecoin into a treasury only when it is healthy on **all four** dimensions [Pharos](https://pharos.watch) measures — not on price alone: no active depeg and current deviation within tolerance, stress below threshold (the earlier, broader signal that rises before a full depeg), an approved redemption route so the position has a credible exit at par, and exit capacity comfortably larger than the position being taken on.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `pharos_treasury` |
| Rego package | `pharos_treasury_risk` (the `--entrypoint` is `pharos_treasury_risk.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `PHAROS_API_KEY` | Required | [pharos.watch](https://pharos.watch) |
Sent by the oracle as the `X-API-Key` request header.
:::warning
This is the heaviest pack in the catalog: **five API calls per evaluation**, against a self-serve Pharos rate limit of 30 requests per minute.
:::
## Data Inputs
Five GET requests against `https://api.pharos.watch`. Four of the five are scoped to a single asset, using per-asset endpoints that are orders of magnitude smaller than the bulk alternatives:
| Call | Size | Why this one |
| --- | --- | --- |
| `/api/stablecoin-summary/{id}` | ~0.6 KB | Identity, `priceUsd`, peg type/mechanism, supply, chain count. Replaces `/api/stablecoin/{id}`, a 345 KB supply time series with no price in it |
| `/api/depeg-events?stablecoin={id}&active=true&includePending=true` | ~0.3 KB | Confirmed active incidents |
| `/api/stress-signals?stablecoin={id}&days={n}` | ~7.5 KB | Stress score, band, per-signal sub-scores |
| `/api/dex-liquidity-history?stablecoin={id}&days=1` | ~47 KB | Liquidity score, TVL, and the `exitRouteObservations` capacity curves. Replaces `/api/dex-liquidity` (2.98 MB, all assets, no query parameters) — a 63× reduction |
| `/api/redemption-backstops` | 1.1 MB | The one endpoint with **no filter at all**. This coin's object is cut out of the raw text and only that ~3 KB parsed; `JSON.parse` on the full document exhausts the WASM heap |
**Reading exit capacity correctly.** Each observation's top-level `executableUsd` is capped at its `requestedNotionalUsd`, so a $1M simulation that fills completely reports $1M — which says nothing about the ceiling. The real ladder is `capacityCurve`: successive notionals (100k → 1M → 10M → 25M) each with the `executionCostBps` actually incurred. The oracle walks that curve and takes the largest rung within the caller's `max_cost_bps`. An observation's own `maxCostBps` is the bound the *simulation ran under* (always 200), not the cost incurred — filtering on it would use the wrong number. Across routes it takes the **max**, not the sum: routes overlap, so summing double-counts shared liquidity.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `stablecoin_id` / `symbol` / `name` | Asset identity | Audit |
| `peg_type` / `peg_mechanism` | e.g. `peggedUSD`, `fiat-backed` | Reviewer context |
| `price` / `price_confidence` | Aggregated price and Pharos's confidence in it | Reviewer context |
| `peg_target` / `peg_deviation_bps` | Target (1) and **signed** deviation. `null` when no source could resolve a price — never `0`, which would read as a perfect peg | Gated symmetrically via `abs()` |
| `depeg_active` / `depeg_severity` / `depeg_direction` | Confirmed incident state | Gated by `deny_on_active_depeg` |
| `depeg_pending_count` | Unconfirmed threshold crossings; does **not** trip `depeg_active` | Reviewer context |
| `supply_usd` / `supply_change_7d_usd` / `chain_count` | Market footprint | Reviewer context |
| `stress_score` / `stress_band` / `stress_signals` | Stress posture; `stress_signals` is the raw `{name: 0-100}` map | Gated by `max_stress_score` |
| `active_stress_indicators` | Convenience view of signals at or above 50. No rule depends on it | Reviewer context |
| `liquidity_score` / `effective_tvl_usd` / `volume_24h_usd` / `pool_count` | Liquidity depth | Gated by `min_liquidity_score` |
| `exit_capacity_usd` / `exit_capacity_multiple` | Capacity within `max_cost_bps`, and its ratio to the position | Gated by `min_exit_capacity_multiple` |
| `redemption_available` / `redemption_route_family` / `redemption_access_model` / `redemption_route_status` / `redemption_score` | Redemption posture | Four separate route rules |
| `immediate_capacity_usd` | Redeemable right now | Reviewer context |
| `price_data_age_seconds` / `stress_data_age_seconds` / `liquidity_data_age_seconds` / `redemption_data_age_seconds` | Per-source ages — these move on very different clocks | Diagnosing which feed drives staleness |
| `data_age_seconds` | The **oldest** of the above | Gated by `max_data_age_seconds` |
| `timestamp` | When this snapshot was taken | Audit |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: nothing on-chain maps an ERC-20 address to a Pharos stablecoin id, so the curator supplies it per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `stablecoin_id` | Required | `ticker-issuer` form, e.g. `usdc-circle` |
| `transaction_amount_usd` | Optional, defaults to `0` | Position size. Omit and `exit_capacity_multiple` is `null` |
| `stress_lookback_days` | Optional, defaults to `7` | Stress window |
| `max_cost_bps` | Optional | Execution-cost bound for the capacity curve walk |
From the CLI or dashboard these go in the simulate payload's `wasm_args`. From the SDK they go in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
pharos_treasury: {
stablecoin_id: 'usdc-circle',
transaction_amount_usd: 1_000_000,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `deny_on_active_depeg` | `boolean` | Deny outright during a confirmed depeg. Contrast `pharos_safe_mode`'s `safe_mode_on_active_depeg`, which only *engages* safe mode |
| `max_peg_deviation_bps` | `number` | Symmetric deviation tolerance in bps |
| `max_stress_score` | `number` | Stress ceiling 0-100 |
| `require_redemption` | `boolean` | Require a working redemption route |
| `approved_redemption_route_families` | `string[]` | Acceptable route families |
| `approved_access_models` | `string[]` | Acceptable access models |
| `required_route_status` | `string` | Status a working route must report. Pharos reports **`open`**, not `active` |
| `min_exit_capacity_multiple` | `number` | Required exit capacity as a multiple of the position |
| `min_liquidity_score` | `number` | Liquidity score floor 0-100 |
| `max_data_age_seconds` | `number` | Freshness ceiling on the oldest source. Read the Notes before tightening it |
| `deny_on_missing_fields` | `string[]` | Field names whose unreported (`null`) value denies. Listing `exit_capacity_multiple` requires every call to supply a position size |
## Rego Checks
Every rule this policy enforces:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `active_depeg` | `deny_on_active_depeg` and an incident is active | A confirmed depeg in progress |
| `peg_deviation_above_max` | `abs(deviation)` over `max_peg_deviation_bps` | Drift off peg before an incident is declared |
| `missing_peg_deviation_bps` | deviation is `null` | An unresolvable price. **Cannot be opted out of** |
| `stress_above_max` | stress over `max_stress_score` | Developing stress ahead of a depeg |
| `redemption_unavailable` | `require_redemption` and no route | No credible exit at par |
| `unapproved_redemption_route` | route family not approved | Redemption through an out-of-policy mechanism |
| `unapproved_access_model` | access model not approved | The wrong parties can redeem |
| `route_status_not_approved` | status is not `required_route_status` | A route reported as impaired or suspended |
| `insufficient_exit_capacity` | multiple below `min_exit_capacity_multiple` | A position that cannot realistically be exited |
| `liquidity_score_below_min` | score below `min_liquidity_score` | Thin or fragile DEX liquidity |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale data |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A configured threshold quietly doing nothing |
### Peg Deviation
Deviation is **signed** by the oracle (negative = below peg) because the direction matters to a reader; the threshold is symmetric via `abs()`. A naive unsigned comparison would miss the direction that matters most.
```rego
deny contains "peg_deviation_above_max" if abs(v.peg_deviation_bps) > t.max_peg_deviation_bps
```
### Unresolvable Peg
Unconditional, and deliberately **not** a `deny_on_missing_fields` entry: this pack's peg rule is built on the deviation, so an unresolvable one is never tolerable. The oracle used to report `0` here when it could not resolve a price, which reads as a perfect peg — the safest possible input — so it now reports `null`.
```rego
deny contains "missing_peg_deviation_bps" if v.peg_deviation_bps == null
```
### Route Status
Pharos reports **`open`** for a working route, not `active`. A curator who guesses `active` denies every healthy asset, which is why the value is a param rather than a constant — and why the pack pins it with a test.
```rego
deny contains "route_status_not_approved" if {
v.redemption_route_status != null
v.redemption_route_status != t.required_route_status
}
```
### Exit Capacity
The differentiated Pharos signal: can this position actually be exited? Passing no `transaction_amount_usd` leaves the multiple `null` rather than infinity, so the rule fails soft rather than reading an unbounded ratio as safe.
```rego
deny contains "insufficient_exit_capacity" if {
v.exit_capacity_multiple != null
v.exit_capacity_multiple < t.min_exit_capacity_multiple
}
```
### Final Allow Rule
The groundedness probes are load-bearing. `is_number(v.peg_deviation_bps)` also grounds the `abs()` in the peg rule, which is *undefined* rather than false on a non-number.
```rego
allow if {
not v.error
is_boolean(v.depeg_active)
is_boolean(v.redemption_available)
is_number(v.peg_deviation_bps)
count(deny) == 0
}
```
## Complete Policy
```rego
package pharos_treasury_risk
import future.keywords
default allow := false
t := data.params.pharos_treasury
v := data.wasm.pharos_treasury
nullable_fields := {
"stress_score": v.stress_score,
"redemption_route_family": v.redemption_route_family,
"redemption_access_model": v.redemption_access_model,
"redemption_route_status": v.redemption_route_status,
"exit_capacity_multiple": v.exit_capacity_multiple,
"liquidity_score": v.liquidity_score,
"data_age_seconds": v.data_age_seconds,
}
deny contains "active_depeg" if {
t.deny_on_active_depeg
v.depeg_active == true
}
deny contains "peg_deviation_above_max" if abs(v.peg_deviation_bps) > t.max_peg_deviation_bps
deny contains "missing_peg_deviation_bps" if v.peg_deviation_bps == null
deny contains "stress_above_max" if {
v.stress_score != null
v.stress_score > t.max_stress_score
}
deny contains "redemption_unavailable" if {
t.require_redemption
v.redemption_available == false
}
deny contains "unapproved_redemption_route" if {
v.redemption_route_family != null
not v.redemption_route_family in t.approved_redemption_route_families
}
deny contains "unapproved_access_model" if {
v.redemption_access_model != null
not v.redemption_access_model in t.approved_access_models
}
deny contains "route_status_not_approved" if {
v.redemption_route_status != null
v.redemption_route_status != t.required_route_status
}
deny contains "insufficient_exit_capacity" if {
v.exit_capacity_multiple != null
v.exit_capacity_multiple < t.min_exit_capacity_multiple
}
deny contains "liquidity_score_below_min" if {
v.liquidity_score != null
v.liquidity_score < t.min_liquidity_score
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
allow if {
not v.error
is_boolean(v.depeg_active)
is_boolean(v.redemption_available)
is_number(v.peg_deviation_bps)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.pharos_treasury`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **`required_route_status` is `"open"`, not `"active"`.** Configuring `active` — the intuitive guess — denies every healthy asset. A test pins this.
* **Freshness ceilings need care.** The four sources move on very different clocks: price and stress refresh in minutes, `dex-liquidity-history` is a **daily bucket** (up to ~24h old by construction), and redemption-backstops lags by hours. `data_age_seconds` is the oldest of them, so a ceiling below ~24h denies permanently. The per-source ages are emitted separately so you can see which feed is actually driving it.
* **`transaction_amount_usd` is caller-supplied and NOT attested.** The attested alternative, `input.value`, is native-token wei (and a *string*), not USD. Pair with a native-value cap in a composite if you need a tamper-proof ceiling.
* `null` is the oracle's "not reported", deliberately distinct from `0`. Null optional fields fail soft; a **missing** key leaves the groundedness probes undefined and correctly blocks `allow`.
* **`peg_deviation_bps` is the exception** — it denies unconditionally when `null`, as `missing_peg_deviation_bps`, and is not a `deny_on_missing_fields` entry.
* Listing `exit_capacity_multiple` in `deny_on_missing_fields` requires every call to supply a position size. Because the list is per field, you can require the rest without that.
* **This pack rides within about 40% of a hard runtime memory limit.** `/api/redemption-backstops` has no filter, so the oracle downloads all ~1.14 MB and slices this coin's ~3 KB object out of the raw text. A `MAX_SLICEABLE_BYTES` guard trips first and returns a readable error — which fails closed — rather than trapping the component with no verdict. The durable fix is a per-asset endpoint from Pharos.
# Pharos Stablecoin Safe Mode \[Use Pharos stress signals in Newton policies to block exposure-increasing actions under stablecoin stress while leaving the exits open.]
This policy is a **graduated response** to stablecoin stress, using [Pharos](https://pharos.watch). When stress rises or a depeg goes active, it stops actions that *increase* exposure while leaving the exits open: deposits and mints are blocked, withdrawals, redemptions and repayments are always permitted, and swaps are permitted only into a curator-approved safer asset. That is the point — an application gets a graduated response instead of a blunt protocol-wide pause.
:::note
This is the only pack in the catalog that reads the **attested intent** as well as oracle data. A graduated response has to know whether the caller is adding exposure or shedding it, and that fact must come from the signed intent rather than from anything the oracle asserts.
:::
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `pharos_safe_mode` |
| Rego package | `pharos_safe_mode` (the `--entrypoint` is `pharos_safe_mode.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `PHAROS_API_KEY` | Required | [pharos.watch](https://pharos.watch) |
Sent by the oracle as the `X-API-Key` request header.
## Data Inputs
Three GET requests against `https://api.pharos.watch`, all filtered by `stablecoin`, totalling roughly 11 KB per evaluation — the lightest Pharos pack: `/api/stress-signals?stablecoin={id}&days={n}`, `/api/depeg-events?stablecoin={id}&active=true&includePending=true`, and `/api/mint-burn-flows?stablecoin={id}&hours={n}`.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `stablecoin_id` / `symbol` | Asset identity | Audit |
| `stress_score` / `stress_band` | From `current.score` / `current.band`, lowercased | Gated by `safe_mode_stress_threshold` |
| `stress_signals` | Raw `{name: 0-100}` map from `current.signals`, skipping unavailable ones | Reviewer context |
| `active_indicators` | Convenience view of signals at or above 50. No rule depends on it | Reviewer context |
| `age_classification` | Pharos's own freshness label (e.g. `fresh`) | Reviewer context |
| `depeg_active` / `depeg_severity` / `peg_deviation_bps` | Active incident state. `peg_deviation_bps` is informational here and is `null`, never `0`, when unresolvable | `depeg_active` engages safe mode |
| `net_flow_usd` / `mint_volume_usd` / `burn_volume_usd` | Mint/burn flow | Reviewer context |
| `flow_stress_score` / `burn_surge` | From the stress `flow` signal, which already folds burn surge and burn/mint ratio against a baseline | Reviewer context |
| `flow_anomaly` | True when `flow_stress_score` is at or above 50 | Reviewer context |
| `data_age_seconds` | Oldest age across all three responses | Gated by `max_data_age_seconds` |
| `timestamp` | When this snapshot was taken | Audit |
### The attested intent
The classifier is `input.function.name` — the bare function name, verified against newton-cli 0.5.2. The full `input` shape:
```json
{
"from": "0x...", "to": "0x...", "value": "1000000000000000000",
"chain_id": "1",
"data": "0xb460af94...",
"function_signature": "0x7769746864726177...",
"function": { "name": "withdraw", "type": "function",
"stateMutability": "nonpayable", "inputs": [], "outputs": [] },
"decoded_function_signature": "function withdraw(uint256, address, address)",
"decoded_function_arguments": ["1000000000000000000", "0x...", "0x..."]
}
```
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: nothing on-chain maps an ERC-20 address to a Pharos stablecoin id, so the curator supplies it per call. Note there is deliberately **no amount** — this pack classifies the *action*, not the size.
| Field | Required? | Notes |
| --- | --- | --- |
| `stablecoin_id` | Required | `ticker-issuer` form, e.g. `usdc-circle` |
| `stress_lookback_days` | Optional, defaults to `7` | Stress window |
| `flow_window_hours` | Optional, defaults to `24` | Mint/burn flow window |
From the CLI or dashboard these go in the simulate payload's `wasm_args`. From the SDK they go in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
pharos_safe_mode: { stablecoin_id: 'usdc-circle' },
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `safe_mode_stress_threshold` | `number` | Stress score at or above which safe mode engages |
| `safe_mode_on_active_depeg` | `boolean` | Let an active depeg engage safe mode on its own. **Engages** safe mode — does not deny outright |
| `exposure_increasing_functions` | `string[]` | Bare names blocked in safe mode (e.g. `deposit`, `mint`) |
| `exposure_reducing_functions` | `string[]` | Bare names always permitted (e.g. `withdraw`, `redeem`) |
| `swap_functions` | `string[]` | Bare names treated as swaps |
| `swap_destination_arg_index` | `object` | Map from swap function name to the index into `decoded_function_arguments` holding the destination token |
| `approved_safe_assets` | `string[]` | Token addresses acceptable as swap destinations. Compared case-insensitively |
| `max_data_age_seconds` | `number` | Freshness ceiling |
| `deny_on_missing_fields` | `string[]` | Field names (`stress_score`, `data_age_seconds`) whose unreported (`null`) value denies rather than passes |
:::warning
The three function lists are an **allowlist**. A function in none of them denies as `unclassified_function` — in a calm market too, not just under stress. Enumerate every function your vault actually calls.
:::
## Rego Checks
Every rule this policy enforces:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `unclassified_function` | the function is in none of the three lists | A novel call that would otherwise sail past every rule |
| `safe_mode_blocks_exposure_increase` | safe mode and an exposure-increasing call | Adding exposure to a stressed asset |
| `unapproved_swap_destination` | safe mode, a swap, destination not approved | Rotating out of a stressed asset into another risky one |
| `missing_swap_destination_index` | a listed swap function has no mapped index | A swap whose destination cannot be evaluated at all |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale data |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A stress threshold quietly doing nothing |
### Safe-Mode Trigger
Named `safe_mode_on_*`, not `deny_on_*`, because that is what it does: an active depeg **engages safe mode**, it does not deny outright. Exposure-reducing calls stay permitted throughout. If you want a depeg to stop everything, [`pharos_treasury`](/developers/vaults/policies/pharos-treasury)'s `deny_on_active_depeg` is the blunt instrument; the two are deliberately named apart.
```rego
safe_mode if {
t.safe_mode_on_active_depeg
v.depeg_active == true
}
safe_mode if {
v.stress_score != null
v.stress_score >= t.safe_mode_stress_threshold
}
```
### Action Classification
Fail closed on an action the curator has not classified. Without `unclassified_function`, a novel function name would sail past every rule below.
```rego
fn := input.function.name
is_reducing if fn in t.exposure_reducing_functions
is_increasing if fn in t.exposure_increasing_functions
is_swap if fn in t.swap_functions
classified if is_reducing
classified if is_increasing
classified if is_swap
deny contains "unclassified_function" if not classified
deny contains "safe_mode_blocks_exposure_increase" if {
safe_mode
is_increasing
}
```
### Swap Destination
Keyed by function name, because swap ABIs disagree on where the destination sits: `swap(tokenIn, tokenOut, amt)` is index 1, `swapExactIn(tokenOut, amt)` is 0, and a Curve-style `exchange(i, j, dx)` has no address argument at all and belongs unmapped.
`has_swap_index` is a named helper for the same reason as `arkham_counterparty`'s multiplier guard: OPA hoists the ref out of a bare `not t.swap_destination_arg_index[fn]`, so an unmapped function would leave the rule *undefined* rather than negating to true. `is_number` also keeps a legitimate index of `0` valid.
```rego
swap_destination_index := t.swap_destination_arg_index[fn]
swap_destination := lower(input.decoded_function_arguments[swap_destination_index])
has_swap_index if is_number(swap_destination_index)
swap_destination_approved if {
some asset in t.approved_safe_assets
lower(asset) == swap_destination
}
deny contains "unapproved_swap_destination" if {
safe_mode
is_swap
not swap_destination_approved
}
deny contains "missing_swap_destination_index" if {
is_swap
not has_swap_index
}
```
### Final Allow Rule
`is_boolean(v.depeg_active)` grounds the oracle payload and `is_string(fn)` grounds the intent. Note what is deliberately **absent**: there is no blanket stress ceiling that denies everything. Withdrawals and redemptions stay permitted at any stress level.
```rego
allow if {
not v.error
is_boolean(v.depeg_active)
is_string(fn)
count(deny) == 0
}
```
## Complete Policy
```rego
package pharos_safe_mode
import future.keywords
default allow := false
t := data.params.pharos_safe_mode
v := data.wasm.pharos_safe_mode
nullable_fields := {
"stress_score": v.stress_score,
"data_age_seconds": v.data_age_seconds,
}
fn := input.function.name
safe_mode if {
t.safe_mode_on_active_depeg
v.depeg_active == true
}
safe_mode if {
v.stress_score != null
v.stress_score >= t.safe_mode_stress_threshold
}
is_reducing if fn in t.exposure_reducing_functions
is_increasing if fn in t.exposure_increasing_functions
is_swap if fn in t.swap_functions
classified if is_reducing
classified if is_increasing
classified if is_swap
swap_destination_index := t.swap_destination_arg_index[fn]
swap_destination := lower(input.decoded_function_arguments[swap_destination_index])
has_swap_index if is_number(swap_destination_index)
swap_destination_approved if {
some asset in t.approved_safe_assets
lower(asset) == swap_destination
}
deny contains "unclassified_function" if not classified
deny contains "safe_mode_blocks_exposure_increase" if {
safe_mode
is_increasing
}
deny contains "unapproved_swap_destination" if {
safe_mode
is_swap
not swap_destination_approved
}
deny contains "missing_swap_destination_index" if {
is_swap
not has_swap_index
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
allow if {
not v.error
is_boolean(v.depeg_active)
is_string(fn)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.pharos_safe_mode`. See [Policies](/developers/vaults/sdk/policies).
:::warning
Composing this pack with [`pharos_treasury`](/developers/vaults/policies/pharos-treasury) changes the **shape** of the response, not just its strictness. `pharos_treasury.deny_on_active_depeg` denies everything during a depeg, including the unwind this pack would have permitted. AND-composition takes the stricter of the two, so set `deny_on_active_depeg: false` if you want the graduated behaviour to survive composition.
:::
## Notes
* **Unclassified functions deny, always** — including in a calm market. The three function lists are an allowlist, not a safe-mode-only concern.
* **`swap_destination_arg_index` is keyed per function**, because swap ABIs disagree on where the destination sits. Give each name in `swap_functions` its own entry — `{"swap": 1, "swapExactIn": 0}`.
* **A swap function with no mapped index denies** as `missing_swap_destination_index`, in a calm market as well as in safe mode: the policy cannot evaluate the destination at all, so it fails closed rather than waving the call through. Leave a Curve-style `exchange` unmapped and it is denied, not misread.
* **An out-of-range index also fails closed.** The destination is left undefined, which denies rather than reading as approved.
* Swap destinations are compared **case-insensitively**: the intent lowercases addresses while curator params are typically checksummed.
* `null` is the oracle's "not reported", deliberately distinct from `0`. Worth listing `stress_score` in `deny_on_missing_fields` if you rely on `safe_mode_stress_threshold` — without a score, safe mode can only ever engage via the depeg branch.
* `input.chain_id` arrives as a *string* and is only populated when the intent JSON sets `chainId` (camelCase in, snake\_case out). The `--chain-id` CLI flag does **not** populate it, so no rule here depends on it.
* **`/api/mint-burn-flows` carries no anomaly flag of its own.** Pharos's judgement of flow abnormality lives in the stress `flow` signal, so the oracle uses that rather than inventing a threshold over raw mint/burn volumes.
* Stress data is nested under `current` (`current.score`, `current.band`, `current.signals`) — not at the top level.
# Pharos Redemption-Backed Asset Gate \[Use Pharos redemption data in Newton policies to admit a stablecoin only when it can actually be redeemed at par.]
This policy admits a stablecoin only when it can actually be **redeemed at par**, using [Pharos](https://pharos.watch). Market price says nothing about whether a holder can convert back to the underlying, so this pack gates on the redemption path itself: direct redemption must be available, the provider and mechanism must be approved, applicable limits must support the intended position size, and the oracle response must be current.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `pharos_redemption` |
| Rego package | `pharos_redemption_backing` (the `--entrypoint` is `pharos_redemption_backing.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `PHAROS_API_KEY` | Required | [pharos.watch](https://pharos.watch) |
Sent by the oracle as the `X-API-Key` request header.
## Data Inputs
Two GET requests against `https://api.pharos.watch`:
1. `/api/redemption-backstops` — the redemption map. This endpoint takes **no query parameters at all** and returns all ~328 coins as a 1.1 MB document. `JSON.parse` on that exhausts the WASM heap, so this coin's object is cut out of the **raw text** and only that ~3 KB is parsed.
2. `/api/stablecoin-reserves/{id}` — ~1.4 KB, reserve composition and provenance.
"Available" means Pharos actually reported a route for this asset — an absent entry is a genuine "no redemption path", not a soft null.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `stablecoin_id` / `symbol` / `issuer` | Identity, derived from the `ticker-issuer` id | Audit |
| `redemption_available` | Whether a route was reported at all | Gated by `require_redemption_available` |
| `route_family` / `access_model` / `settlement_model` / `execution_model` | How redemption works, who may use it, how it settles | Three separate approval rules |
| `route_status` | Route health. Pharos reports **`open`** for a working route, not `active` | Gated by `required_route_status` |
| `holder_eligibility` / `provider` / `source_mode` | Who qualifies, and who Pharos attributes the route to | Reviewer context |
| `immediate_capacity_usd` / `modeled_exit_size_usd` | What can be redeemed now, and the size Pharos modelled | Basis for the capacity multiple |
| `capacity_multiple` | `immediate_capacity_usd / transaction_amount_usd`, or `null` when no amount was supplied | Gated by `min_capacity_multiple` |
| `capacity_confidence` | Qualitative band (e.g. `documented-bound`) — a **string**, not a number | Gated by `approved_capacity_confidence` |
| `route_score` / `access_score` / `settlement_score` / `capacity_score` | Pharos's 0-100 quality measures | `route_score` gated by `min_route_score` |
| `fee_bps` / `queue_enabled` | Cost and whether redemptions queue | Reviewer context |
| `reserve_composition` | `{slice name: percentage}` | Reviewer context |
| `reserve_elevated_risk_pct` | Share of reserves Pharos rates worse than low risk | Gated by `max_reserve_elevated_risk_pct` |
| `reserve_mode` / `reserve_source` / `reserve_sync_status` / `reserve_stale` | Backing provenance | Reviewer context |
| `data_age_seconds` | Oldest age across both responses | Gated by `max_data_age_seconds` |
| `timestamp` | When this snapshot was taken | Audit |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: nothing on-chain maps an ERC-20 address to a Pharos stablecoin id, so the curator supplies it per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `stablecoin_id` | Required | `ticker-issuer` form, e.g. `usdc-circle` |
| `transaction_amount_usd` | Optional, defaults to `0` | Position size. Omit and `capacity_multiple` is `null` |
From the CLI or dashboard these go in the simulate payload's `wasm_args`. From the SDK they go in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
pharos_redemption: {
stablecoin_id: 'usdc-circle',
transaction_amount_usd: 1_000_000,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `require_redemption_available` | `boolean` | Require a working redemption route |
| `approved_route_families` | `string[]` | Acceptable route families (observed: `offchain-issuer`) |
| `approved_access_models` | `string[]` | Acceptable access models (observed: `issuer-api`) |
| `approved_settlement_models` | `string[]` | Acceptable settlement models (observed: `same-day`) |
| `required_route_status` | `string` | Status a working route must report — **`open`**, not `active` |
| `min_route_score` | `number` | Minimum 0-100 route score |
| `approved_capacity_confidence` | `string[]` | Acceptable confidence bands; an empty array disables the check |
| `min_capacity_multiple` | `number` | Required immediate capacity as a multiple of the position |
| `max_reserve_elevated_risk_pct` | `number` | Max share of reserves rated worse than low risk |
| `max_data_age_seconds` | `number` | Freshness ceiling; the feed lags hours, so keep this generous |
| `deny_on_missing_fields` | `string[]` | Field names whose unreported (`null`) value denies. Listing `capacity_multiple` requires every call to supply a position size |
## Rego Checks
Every rule this policy enforces:
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `redemption_unavailable` | `require_redemption_available` and no route | No redemption path at all |
| `unapproved_route_family` | family not approved | Redemption through an out-of-policy mechanism |
| `unapproved_access_model` | access model not approved | The wrong parties can redeem |
| `unapproved_settlement_model` | settlement model not approved | Settlement slower than the curator accepts |
| `route_status_not_approved` | status is not `required_route_status` | An impaired or suspended route |
| `position_exceeds_capacity` | multiple below `min_capacity_multiple` | A position larger than what can actually be redeemed now |
| `low_route_score` | score below `min_route_score` | A route Pharos rates poorly |
| `unapproved_capacity_confidence` | band not in `approved_capacity_confidence` | Capacity Pharos cannot evidence |
| `reserve_risk_above_max` | elevated-risk share over `max_reserve_elevated_risk_pct` | Backing concentrated in riskier assets |
| `stale_data` | age over `max_data_age_seconds` | Decisions made on stale data |
| `missing_` | the field is named in `deny_on_missing_fields` and the oracle reported `null` | A configured threshold quietly doing nothing |
### Route Status
Pharos reports **`open`** for a working route, not `active`. Configuring `active` — the intuitive guess — denies every healthy asset. A test pins this.
```rego
deny contains "route_status_not_approved" if {
v.route_status != null
v.route_status != t.required_route_status
}
```
### Position Sizing
Sizing is against **immediate** capacity: Pharos publishes no daily limit and no redemption minimum on this endpoint. Passing no amount leaves the multiple `null` and the rule fails soft.
```rego
deny contains "position_exceeds_capacity" if {
v.capacity_multiple != null
v.capacity_multiple < t.min_capacity_multiple
}
```
### Capacity Confidence
`capacity_confidence` is a qualitative **string** band, not a number, so this is a membership check rather than a floor. The `count(...) > 0` guard is what lets an empty `approved_capacity_confidence` disable the check entirely.
```rego
deny contains "unapproved_capacity_confidence" if {
count(t.approved_capacity_confidence) > 0
v.capacity_confidence != null
not v.capacity_confidence in t.approved_capacity_confidence
}
```
### Final Allow Rule
The groundedness probe is load-bearing: every deny rule silent-skips on an undefined field, so an error envelope yields an **empty** deny set and a bare `count(deny) == 0` would fail *open*.
```rego
allow if {
not v.error
is_boolean(v.redemption_available)
count(deny) == 0
}
```
## Complete Policy
```rego
package pharos_redemption_backing
import future.keywords
default allow := false
t := data.params.pharos_redemption
v := data.wasm.pharos_redemption
nullable_fields := {
"route_family": v.route_family,
"access_model": v.access_model,
"settlement_model": v.settlement_model,
"route_status": v.route_status,
"capacity_multiple": v.capacity_multiple,
"route_score": v.route_score,
"capacity_confidence": v.capacity_confidence,
"reserve_elevated_risk_pct": v.reserve_elevated_risk_pct,
"data_age_seconds": v.data_age_seconds,
}
deny contains "redemption_unavailable" if {
t.require_redemption_available
v.redemption_available == false
}
deny contains "unapproved_route_family" if {
v.route_family != null
not v.route_family in t.approved_route_families
}
deny contains "unapproved_access_model" if {
v.access_model != null
not v.access_model in t.approved_access_models
}
deny contains "unapproved_settlement_model" if {
v.settlement_model != null
not v.settlement_model in t.approved_settlement_models
}
deny contains "route_status_not_approved" if {
v.route_status != null
v.route_status != t.required_route_status
}
deny contains "position_exceeds_capacity" if {
v.capacity_multiple != null
v.capacity_multiple < t.min_capacity_multiple
}
deny contains "low_route_score" if {
v.route_score != null
v.route_score < t.min_route_score
}
deny contains "unapproved_capacity_confidence" if {
count(t.approved_capacity_confidence) > 0
v.capacity_confidence != null
not v.capacity_confidence in t.approved_capacity_confidence
}
deny contains "reserve_risk_above_max" if {
v.reserve_elevated_risk_pct != null
v.reserve_elevated_risk_pct > t.max_reserve_elevated_risk_pct
}
deny contains "stale_data" if {
v.data_age_seconds != null
v.data_age_seconds > t.max_data_age_seconds
}
deny contains sprintf("missing_%v", [name]) if {
some name in t.deny_on_missing_fields
nullable_fields[name] == null
}
allow if {
not v.error
is_boolean(v.redemption_available)
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.pharos_redemption`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **`required_route_status` is `"open"`, not `"active"`** — configuring `active` denies every healthy asset. A test pins this.
* **Pharos publishes no daily limit and no redemption minimum.** Sizing is therefore against `immediate_capacity_usd`, and there is no `below_min_redeem` rule.
* **There is no numeric confidence either.** `capacity_confidence` is a qualitative band (`documented-bound`), so route quality is gated on the 0-100 `route_score`.
* **`transaction_amount_usd` is caller-supplied and NOT attested.** See [`pharos_treasury`](/developers/vaults/policies/pharos-treasury) for the same caveat.
* `null` is the oracle's "not reported", distinct from `0`. Null optional fields fail soft; a **missing** key blocks `allow`.
* Listing `capacity_multiple` in `deny_on_missing_fields` requires every call to supply a position size. Because the list is per field, you can require the rest without that.
* **This pack rides within about 40% of a hard runtime memory limit.** `/api/redemption-backstops` has no filter, so the oracle downloads all ~1.14 MB and slices this coin's ~3 KB object out of the raw text. A `MAX_SLICEABLE_BYTES` guard trips first and returns a readable error — which fails closed — rather than trapping the component with no verdict. The durable fix is a per-asset endpoint from Pharos.
# Xerberus Liquidity Exit \[Use Xerberus exit quotes in Newton policies to deny positions that cannot be unwound quickly at a bounded market impact.]
This policy asks one question before an agent or curator takes on a position: **can it be unwound?** Using [Xerberus](https://xerberus.io), it estimates how many days selling the proposed notional would take while staying inside a market-impact band, and denies positions that would take too long to exit.
It is an asset-level check. It does not tell you whether the portfolio as a whole becomes too concentrated. Pair it with [Xerberus Portfolio What-If](/developers/vaults/policies/xerberus-what-if) and [Xerberus Common Cause](/developers/vaults/policies/xerberus-common-cause) for that.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `xerberus_liquidity_exit` |
| Rego package | `xerberus_liquidity_exit` (the `--entrypoint` is `xerberus_liquidity_exit.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `XERBERUS_API_KEY` | Required | [xerberus.io](https://xerberus.io) |
Sent by the oracle as the `x-api-key` request header.
## Data Inputs
One MCP `tools/call` of the `liquidity_exit_quote` tool, sent as JSON-RPC 2.0 to `https://mcp.xerberus.io/enterprise/mcp`. Xerberus exposes its risk functions as MCP tools rather than REST paths, so all three Xerberus packs share one transport:
* Non-2xx HTTP, a JSON-RPC `error`, or a tool result with `isError` all fail the oracle, and the policy fails closed.
* The response may be plain JSON or a single-shot `text/event-stream`. Either way the oracle reads the JSON-RPC message carrying its request id.
* `structuredContent` is preferred; otherwise the JSON in the tool's text content is parsed.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `token` | Echoed from `wasmArgs`. Addresses are lowercased | Gated by `require_token_in_intent` |
| `symbol` / `chain` | Token symbol; chain (always `ethereum` today) | Audit |
| `notional_usd` | Position size quoted | Audit |
| `impact_band_pct` | Impact band the quote was modeled at | Gated by `max_impact_band_pct` |
| `max_sale_per_day_usd` | Most that can be sold per day inside the band | `no_exit_liquidity` |
| `days_to_exit` | Estimated days to fully exit | Gated by `max_days_to_exit` |
| `classification` | Xerberus's label, e.g. moderate, under one week | Reviewer context |
| `cex_status` | Whether a centralized spot market was found | Reviewer context |
| `requested_window` / `data_window` | The window asked for, and the window Xerberus answered from | Audit |
| `window_honored` | `true` or `false` when a window was pinned, `null` otherwise | `window_not_honored` |
| `is_stale` | Xerberus's own freshness flag | Gated by `deny_on_stale` |
| `data_age_seconds` | Age of `data_window` at evaluation time | Gated by `max_data_age_seconds` |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: the token and notional of the position being acquired are not something the SDK can read off the vault, so the curator supplies them per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `token` | Required | Token contract address. Symbols work but are ambiguous and can't be bound to the intent |
| `usd_notional` | Required | Proposed position size in USD |
| `impact_pct` | Optional | Maximum acceptable market impact, in percent. Use 0.5 for stablecoins |
| `chain` | Optional | `ethereum`, the only chain Xerberus scores today. Defaults to `ethereum` |
| `window` | Optional | Xerberus data-window timestamp (ISO-8601). Pass the same value to every Xerberus pack |
From the CLI or dashboard this goes in the simulate payload's `wasm_args`. From the SDK it goes in `sendCall`'s `wasmArgs` bag:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
xerberus_liquidity_exit: {
token: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', // wstETH
usd_notional: 1_000_000,
impact_pct: 1,
window,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `max_days_to_exit` | `number` | Days-to-exit ceiling. The Xerberus reference design uses 7 |
| `max_impact_band_pct` | `number` | Loosest impact band accepted, so a caller can't request a looser band than you allow. Reference: 1 (0.5 for stablecoins) |
| `deny_on_stale` | `boolean` | Deny when Xerberus flags its data window as stale. Keep on for exposure-increasing actions |
| `max_data_age_seconds` | `number` | Oldest data window accepted |
| `require_token_in_intent` | `boolean` | Deny unless the quoted token is the intent's target or appears in its decoded arguments |
## Rego Checks
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `oracle_error` | The oracle emitted `{error}` | Transport, auth, or tool failures |
| `malformed_oracle_output` | A field the rules read is missing or `null` | An unpriced quote or a changed response shape |
| `stale_data` | `deny_on_stale` and `is_stale` | Decisions made on data Xerberus itself calls stale |
| `data_too_old` | `data_age_seconds > max_data_age_seconds` | Data older than you accept |
| `window_not_honored` | A window was pinned and Xerberus answered from another | Results that can't be lined up with the other Xerberus packs |
| `exit_too_slow` | `days_to_exit > max_days_to_exit` | Positions that take too long to unwind |
| `impact_band_exceeded` | `impact_band_pct > max_impact_band_pct` | A quote modeled at a looser impact than you accept |
| `no_exit_liquidity` | `max_sale_per_day_usd <= 0` | No modeled exit at all |
| `token_not_in_intent` | `require_token_in_intent` and the token isn't the target or an argument | A quote for one token authorizing a transaction on another |
### Intent Binding
The token is matched against the call target and every decoded argument. `contains` rather than equality lets an address nested in a tuple argument still match.
```rego
intent_mentions(addr) if lower(input.to) == addr
intent_mentions(addr) if {
some arg in input.decoded_function_arguments
is_string(arg)
contains(lower(arg), addr)
}
deny contains "token_not_in_intent" if {
t.require_token_in_intent
is_string(v.token)
not intent_mentions(lower(v.token))
}
```
### Final Allow Rule
`well_formed` requires every field a deny rule reads, including the freshness fields. An error envelope, an empty slot, or an unpriced quote leaves them undefined, so a bare `count(deny) == 0` would fail *open*.
```rego
allow if {
not v.error
well_formed
count(deny) == 0
}
```
## Complete Policy
```rego
package xerberus_liquidity_exit
import future.keywords
default allow := false
t := data.params.xerberus_liquidity_exit
v := data.wasm.xerberus_liquidity_exit
well_formed if {
is_string(v.token)
is_number(v.days_to_exit)
is_number(v.max_sale_per_day_usd)
is_number(v.impact_band_pct)
is_boolean(v.is_stale)
is_number(v.data_age_seconds)
}
intent_mentions(addr) if lower(input.to) == addr
intent_mentions(addr) if {
some arg in input.decoded_function_arguments
is_string(arg)
contains(lower(arg), addr)
}
deny contains "oracle_error" if v.error
deny contains "malformed_oracle_output" if {
not v.error
not well_formed
}
deny contains "stale_data" if {
t.deny_on_stale
v.is_stale == true
}
deny contains "data_too_old" if v.data_age_seconds > t.max_data_age_seconds
deny contains "window_not_honored" if v.window_honored == false
deny contains "exit_too_slow" if v.days_to_exit > t.max_days_to_exit
deny contains "impact_band_exceeded" if v.impact_band_pct > t.max_impact_band_pct
deny contains "no_exit_liquidity" if {
is_number(v.max_sale_per_day_usd)
v.max_sale_per_day_usd <= 0
}
deny contains "token_not_in_intent" if {
t.require_token_in_intent
is_string(v.token)
not intent_mentions(lower(v.token))
}
allow if {
not v.error
well_formed
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.xerberus_liquidity_exit`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **Stale data fails closed.** A missing `is_stale` or an unparseable `data_window` makes the output malformed, which blocks `allow`.
* **Pin the window when composing.** Pass one `window` to all three Xerberus packs so their results come from the same Xerberus snapshot. Never combine a fresh exit quote with an older portfolio or common-cause result.
* **Use token addresses.** A symbol can't be matched against the intent, so `require_token_in_intent` only works with an address.
* **The notional is caller-supplied.** The pack can't price calldata itself, so your intent builder is responsible for passing the real USD size.
* **Exit time is asset-level.** In Xerberus's reference scenario, a $1M wstETH position exits in about 5.3 days and passes a 7-day limit, yet the same trade makes the portfolio more concentrated and less liquid. Only [Portfolio What-If](/developers/vaults/policies/xerberus-what-if) catches that.
# Xerberus Portfolio What-If \[Use Xerberus portfolio simulations in Newton policies to deny trades that concentrate the book or make it harder to exit.]
This policy catches transactions that look fine in isolation but make the **portfolio** riskier. Using [Xerberus](https://xerberus.io), it simulates the managed book before and after a proposed change and denies one that:
* pushes the largest position past a concentration limit while growing it,
* raises the token Herfindahl-Hirschman index (HHI) too far,
* shrinks the share of the book that can be exited within 30 days, or
* lengthens the portfolio's slowest exit.
Xerberus returns deterministic exposure and liquidity deltas here. It does not rerun a systemic rating or a tail-risk model.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `xerberus_what_if` |
| Rego package | `xerberus_portfolio_what_if` (the `--entrypoint` is `xerberus_portfolio_what_if.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `XERBERUS_API_KEY` | Required | [xerberus.io](https://xerberus.io) |
Sent by the oracle as the `x-api-key` request header.
## Data Inputs
One MCP `tools/call` of the `what_if` tool against `https://mcp.xerberus.io/enterprise/mcp`, using the same transport as [Xerberus Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit#data-inputs).
Position shares are derived from `usd / book_usd`, so the unit never depends on whether Xerberus reports `pct` as a fraction or a percentage. Deltas come from Xerberus's `delta` block and fall back to `after − before`.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `wallets` / `add` / `remove` | Echoed from `wasmArgs`. Addresses are lowercased | Gated by `require_intent_party_in_wallets` |
| `is_risk_reducing` | `true` for a pure removal with no additions | The staleness exemption |
| `book_usd_before` / `book_usd_after` | Portfolio value either side of the change | Reviewer context |
| `post_largest_token` / `post_largest_token_pct` | Largest position after the change, and its share of the book in percent | Gated by `max_post_token_share_pct` |
| `pre_largest_token_pct` | That token's share before the change (0 if outside the reported top positions) | Concentration only denies when it grows |
| `hhi_before` / `hhi_after` / `hhi_delta` | Token HHI (0–1 scale) and its change | Gated by `max_hhi_delta` |
| `ladder_30d_before_pct` / `ladder_30d_after_pct` / `ladder_30d_delta_pp` | Share of the book exitable within 30 days, and its change in percentage points | Gated by `min_ladder_30d_delta_pp` |
| `slowest_exit_days_before` / `slowest_exit_days_after` / `slowest_exit_delta_days` | Slowest token exit and its change | Gated by `max_slowest_exit_delta_days` |
| `issues` / `issues_count` | Simulation issues, flattened to strings (first 10), and their count | Gated by `deny_on_issues` |
| `requested_window` / `data_window` / `window_honored` / `is_stale` / `data_age_seconds` | Freshness, as in [Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit#data-inputs) | Freshness rules |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**: the change being simulated comes from the action, which the SDK can't price, so the curator supplies it per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `wallets` | Required | 1–10 wallet addresses that make up the managed portfolio |
| `add` | Optional | Up to 10 `{ token, usd }` additions. At least one of `add` or `remove` must be non-empty |
| `remove` | Optional | Up to 10 `{ token, usd }` removals. Removals with no additions count as risk-reducing |
| `impact_pct` | Optional | Market-impact assumption, in percent |
| `chain` | Optional | `ethereum` only today. Defaults to `ethereum` |
| `window` | Optional | Xerberus data-window timestamp (ISO-8601). Pass the same value to every Xerberus pack |
From the SDK:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
xerberus_what_if: {
wallets: [vault],
add: [{ token: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', usd: 1_000_000 }],
window,
},
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `max_post_token_share_pct` | `number` | Concentration ceiling, in percent. The Xerberus reference design uses 50 |
| `max_hhi_delta` | `number` | Largest tolerated HHI increase. Reference: 0.01 |
| `min_ladder_30d_delta_pp` | `number` | Largest tolerated drop in the 30-day exitable share, in percentage points (negative). Reference: −3 |
| `max_slowest_exit_delta_days` | `number` | Largest tolerated increase in the slowest exit, in days. Reference: 3 |
| `deny_on_issues` | `boolean` | Deny on any simulation issue, such as an unpriced token |
| `deny_on_stale` | `boolean` | Deny when Xerberus flags its data window as stale |
| `max_data_age_seconds` | `number` | Oldest data window accepted |
| `require_intent_party_in_wallets` | `boolean` | Deny unless the intent's sender or target is one of the simulated wallets |
| `allow_stale_when_risk_reducing` | `boolean` | Let pure removals through on stale or old data |
## Rego Checks
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `oracle_error` / `malformed_oracle_output` | Oracle error, or a field the rules read is missing | Failures that must not read as "no issues" |
| `stale_data` | `deny_on_stale` and `is_stale`, unless exempt | Decisions made on stale data |
| `data_too_old` | `data_age_seconds > max_data_age_seconds`, unless exempt | Data older than you accept |
| `window_not_honored` | A window was pinned and Xerberus answered from another | Results that can't be lined up with the other Xerberus packs |
| `concentration_limit` | Largest post-trade share over the limit **and** grown | A trade that concentrates the book |
| `hhi_delta_limit` | `hhi_delta > max_hhi_delta` | A jump in overall token concentration |
| `liquidity_30d_deterioration` | `ladder_30d_delta_pp < min_ladder_30d_delta_pp` | A trade that makes the book harder to exit within a month |
| `slowest_exit_limit` | `slowest_exit_delta_days > max_slowest_exit_delta_days` | A trade that lengthens the worst-case exit |
| `simulation_issues` | `deny_on_issues` and `issues_count > 0` | A simulation Xerberus couldn't fully run |
| `wallets_not_bound_to_intent` | `require_intent_party_in_wallets` and neither sender nor target is a simulated wallet | A simulation of one portfolio authorizing a trade on another |
### Concentration
Only a change that pushes the largest position past the limit **and** grows its share denies, so a trade that trims an already-concentrated book isn't blocked by the level it's reducing.
```rego
deny contains "concentration_limit" if {
v.post_largest_token_pct > t.max_post_token_share_pct
v.post_largest_token_pct > v.pre_largest_token_pct
}
```
### Risk-Reducing Exemption
With `allow_stale_when_risk_reducing` on, a pure removal is exempt from the staleness rules, so unavailable data never stops an agent reducing exposure. The delta checks still apply, and the exemption never covers additions.
```rego
stale_exempt if {
t.allow_stale_when_risk_reducing
v.is_risk_reducing == true
}
deny contains "stale_data" if {
t.deny_on_stale
v.is_stale == true
not stale_exempt
}
```
### Final Allow Rule
```rego
allow if {
not v.error
well_formed
count(deny) == 0
}
```
## Complete Policy
```rego
package xerberus_portfolio_what_if
import future.keywords
default allow := false
t := data.params.xerberus_what_if
v := data.wasm.xerberus_what_if
well_formed if {
is_array(v.wallets)
is_boolean(v.is_risk_reducing)
is_number(v.post_largest_token_pct)
is_number(v.pre_largest_token_pct)
is_number(v.hhi_delta)
is_number(v.ladder_30d_delta_pp)
is_number(v.slowest_exit_delta_days)
is_number(v.issues_count)
is_boolean(v.is_stale)
is_number(v.data_age_seconds)
}
stale_exempt if {
t.allow_stale_when_risk_reducing
v.is_risk_reducing == true
}
wallets_bound_to_intent if {
some w in v.wallets
w == lower(input.from)
}
wallets_bound_to_intent if {
some w in v.wallets
w == lower(input.to)
}
deny contains "oracle_error" if v.error
deny contains "malformed_oracle_output" if {
not v.error
not well_formed
}
deny contains "stale_data" if {
t.deny_on_stale
v.is_stale == true
not stale_exempt
}
deny contains "data_too_old" if {
v.data_age_seconds > t.max_data_age_seconds
not stale_exempt
}
deny contains "window_not_honored" if v.window_honored == false
deny contains "concentration_limit" if {
v.post_largest_token_pct > t.max_post_token_share_pct
v.post_largest_token_pct > v.pre_largest_token_pct
}
deny contains "hhi_delta_limit" if v.hhi_delta > t.max_hhi_delta
deny contains "liquidity_30d_deterioration" if {
is_number(v.ladder_30d_delta_pp)
v.ladder_30d_delta_pp < t.min_ladder_30d_delta_pp
}
deny contains "slowest_exit_limit" if v.slowest_exit_delta_days > t.max_slowest_exit_delta_days
deny contains "simulation_issues" if {
t.deny_on_issues
v.issues_count > 0
}
deny contains "wallets_not_bound_to_intent" if {
t.require_intent_party_in_wallets
is_array(v.wallets)
not wallets_bound_to_intent
}
allow if {
not v.error
well_formed
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.xerberus_what_if`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **The reference scenario denies.** Acquiring $1M of wstETH on a $17.8M treasury moves wstETH from 50.9% to 53.5% of the book, raises HHI by 0.0128, cuts the 30-day exitable share by 4.32 pp and lengthens the slowest exit by 5.26 days. That trips every portfolio limit at the reference thresholds, even though the standalone [exit quote](/developers/vaults/policies/xerberus-liquidity-exit) is under a week.
* **The change is caller-supplied.** `add` and `remove` describe the trade; your intent builder is responsible for passing the real one.
* **Pin the window when composing** with the other Xerberus packs, so all results come from one snapshot.
* A token outside the reported top positions counts as a 0% share before the change. That can only make a trade look more concentrating, never less.
# Xerberus Common Cause \[Use Xerberus common-cause analysis in Newton policies to deny false diversification across venues that share a failure mechanism.]
This policy catches **false diversification**. Positions in Fluid, Morpho and Compound look independent, but they can depend on the same oracle, hold the same collateral, or lack the same safeguard, and a failure in that shared piece hits them all at once. Using [Xerberus](https://xerberus.io), it compares the venues a portfolio holds and denies when their shared failure mechanisms exceed your limits.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `xerberus_common_cause` |
| Rego package | `xerberus_common_cause` (the `--entrypoint` is `xerberus_common_cause.allow`) |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
:::note
This oracle is not deployed yet, so it has no row in the VaultKit address table. [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) is the source of truth for every pack, chain, and environment.
:::
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `XERBERUS_API_KEY` | Required | [xerberus.io](https://xerberus.io) |
Sent by the oracle as the `x-api-key` request header.
## Data Inputs
One MCP `tools/call` of the `common_cause` tool against `https://mcp.xerberus.io/enterprise/mcp`, using the same transport as [Xerberus Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit#data-inputs).
Arrays are capped at 25 entries, largest first. Failure classes, dependency targets and collateral addresses are lowercased.
| Field | What it means | Why the policy uses it |
| --- | --- | --- |
| `wallets` | Echoed from `wasmArgs`, lowercased | Gated by `require_intent_party_in_wallets` |
| `subjects_compared_count` / `scored_subjects_count` | Venues compared, and how many Xerberus could score | `insufficient_venues` |
| `total_exposure_usd` | Exposure summed across the compared venues | Denominator for the collateral share |
| `unscored_subjects[]` | `{ subject, exposure_usd }` for venues Xerberus couldn't score | Gated by `deny_on_unscored` |
| `shared_gaps[]` | `{ tag, failure_class, subject_count, combined_exposure_usd }`: a missing safeguard shared across venues | Gated by `max_shared_gap_exposure_usd` |
| `largest_shared_gap_exposure_usd` | Largest combined exposure behind one shared gap | Reviewer context |
| `shared_dependencies[]` | `{ target, connection_types, subject_count }`: a declared dependency shared across venues | Gated by `forbidden_shared_dependencies` |
| `shared_collateral[]` | `{ token, symbol, subject_count, combined_exposure_usd }`: a collateral token shared across venues | Gated by `max_shared_collateral_pct_of_book` |
| `largest_shared_collateral_exposure_usd` | Largest combined exposure behind one collateral token | Reviewer context |
| `basis` | Xerberus's basis for the comparison | Reviewer context |
| `requested_window` / `data_window` / `window_honored` / `is_stale` / `data_age_seconds` | Freshness, as in [Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit#data-inputs) | Freshness rules |
## Per-call wasmArgs
This pack ships **no `prepareQuery`**, so the curator supplies the wallets per call.
| Field | Required? | Notes |
| --- | --- | --- |
| `wallets` | Required | 1–10 wallet addresses holding the positions to compare. At least two Xerberus-mapped venues are needed for a meaningful result |
| `chain` | Optional | `ethereum` only today. Defaults to `ethereum` |
| `window` | Optional | Xerberus data-window timestamp (ISO-8601). Pass the same value to every Xerberus pack |
From the SDK:
```typescript
await shield.morpho.submitCap(vault, marketParams, newCap, {
wasmArgs: {
xerberus_common_cause: { wallets: [vault], window },
},
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
## Policy Parameters
| Param | Type | Description |
| --- | --- | --- |
| `max_shared_gap_exposure_usd` | `number` | Exposure ceiling behind one shared missing safeguard. The Xerberus reference design uses 5,000,000 |
| `gap_failure_classes` | `string[]` | Failure classes the gap limit applies to (case-insensitive). Empty means every class |
| `forbidden_shared_dependencies` | `string[]` | Dependency targets that must not be shared, such as `protocol:chainlink` (case-insensitive). Empty disables the check |
| `min_venues_for_shared_dependency` | `number` | How many venues must share a forbidden dependency before it denies. Reference: 2 |
| `max_shared_collateral_pct_of_book` | `number` | Ceiling on one collateral token's share of the compared exposure, in percent. Reference: 25 |
| `material_subject_usd` | `number` | Exposure at which an unscored venue matters |
| `deny_on_unscored` | `boolean` | Deny if a material venue is unscored |
| `min_subjects_compared` | `number` | Venues needed for a meaningful comparison. Reference: 2 |
| `deny_on_stale` | `boolean` | Deny when Xerberus flags its data window as stale |
| `max_data_age_seconds` | `number` | Oldest data window accepted |
| `require_intent_party_in_wallets` | `boolean` | Deny unless the intent's sender or target is one of the analysed wallets |
## Rego Checks
| Deny reason | Condition | What it catches |
| --- | --- | --- |
| `oracle_error` / `malformed_oracle_output` | Oracle error, or a field the rules read is missing | Failures that must not read as "nothing shared" |
| `stale_data` | `deny_on_stale` and `is_stale` | Decisions made on stale data |
| `data_too_old` | `data_age_seconds > max_data_age_seconds` | Data older than you accept |
| `window_not_honored` | A window was pinned and Xerberus answered from another | Results that can't be lined up with the other Xerberus packs |
| `shared_gap_limit` | An in-scope shared gap carries more than `max_shared_gap_exposure_usd` | One missing safeguard exposing too much at once |
| `shared_dependency_limit` | A forbidden target is shared by at least `min_venues_for_shared_dependency` venues | Several venues leaning on the same provider |
| `shared_collateral_limit` | One shared collateral token exceeds `max_shared_collateral_pct_of_book` | Concentration hidden behind different venues |
| `unscored_material_subject` | `deny_on_unscored` and an unscored venue holds at least `material_subject_usd` | Material exposure Xerberus couldn't assess |
| `insufficient_venues` | Fewer than `min_subjects_compared` venues | A single-venue result reading as "no shared risk" |
| `wallets_not_bound_to_intent` | `require_intent_party_in_wallets` and neither sender nor target is an analysed wallet | Analysis of one portfolio authorizing a trade on another |
### Shared Risks
Each breach is a set, so a reviewer can see exactly which gap, dependency or collateral tripped.
```rego
gap_breaches contains g if {
some g in v.shared_gaps
gap_in_scope(g)
g.combined_exposure_usd > t.max_shared_gap_exposure_usd
}
dependency_breaches contains d if {
some d in v.shared_dependencies
d.target in forbidden_dependencies
d.subject_count >= t.min_venues_for_shared_dependency
}
```
### Explainability
A separate `shared_risks` rule renders each breach as text, for example `"forbidden dependency protocol:chainlink shared by 3 venues"`. This is **not** evaluated on-chain; the AVS entrypoint is `xerberus_common_cause.allow` and nothing else. It exists for `opa eval`, local simulation, and composites that want to surface a reason to an operator.
### Final Allow Rule
`well_formed` requires every array the breach sets iterate over. An error envelope has none of them, every set comes out empty, and a bare `count(deny) == 0` would fail *open*.
```rego
allow if {
not v.error
well_formed
count(deny) == 0
}
```
## Complete Policy
```rego
package xerberus_common_cause
import future.keywords
default allow := false
t := data.params.xerberus_common_cause
v := data.wasm.xerberus_common_cause
well_formed if {
is_array(v.wallets)
is_number(v.subjects_compared_count)
is_number(v.total_exposure_usd)
is_array(v.unscored_subjects)
is_array(v.shared_gaps)
is_array(v.shared_dependencies)
is_array(v.shared_collateral)
is_boolean(v.is_stale)
is_number(v.data_age_seconds)
}
gap_classes := {lower(c) | some c in t.gap_failure_classes}
forbidden_dependencies := {lower(d) | some d in t.forbidden_shared_dependencies}
gap_in_scope(_) if count(gap_classes) == 0
gap_in_scope(g) if g.failure_class in gap_classes
gap_breaches contains g if {
some g in v.shared_gaps
gap_in_scope(g)
g.combined_exposure_usd > t.max_shared_gap_exposure_usd
}
dependency_breaches contains d if {
some d in v.shared_dependencies
d.target in forbidden_dependencies
d.subject_count >= t.min_venues_for_shared_dependency
}
collateral_breaches contains c if {
v.total_exposure_usd > 0
some c in v.shared_collateral
c.combined_exposure_usd * 100 > t.max_shared_collateral_pct_of_book * v.total_exposure_usd
}
unscored_material contains s if {
some s in v.unscored_subjects
s.exposure_usd >= t.material_subject_usd
}
shared_risks contains detail if {
some g in gap_breaches
detail := sprintf("missing safeguard %v (%v) across %v venues, $%v combined", [g.tag, g.failure_class, g.subject_count, g.combined_exposure_usd])
}
shared_risks contains detail if {
some d in dependency_breaches
detail := sprintf("forbidden dependency %v shared by %v venues", [d.target, d.subject_count])
}
shared_risks contains detail if {
some c in collateral_breaches
detail := sprintf("collateral %v shared by %v venues, $%v combined", [c.symbol, c.subject_count, c.combined_exposure_usd])
}
wallets_bound_to_intent if {
some w in v.wallets
w == lower(input.from)
}
wallets_bound_to_intent if {
some w in v.wallets
w == lower(input.to)
}
deny contains "oracle_error" if v.error
deny contains "malformed_oracle_output" if {
not v.error
not well_formed
}
deny contains "stale_data" if {
t.deny_on_stale
v.is_stale == true
}
deny contains "data_too_old" if v.data_age_seconds > t.max_data_age_seconds
deny contains "window_not_honored" if v.window_honored == false
deny contains "shared_gap_limit" if count(gap_breaches) > 0
deny contains "shared_dependency_limit" if count(dependency_breaches) > 0
deny contains "shared_collateral_limit" if count(collateral_breaches) > 0
deny contains "unscored_material_subject" if {
t.deny_on_unscored
count(unscored_material) > 0
}
deny contains "insufficient_venues" if v.subjects_compared_count < t.min_subjects_compared
deny contains "wallets_not_bound_to_intent" if {
t.require_intent_party_in_wallets
is_array(v.wallets)
not wallets_bound_to_intent
}
allow if {
not v.error
well_formed
count(deny) == 0
}
```
Composing this pack into a VaultKit composite changes only where params live: the manifest envelope puts the curator's slice at `data.params.params.xerberus_common_cause`. See [Policies](/developers/vaults/sdk/policies).
## Notes
* **New venues are invisible.** Xerberus only sees positions that already exist in the supplied wallets, so a transaction entering a brand-new venue can't be checked for shared risk until after it lands. Gate first-time venue entry separately, with a destination-protocol check or manual approval.
* **Collateral share is measured against compared exposure**, not the whole book. That's stricter: in Xerberus's reference scenario, WBTC's $3.80M is about 21% of the $17.8M treasury but about 41% of the $9.31M across the compared venues.
* **The reference scenario denies.** About $9.31M across Fluid, Morpho and Compound shares oracle-integrity gaps (price-deviation circuit breakers, graceful degradation, minimum-price protection), and all three declare a Chainlink dependency.
* **Pin the window when composing** with the other Xerberus packs, so all results come from one snapshot.
# Overview \[VaultKit 2.1 declares typed policies and routes vault-manager actions through Newton attestations.]
Newton VaultKit is a TypeScript SDK and companion Solidity contract system for vault curators. It routes manager actions through a Newton Policy Protocol attestation gate before the underlying vault receives the call.
Curators continue using the vault SDKs and contracts they already know. VaultKit adds a Shield clone between the curator and the vault, packages each action as an exact `Intent`, asks Newton operators to evaluate that intent against the configured policy, and forwards the action only when a valid attestation is available.
## What It Does
VaultKit turns a vault action into this flow:
```mermaid
sequenceDiagram
participant App as Curator App
participant SDK as Newton VaultKit
participant AVS as Newton AVS
participant Shield as Shield Contract
participant Vendor as Vault Protocol
App->>SDK: Call typed vault action
SDK->>AVS: Submit signed intent and policy inputs
AVS-->>SDK: Return allow/deny result and attestation data
SDK->>Shield: Execute attested intent
Shield->>Vendor: Forward approved manager call
```
## Core Pieces
* **Shield clones:** One audited implementation per chain, with deterministic per-curator clones deployed through `ShieldFactory`.
* **Policy-first authoring:** `definePolicy().with(...)` combines published packs and custom `defineOracle(...)` outputs.
* **Policy core:** `@newton-xyz/policy-core` owns oracle authoring, manifest encoding, schema generation, introspection, and provenance classification.
* **Vendor modules:** Typed wrappers for MetaMorpho, Morpho Blue, Euler Earn, EVault, and Superform manager actions.
* **Generic calls:** `shield.sendCall(...)` supports protocols and manager actions without a first-class vendor module.
* **Typed errors:** SDK failures map to stable error classes such as `PolicyDeniedError`, `AttestationTimeoutError`, `ShieldExecutionError`, `ParamMismatchError`, `GatewayError`, and `UnsupportedChainError`.
* **Verification:** `client.policy`, `client.verification`, and `client.reverify()` expose the resolved policy and attach-time checks.
* **Negative assertions:** `assertIntentBlocked(...)` proves a noncompliant intent is rejected without mining a transaction.
* **Browser-safe core:** The main package avoids `node:*` imports. Vendor modules may inherit constraints from the vendor SDKs they wrap.
## Manager Actions Only
VaultKit is for privileged vault operations such as reallocations, cap changes, and other curator or manager calls. End-user deposits and withdrawals still flow through the vault protocol's normal UX unless the vault deliberately routes those actions through a Shield.
## How to Read This Section
* Start with [Getting Started](/developers/vaults/sdk/integration-guide) to install packages, create clients, configure a Shield, and run a policy-gated action.
* Use [Morpho](/developers/vaults/sdk/morpho) for MetaMorpho curator setup, composite policy choices, and `reallocate` role requirements.
* Use [Morpho Blue](/developers/vaults/sdk/morpho-blue) for managed-sleeve position operations.
* Use [Superform](/developers/vaults/sdk/superform) for SuperVault manager actions and typed hook bundles.
* Use [Policies](/developers/vaults/sdk/policies) for published packs, custom oracles, bare policies, and verification.
* Read [Concepts](/developers/vaults/sdk/concepts) if you want the mental model for `createShield`, packs, vendor modules, and `sendCall`.
* Use [Examples](/developers/vaults/sdk/examples) for copyable integration patterns.
* Keep [Reference](/developers/vaults/sdk/reference) nearby for parameters, return values, supported chains, and exported errors.
Install VaultKit and run a policy-gated vault action.
Gate MetaMorpho curator actions with a Newton Shield.
Understand Shield clients, policy packs, vendor modules, and generic calls.
Review the API surface, chains, errors, and package structure.
# Integration Guide \[Install VaultKit 2.1, declare a Newton policy, and run a typed vault-manager action.]
VaultKit's current authoring flow is policy-first: declare the policy modules synchronously with `definePolicy(...)`, pass the deployed `NewtonPolicy` address to `createShield(...)`, extend the returned client with a vendor module, and then call a typed action.
## Prerequisites
| Requirement | Version or purpose |
| --- | --- |
| Node.js | 22 or newer |
| pnpm | 10 or newer |
| `viem` | `^2.35.1` |
| `zod` | `^3.0.0` |
| Newton API key | Created for the chain environment you will use |
| Curator wallet | Funded on the target chain and authorized to manage the target vault |
## Install
```bash
pnpm add @newton-xyz/vaultkit @newton-xyz/policy-core viem zod
pnpm add @newton-xyz/policy-pack-vaultsfyi
```
Install only the vendor SDKs your integration needs. The typed MetaMorpho module uses:
```bash
pnpm add @morpho-org/blue-sdk @morpho-org/blue-sdk-viem @morpho-org/morpho-ts
```
`@newton-xyz/policy-pack-shared` is not part of the VaultKit 2.x flow. Policy authoring and manifest utilities now live in `@newton-xyz/policy-core`.
## 1. Create Viem Clients
```typescript
import { createPublicClient, createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
const account = privateKeyToAccount(
process.env.CURATOR_PRIVATE_KEY as `0x${string}`,
)
const publicClient = createPublicClient({
chain: base,
transport: http(process.env.RPC_URL),
})
const walletClient = createWalletClient({
account,
chain: base,
transport: http(process.env.RPC_URL),
})
```
## 2. Declare the Policy
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '8453',
env: 'prod',
}).with(vaultsfyi)
```
`definePolicy(...)` is synchronous and does not read the chain. Add more published packs or custom `defineOracle(...)` outputs with additional `.with(...)` calls. Module order does not need to match the deployed contract; VaultKit aligns the declared modules to `NewtonPolicy.getPolicyData()` when the client is created.
## 3. Create the Client and Add a Vendor Module
```typescript
import { createShield } from '@newton-xyz/vaultkit'
import { morphoActions } from '@newton-xyz/vaultkit/vendors/morpho'
const client = (await createShield({
apiKey: process.env.NEWTON_API_KEY!,
walletClient,
publicClient,
rpc: process.env.RPC_URL!,
vault: '0xMetaMorphoVault',
policy,
policyAddress: '0xDeployedNewtonPolicy',
})).extend(morphoActions)
```
The `policyAddress` must reference a deployed `NewtonPolicy` whose `getPolicyData()` set matches the modules declared with `.with(...)`. `createShield(...)` resolves that policy and exposes the result as `client.policy`.
## 4. Configure Parameters and Secrets
```typescript
await client.setParams({
vaultsfyi: {
risk_score_floor: 80,
tvl_drawdown_24h_max_pct: 25,
},
})
await client.uploadSecrets({
vaultsfyi: {
VAULTS_FYI_API_KEY: process.env.VAULTSFYI_API_KEY!,
},
})
```
Parameters are encoded into the onchain composite manifest. Secrets are encrypted client-side and uploaded separately; they are not written onchain.
Use `client.encodeParams(params)` to inspect the exact bytes before writing them.
## 5. Verify the Attachment
```typescript
console.log(client.policy.address)
console.log(client.policy.modules)
console.log(client.verification)
```
When attaching to an existing deployment, VaultKit verifies progressively:
1. The attached contract points to the expected policy address.
2. The onchain oracle set and WASM CIDs match the declared modules.
3. Stored params decode against the module schemas.
4. When `expectedParams` is supplied, stored bytes exactly match the expected encoding.
Call `client.reverify()` after changing parameters. Use `attachWithoutVerify: true` only to recover an incomplete first-time setup, and remove it immediately after writing the correct configuration.
## 6. Grant the Required Vault Role
The client address must hold the manager or allocator role required by the vendor action. Role assignment is vendor-specific; VaultKit does not bypass the vendor's authorization model.
```typescript
console.log(client.policyClientAddress)
```
Grant that address the appropriate role using the vendor's normal governance process.
## 7. Execute a Typed Action
```typescript
const result = await client.morpho.reallocate(
'0xMetaMorphoVault',
[{ marketParams, assets: 1_000_000n }],
{
prepareQueryOptions: {
vaultsfyi: {
previousAllocationHash: '0x...',
},
},
},
)
const receipt = await publicClient.waitForTransactionReceipt({
hash: result.transactionHash,
})
```
Each policy module owns its `wasmArgs` construction. Pass module-specific inputs through `prepareQueryOptions.`; VaultKit validates the resulting data before building the intent.
## 8. Handle Expected Failures
```typescript
import {
AttestationTimeoutError,
PolicyDeniedError,
RpcReadError,
} from '@newton-xyz/vaultkit'
try {
await client.morpho.reallocate('0xMetaMorphoVault', allocations)
} catch (error) {
if (error instanceof PolicyDeniedError) {
console.error('The policy denied this action:', error.reason)
} else if (error instanceof AttestationTimeoutError) {
console.error('Request a fresh task after backoff')
} else if (error instanceof RpcReadError) {
console.error(error.read, error.cause)
} else {
throw error
}
}
```
See [Errors](/developers/vaults/sdk/errors) before adding retries. A deterministic policy or schema failure does not become valid when retried.
## Next Steps
Compose published packs, custom oracles, and policy-level parameters.
Build a typed oracle with `defineOracle(...)`.
Copy current VaultKit 2.1 recipes.
# Policies \[Declare VaultKit policies from published packs, custom oracles, or a bare deployed policy.]
Every VaultKit client resolves to one `NewtonPolicy`. That policy can contain one or more policy-data oracles, or it can be a bare monolithic policy with no composable modules.
## Published Policy Packs
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { chainalysis } from '@newton-xyz/policy-pack-chainalysis'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.with(chainalysis)
```
`definePolicy(...)` is synchronous and chainless. `createShield(...)` performs the onchain read, matches the declared modules to `NewtonPolicy.getPolicyData()`, and exposes the resolved result on `client.policy`.
```typescript
const client = await createShield({
apiKey,
walletClient,
rpc,
vault,
policy,
policyAddress: '0xDeployedNewtonPolicy',
})
```
A duplicate short ID is rejected because params and per-call inputs are namespaced by that ID.
## Custom Oracles
Published packs and custom `defineOracle(...)` outputs implement the same `PolicyPack` contract:
```typescript
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.with(myOracle)
```
There is no custom-module registry gate. The same deployment matching, WASM CID checks, schema validation, and manifest encoding apply to every module.
## Policy-Level Parameters
Use `.withPolicyParams(...)` for composite-wide values that belong to the policy rather than a single oracle:
```typescript
import { z } from 'zod'
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.withPolicyParams(
z.object({
allowedYieldSources: z.array(z.string()),
maxFeeBps: z.number().int().min(0).max(10_000),
}),
)
await client.setParams({
_policy: {
allowedYieldSources: ['0xYieldSource'],
maxFeeBps: 100,
},
vaultsfyi: { risk_score_floor: 80 },
})
```
The manifest stores that slice at `params._policy`; Rego reads it at `data.params.params._policy`.
## Bare Policies
Use `policyFromAddress(...)` for a deployed monolithic or calldata-only policy:
```typescript
import { policyFromAddress } from '@newton-xyz/vaultkit'
const policy = policyFromAddress({
address: '0xNewtonPolicy',
chainId: '8453',
env: 'prod',
params: {
encode: encodeMyParams,
decode: decodeMyParams,
},
})
```
A bare policy has `modules: []`. Its optional codec enables `setParams(...)`, decoding, and higher verification rungs. Pass this object as `policy` and omit the separate `policyAddress`.
## Manifest Shape
`setParams(...)` writes a versioned UTF-8 JSON envelope:
```json
{
"_manifest": { "magic": "NPM1", "version": 1 },
"modules": [
{
"id": "vaultsfyi/risk-envelope/v1",
"policyDataAddress": "0xPolicyData",
"wasmCid": "bafy..."
}
],
"params": {
"_policy": { "maxFeeBps": 100 },
"vaultsfyi": { "risk_score_floor": 80 }
}
}
```
Generate schemas and encode this format with `@newton-xyz/policy-core`; do not hand-maintain a second manifest encoder.
## Per-Call Inputs
`sendCall` takes two short-ID-keyed per-module bags. Which one a pack uses depends on whether it ships a `prepareQuery`.
```typescript
await client.sendCall({
to,
data,
functionSignature,
// INPUTS to a pack's own prepareQuery, for packs that have one.
prepareQueryOptions: {
vaultsfyi: { previousAllocationHash: '0x...' },
my_oracle: { market: '0xMarket' },
},
// The wasmArgs THEMSELVES, for packs that ship no prepareQuery.
wasmArgs: {
arkham_entity: { address: '0xDestination', chain: 'ethereum' },
pharos_treasury: { stablecoin_id: 'usdc-circle' },
},
})
```
Each module receives only its short-ID-keyed slice. Its `prepareQuery(...)` result is checked against its `wasmArgsSchema`; a failure stops intent construction.
A pack ships no `prepareQuery` when its `wasmArgs` name something the SDK cannot read from chain state — [`balancer`](/developers/vaults/policies/balancer)'s `poolId`, [`arkham_entity`](/developers/vaults/policies/arkham-entity)'s `address`, [`pharos_treasury`](/developers/vaults/policies/pharos-treasury)'s `stablecoin_id`. Those modules take their slice from `wasmArgs` instead.
The two are separate fields rather than one because they are different things: `prepareQueryOptions` is input **to** a `prepareQuery`, `wasmArgs` is what one would have **returned**. A pack with no `prepareQuery` also carries `TOptions = unknown`, so routing its args through the options bag would compile while checking nothing; `wasmArgs` is typed per module, so a misspelled field is a compile error.
A module that ships a `prepareQuery` owns its slice outright — there is no merge, so a stray `wasmArgs` entry cannot override what the pack derived from chain state. Either way a slice its schema rejects, including the `{}` a `prepareQuery`-less module gets when nothing is supplied, throws `InvalidConfigurationError` rather than reaching the AVS.
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later.
## Verification and Introspection
The resolved client exposes objective onchain facts:
```typescript
client.policy.address
client.policy.modules
client.verification
await client.reverify()
```
For an independent report:
```typescript
import { introspectComposite } from '@newton-xyz/policy-core'
const report = await introspectComposite({
publicClient,
shieldAddress: client.policyClientAddress,
})
```
`introspectComposite(...)` returns comparison booleans instead of throwing on a binding mismatch. It throws only when the stored manifest cannot be decoded.
## Provenance Is Consumer-Selected
`ResolvedModule` reports objective values such as `id`, `policyDataAddress`, and `wasmCid`. It does not claim that a module is audited or trusted. Consumers can classify those facts with `classifyProvenance(...)` from `@newton-xyz/policy-core` and a registry they trust.
## Low-Level Composition
VaultKit no longer re-exports `defineComposite(...)`. Most curator integrations should use `definePolicy().with(...)`. Import `defineComposite`, `defineOracle`, manifest encoders, schema generators, and introspection helpers directly from `@newton-xyz/policy-core` when building tooling outside `createShield(...)`.
Define schemas, query preparation, deployments, and metadata for your own oracle.
Browse published risk, compliance, and market-data packs.
# Custom Oracles \[Author a typed policy-data oracle with `@newton-xyz/policy-core` and compose it into VaultKit.]
`defineOracle(...)` gives custom and published oracles the same typed interface. It describes schemas, deployments, query preparation, and metadata; it does not build the oracle WASM or deploy `NewtonPolicyData`.
## Define the Oracle
```typescript
import { defineOracle } from '@newton-xyz/policy-core'
import { z } from 'zod'
export const maxLtvOracle = defineOracle({
id: 'my_oracle/max-ltv/v1',
paramsSchema: z.object({
max_ltv: z.number().int().min(0).max(10_000),
}).strict(),
wasmArgsSchema: z.object({
market: z.string(),
}).strict(),
secretsSchema: z.object({
MY_ORACLE_API_KEY: z.string().min(1),
}).strict(),
async prepareQuery(
args,
options?: { market?: string },
) {
return {
wasmArgs: {
market: options?.market ?? args.target,
},
}
},
deployments: {
'8453': {
prod: {
policyData: '0xPolicyData',
wasmCid: 'bafy...',
policyCodeHash: '0x...',
deployedAt: '2026-07-30',
},
},
},
metadata: {
name: 'Max LTV',
version: '1.0.0',
description: 'Checks current LTV against a configured ceiling.',
},
})
```
## ID Rules
IDs use `//`. The short ID becomes a Rego dot-path and must match `^[a-z][a-z0-9_]*$`.
* `my_oracle/max-ltv/v1` is valid.
* `my-oracle/max-ltv/v1` is invalid because the short ID contains a hyphen.
## Compose It
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.with(maxLtvOracle)
```
Custom and published modules receive identical module-set, deployment, WASM CID, and schema checks.
## Configure and Call
```typescript
await client.setParams({
vaultsfyi: { risk_score_floor: 80 },
my_oracle: { max_ltv: 7_500 },
})
await client.uploadSecrets({
my_oracle: {
MY_ORACLE_API_KEY: process.env.MY_ORACLE_API_KEY!,
},
})
await client.sendCall({
to,
data,
functionSignature,
prepareQueryOptions: {
my_oracle: { market: '0xMarket' },
},
})
```
## Generate the Params Schema
Use the schema derivation utilities from `@newton-xyz/policy-core` to generate the complete `{ _manifest, modules, params }` schema. The deployed policy validates the full manifest envelope, not only `params.my_oracle`.
Commit the generated schema alongside the policy-authorship project and regenerate it when a module schema changes.
## Deployment Boundary
The TypeScript definition is only the consumer-side contract. Taking an oracle live also requires:
1. Implementing and testing the WASM oracle.
2. Authoring fail-closed Rego.
3. Building and publishing the content-addressed artifacts.
4. Deploying `NewtonPolicyData` and a `NewtonPolicy` that references it.
5. Recording the real address and WASM CID in `deployments`.
An empty deployment record is valid during offline authoring, but `createShield(...)` cannot resolve it for a live chain.
## Provenance
Do not hard-code “audited” into the oracle object. Consumers can call `classifyProvenance(...)` with the module's objective facts and an audited-address registry they trust.
# Examples \[VaultKit 2.1 recipes for policies, typed vendor calls, generic calls, verification, and blocked-intent assertions.]
## Declare a Published Policy
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { chainalysis } from '@newton-xyz/policy-pack-chainalysis'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '8453',
env: 'prod',
})
.with(vaultsfyi)
.with(chainalysis)
```
## Attach or Deploy Idempotently
```typescript
import { createShield } from '@newton-xyz/vaultkit'
const client = await createShield({
apiKey: process.env.NEWTON_API_KEY!,
walletClient,
publicClient,
rpc: process.env.RPC_URL!,
vault: '0xVault',
policy,
policyAddress: '0xNewtonPolicy',
expectedParams: {
vaultsfyi: { risk_score_floor: 80 },
chainalysis: { deny_on_sanctioned: true },
},
})
```
An existing attachment must pass the verification ladder. A mismatch throws instead of silently changing the policy.
## Use a Bare Policy
```typescript
import { createShield, policyFromAddress } from '@newton-xyz/vaultkit'
const policy = policyFromAddress({
address: '0xNewtonPolicy',
chainId: '8453',
env: 'prod',
params: {
encode: (value) => encodeMyPolicyParams(value),
decode: (value) => decodeMyPolicyParams(value),
},
})
const client = await createShield({
apiKey,
walletClient,
rpc,
vault,
policy,
})
```
Use `policyFromAddress(...)` for a monolithic or calldata-only policy that does not use the composite module model. Do not also pass `policyAddress`; the address is already part of the bare policy.
## Add Policy-Level Parameters
```typescript
import { z } from 'zod'
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.withPolicyParams(
z.object({
allowedYieldSources: z.array(z.string()),
maxFeeBps: z.number().int().min(0).max(10_000),
}),
)
await client.setParams({
_policy: {
allowedYieldSources: ['0xYieldSource'],
maxFeeBps: 100,
},
vaultsfyi: {
risk_score_floor: 80,
},
})
```
The gate reads these composite-wide values under `data.params.params._policy`.
## Extend with a Typed Vendor Module
```typescript
import { morphoActions } from '@newton-xyz/vaultkit/vendors/morpho'
const morphoClient = client.extend(morphoActions)
await morphoClient.morpho.reallocate(
'0xMetaMorphoVault',
[{ marketParams, assets: 1_000_000n }],
{
prepareQueryOptions: {
chainalysis: { address: curatorAddress },
},
},
)
```
## Use the Generic Escape Hatch
```typescript
await client.sendCall(
{
to: '0xTarget',
data: encodedCalldata,
functionSignature: 'someManagerAction(uint256)',
prepareQueryOptions: {
vaultsfyi: { previousAllocationHash: '0x...' },
},
},
'DIRECT',
30_000,
)
```
With `sendCall(...)`, your integration owns the target, calldata, value, and human-readable function signature. Prefer a typed vendor module when one exists.
## Inspect and Reverify
```typescript
console.log(client.policy.address)
console.table(client.policy.modules)
console.log(client.verification.achievedRung)
await client.setParams(nextParams)
const current = await client.reverify()
```
For independent manifest inspection, import `introspectComposite(...)` from `@newton-xyz/policy-core`.
## Prove a Denied Intent Is Blocked
```typescript
import {
NotApprovedDelegateError,
PolicyNotDeniedError,
} from '@newton-xyz/vaultkit'
try {
const proof = await client.assertIntentBlocked({
to: '0xTarget',
data: noncompliantCalldata,
functionSignature: 'someManagerAction(uint256)',
})
console.log(proof.blocked, proof.taskId, proof.reason)
} catch (error) {
if (error instanceof PolicyNotDeniedError) {
console.error('The supplied intent was allowed, so it proves no block')
} else if (error instanceof NotApprovedDelegateError) {
console.error('Use a wallet approved for this client')
} else {
throw error
}
}
```
`assertIntentBlocked(...)` evaluates the intent and uses `eth_call` to confirm that the denied attestation reverts specifically with `InvalidAttestation`. It does not mine a transaction and does not return a transaction hash.
## Use Multiple Chains
Create one policy declaration and one client per chain. Never reuse a client while changing the wallet's active chain implicitly.
```typescript
const basePolicy = definePolicy({ chainId: '8453', env: 'prod' }).with(vaultsfyi)
const ethereumPolicy = definePolicy({ chainId: '1', env: 'prod' }).with(vaultsfyi)
const baseClient = await createShield({
apiKey,
walletClient: baseWalletClient,
rpc: baseRpc,
vault: baseVault,
policy: basePolicy,
policyAddress: basePolicyAddress,
})
const ethereumClient = await createShield({
apiKey,
walletClient: ethereumWalletClient,
rpc: ethereumRpc,
vault: ethereumVault,
policy: ethereumPolicy,
policyAddress: ethereumPolicyAddress,
})
```
# Reference \[API reference for `@newton-xyz/vaultkit` 2.1.]
## Installation
```bash
pnpm add @newton-xyz/vaultkit @newton-xyz/policy-core viem zod
```
Node 22+ is required. `@newton-xyz/policy-core`, `viem`, and `zod` are peer dependencies. Morpho packages are optional peer dependencies and are only needed for the Morpho vendor modules.
```typescript
import { VERSION } from '@newton-xyz/vaultkit'
```
`VERSION` matches the installed package version.
## Supported Chains
| Chain | Chain ID |
| --- | --- |
| Ethereum | `1` |
| Base | `8453` |
| Sepolia | `11155111` |
| Base Sepolia | `84532` |
## `definePolicy`
```typescript
function definePolicy(config: {
chainId: string
env: GatewayEnv
}): PolicyDraft
```
A `PolicyDraft` is immutable:
```typescript
const policy = definePolicy({ chainId: '8453', env: 'prod' })
.with(vaultsfyi)
.with(chainalysis)
.withPolicyParams(policyParamsSchema)
```
* `.with(module)` appends a published pack or custom oracle.
* `.withPolicyParams(schema)` declares the reserved `_policy` params object.
* Chain reads are deferred until `createShield(...)`.
## `policyFromAddress`
```typescript
function policyFromAddress(config: {
address: Address
chainId: string
env: GatewayEnv
params?: ParamCodec
}): BarePolicy
```
Use this for a deployed monolithic or calldata-only policy. `ParamCodec` can supply `encode` and `decode` to enable params methods and higher verification rungs.
## `createShield`
```typescript
function createShield(config: CreateShieldConfig): Promise
```
| Field | Type | Notes |
| --- | --- | --- |
| `apiKey` | `string` | Newton Gateway key for the selected environment. |
| `walletClient` | `WalletClient` | Curator signer; its chain selects the deployment slot. |
| `rpc` | `string` | RPC URL for reads and writes. |
| `policy` | `PolicyDraft \| BarePolicy` | Policy declaration or bare deployed policy. |
| `policyAddress?` | `Address` | Required for `PolicyDraft`; omit for `BarePolicy`. |
| `vault` | `Address` | Managed target used in deterministic addressing. |
| `publicClient?` | `PublicClient` | Reuse an existing client for reads. |
| `env?` | `GatewayEnv` | Must agree with `policy.env` when provided. |
| `gatewayUrl?` | `string` | Gateway override for testing. |
| `version?` | `bigint` | Addressing version; defaults to `0n`. |
| `policyClientAddress?` | `Address` | Attach to a known address instead of predicting. |
| `expectedParams?` | `Record` | Require canonical byte equality at attach time. |
| `attachWithoutVerify?` | `boolean` | Explicit recovery-only escape below verification rung 1. |
| `mustDeploy?` | `boolean` | Fail if an existing deployment would be attached. |
| `bypassDelaySeconds?` | `bigint` | Deployment configuration; default seven days, minimum one day. |
| `attestationExpiration?` | `bigint` | Per-call expiration override. |
| `wsRpcUrl?` | `string` | WebSocket RPC for subscription-based paths. |
## `ShieldClient`
| Member | Purpose |
| --- | --- |
| `policy` | Resolved policy address, chain, environment, params codec, and modules. |
| `verification` | Attach-time `VerificationResult`. |
| `policyClientAddress` | Onchain client address. |
| `setParams(params)` | Encode and store policy configuration. |
| `encodeParams(params)` | Return the exact encoded bytes without writing. |
| `uploadSecrets(secrets)` | Validate, encrypt, and upload per-module secrets. |
| `sendCall(args, mode?, timeoutMs?)` | Evaluate and execute a generic manager action. |
| `assertIntentBlocked(args, timeoutMs?)` | Confirm that a denied intent is rejected, without mining a transaction. |
| `prepareIntent(args)` | Build and sign an intent without submitting it. |
| `reverify()` | Refresh the verification result against current state. |
| `extend(vendorActions)` | Add a typed vendor namespace. |
## `SendCallArgs`
```typescript
interface SendCallArgs {
to: Address
data: Hex
value?: bigint
functionSignature: string
prepareQueryOptions?: Record
}
```
```typescript
type SubmissionMode = 'ATTESTATION' | 'DIRECT'
interface SendCallResult {
transactionHash: Hex
taskId: Hex
evaluationResult: boolean
}
```
## `assertIntentBlocked`
```typescript
interface PolicyBlockedResult {
blocked: true
taskId: Hex
reason: string
}
```
The method expects a denial, simulates the denied direct execution with `eth_call`, and resolves only if the revert is `InvalidAttestation`.
It can throw:
* `PolicyNotDeniedError` when the supplied action was allowed.
* `NotApprovedDelegateError` when the wallet is not authorized to call the client.
* `PolicyDeniedError` when the gateway denial cannot be submitted as the negative assertion requires.
* The decoded error for any unrelated revert.
## Verification
```typescript
interface VerificationResult {
achievedRung: 0 | 1 | 2 | 3 | 4
addressChecked: boolean
oracleSetChecked: boolean
paramsChecked: boolean
byteEqualChecked: boolean
skippedReason?: 'attachWithoutVerify' | 'freshDeploy'
}
```
A bare policy's empty oracle set satisfies the structural check vacuously. Check `client.policy.modules.length > 0` before presenting “oracle set verified” in a UI.
## Gateway Client
```typescript
function createGatewayClient(config: GatewayClientConfig): GatewayClient
function resolveGatewayUrl(
chainId: SupportedChainId,
env: GatewayEnv,
): string
```
Construct a gateway client directly only for lower-level calls such as task creation, policy simulation, or encrypted-secret storage.
## Deployment Utilities
```typescript
const SHIELD_DEPLOYMENTS: ShieldDeploymentRegistry
const SUPPORTED_CHAINS: readonly SupportedChainId[]
const GATEWAY_API_URLS: Record
function predictShieldAddress(args: PredictShieldAddressArgs): Address
function resolveShieldDeployment(
chainId: number,
env?: GatewayEnv,
registry?: ShieldDeploymentRegistry,
): ShieldDeployment
function isSupportedChainId(value: unknown): value is SupportedChainId
function isGatewayEnv(value: unknown): value is GatewayEnv
```
## Vendor Subpaths
```typescript
import { morphoActions } from '@newton-xyz/vaultkit/vendors/morpho'
import { morphoBlueActions } from '@newton-xyz/vaultkit/vendors/morpho-blue'
import { eulerActions } from '@newton-xyz/vaultkit/vendors/euler'
import { eulerVaultActions } from '@newton-xyz/vaultkit/vendors/euler-vault'
import { superformActions } from '@newton-xyz/vaultkit/vendors/superform'
```
Each extension adds its own namespace. `extend(...)` throws `ExtendCollisionError` if an extension would overwrite an existing member.
## Policy Core
Import these directly from `@newton-xyz/policy-core`:
* `defineOracle`
* `defineComposite`
* `introspectComposite`
* `classifyProvenance`
* Manifest encoders and decoders
* Composite params-schema derivation
VaultKit re-exports common policy types and `classifyProvenance`, but authoring and inspection utilities such as `defineOracle` and `defineComposite` come directly from `policy-core`.
## Error Guard
```typescript
function isShieldError(error: unknown): error is ShieldError
```
All SDK errors extend `ShieldError`. `NewtonShieldError` remains a deprecated compatibility alias.
## Related
* [Policies](/developers/vaults/sdk/policies)
* [Custom Oracles](/developers/vaults/sdk/custom-oracles)
* [Errors](/developers/vaults/sdk/errors)
# Errors \[VaultKit 2.1 error classes, stable codes, diagnostics, and retry guidance.]
Every SDK error extends `ShieldError` and carries a stable `code`. `NewtonShieldError` is retained as a deprecated alias.
```typescript
import { isShieldError, ShieldError } from '@newton-xyz/vaultkit'
try {
await operation()
} catch (error) {
if (isShieldError(error)) {
console.error(error.code, error.message, error.cause)
}
}
```
## Error Classes
| Class | Code | Retry guidance |
| --- | --- | --- |
| `PolicyDeniedError` | `policy_denied` | Do not retry unchanged input. Rebuild against current state or accept the denial. |
| `PolicyNotDeniedError` | `policy_not_denied` | `assertIntentBlocked(...)` received an allowed intent; supply a genuinely noncompliant intent. |
| `NotApprovedDelegateError` | `not_approved_delegate` | Authorize the wallet or use an approved wallet. |
| `NewtonTaskEvaluationError` | `newton_task_evaluation_failed` | Inspect `operator_errors[]`; retry only transient operator failures. |
| `AttestationTimeoutError` | `attestation_timeout` | Retry with a fresh task after backoff. |
| `GatewayError` | `gateway_error` | Transport umbrella; inspect the subtype and status. |
| `GatewayHttpError` | `gateway_http_error` | Retry 5xx; fix authentication or request errors for 4xx. |
| `GatewayRpcError` | `gateway_rpc_error` | Depends on the JSON-RPC code. |
| `GatewayTimeoutError` | `gateway_timeout` | Retry with backoff. |
| `RpcReadError` | `rpc_read_error` | Retry against a healthier RPC endpoint. This class is emitted only for reads proven to have failed for a transport reason. |
| `PolicyMismatchError` | `policy_mismatch` | Reconcile the expected policy, module set, CIDs, or params. |
| `ParamMismatchError` | `param_mismatch` | Reconcile the expected and stored params. |
| `InvalidConfigurationError` | `invalid_configuration` | Fix the caller configuration. |
| `ExistingCloneVersionError` | `existing_clone_version` | Choose the intended existing version or a genuinely new version. |
| `ShieldDeploymentNotFoundError` | `shield_deployment_not_found` | The chain is supported but that environment has no recorded deployment. |
| `UnsupportedChainError` | `unsupported_chain` | Select a supported chain. |
| `ConcurrentIntentError` | `concurrent_intent` | Wait for the in-flight call; serialize calls per client. |
| `ExtendCollisionError` | `extend_collision` | Remove or rename the colliding extension. |
| `IntentConfigurationError` | `intent_configuration` | Fix malformed intent inputs. |
| `IntentMismatchError` | `intent_mismatch` | Rebuild and re-evaluate; never submit mismatched task data. |
| `TransactionFailedError` | `transaction_failed` | Inspect `cause`; retry only if the underlying failure is transient. |
| `ShieldExecutionError` | `shield_execution` | Inspect the decoded revert before deciding. |
## RPC Reads: Transport vs Terminal
VaultKit classifies chain-read failures before exposing them. Proven network, timeout, socket, HTTP, or transient JSON-RPC failures become `RpcReadError`. Contract reverts, zero data, decode failures, and configuration mismatches remain terminal errors such as `InvalidConfigurationError` or `PolicyMismatchError`.
```typescript
try {
await createShield(config)
} catch (error) {
if (error instanceof RpcReadError) {
console.error(`Transient read failed: ${error.read}`)
// Retry against a healthy endpoint with backoff.
}
}
```
A chainless `publicClient` is probed with `getChainId()`. A terminal chain mismatch is not safe to retry.
## Failed Evaluation Diagnostics
The top-level task summary can say that quorum was not reached even when operators returned deterministic policy failures. Inspect the raw gateway result's `operator_errors[]` before retrying:
```json
{
"error_code": -32002,
"message": "Policy evaluation failed: params schema validation failed",
"operator_address": "0x...",
"retryable": false,
"timestamp": "2026-07-01T00:00:00Z"
}
```
`retryable: false` usually indicates invalid params, missing secrets, schema mismatch, or deterministic oracle evaluation. Repeating the same request will not repair it.
## Blocked-Intent Errors
`assertIntentBlocked(...)` distinguishes three important outcomes:
* A confirmed `InvalidAttestation` simulation returns `{ blocked: true, taskId, reason }`.
* An allowed action throws `PolicyNotDeniedError`.
* An unauthorized wallet throws `NotApprovedDelegateError` before the policy-block assertion, so an authorization failure is never mislabeled as policy enforcement.
For raw simulation tooling, `decodeShieldRevertName(error)` returns the decoded custom-error name when available.
## Retry Pattern
```typescript
import {
AttestationTimeoutError,
GatewayTimeoutError,
RpcReadError,
} from '@newton-xyz/vaultkit'
async function retryable(operation: () => Promise, attempts = 3) {
let lastError: unknown
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await operation()
} catch (error) {
const canRetry =
error instanceof AttestationTimeoutError ||
error instanceof GatewayTimeoutError ||
error instanceof RpcReadError
if (!canRetry) throw error
lastError = error
await new Promise((resolve) =>
setTimeout(resolve, 1_000 * 2 ** attempt),
)
}
}
throw lastError
}
```
Always log `code`, `message`, and `cause`. For decoded execution failures, also log the error name and arguments.
# Morpho Guide \[How to gate MetaMorpho curator actions with Newton VaultKit and composite policy packs.]
Morpho vault curators can route MetaMorpho manager actions through a Newton Shield before the vault receives the call. VaultKit's Morpho overlay builds the MetaMorpho calldata with Morpho's SDK, packages that exact call as an intent, asks Newton operators to evaluate the configured policy, and forwards the call only when the policy returns an approval attestation.
This guide focuses on `reallocate`, a curator-only MetaMorpho action. End-user deposits and withdrawals continue through the normal Morpho vault flow.
## What This Demonstrates
* **Exact calldata binding:** the attestation is bound to the exact `reallocate` calldata your app encodes.
* **Composite policy checks:** one Shield can enforce a Vaults.fyi-only policy or a Vaults.fyi + Chainalysis policy in a single evaluation.
* **Onchain policy parameters:** `setParams(...)` writes the curator's thresholds into the Shield-bound policy manifest.
* **Manager-action gating:** the Shield clone must hold a MetaMorpho role that can call `reallocate`.
## Target Network
The public end-to-end Morpho examples use **Base Sepolia** (`84532`) with Newton `prod`, also called testnet production. Base Sepolia is used because the MetaMorpho V1.1 factory is deployed there for testnet curator flows.
Newton environments are separate stacks. Create the key from the Newton dashboard for the same environment your Shield uses. The public Base Sepolia example uses `prod`.
## Install Packages
Every VaultKit Morpho integration needs VaultKit, policy core, Viem, the policy packs you plan to enforce, and Morpho's SDK:
```bash
pnpm add @newton-xyz/vaultkit @newton-xyz/policy-core viem zod
pnpm add @newton-xyz/policy-pack-vaultsfyi
pnpm add @morpho-org/blue-sdk @morpho-org/blue-sdk-viem @morpho-org/morpho-ts
```
For a two-module policy that also screens an address, add Chainalysis:
```bash
pnpm add @newton-xyz/policy-pack-chainalysis
```
The Morpho packages are optional peer dependencies of VaultKit. Curators who use the generic `sendCall(...)` escape hatch can encode calldata themselves, but the typed path below uses `shield.morpho.reallocate(...)`.
## Declare the Policy
Use `definePolicy().with(...)` with the modules deployed in your Newton policy. A Vaults.fyi-only policy looks like this:
```typescript
import { definePolicy } from '@newton-xyz/vaultkit'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '84532',
env: 'prod',
}).with(vaultsfyi)
```
For Vaults.fyi plus Chainalysis, install the Chainalysis pack and include both modules:
```typescript
import { chainalysis } from '@newton-xyz/policy-pack-chainalysis'
import { definePolicy } from '@newton-xyz/vaultkit'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '84532',
env: 'prod',
})
.with(vaultsfyi)
.with(chainalysis)
```
The module list must match the policy's onchain `getPolicyData()` exactly. If the Shield is configured with a policy address that references a different module set, the SDK raises a typed configuration error before executing a vault action.
## Create or Attach to a Shield
Create one client for the curator, vault, chain, policy, and version slot:
```typescript
import { createShield } from '@newton-xyz/vaultkit'
import { morphoActions } from '@newton-xyz/vaultkit/vendors/morpho'
const shield = (await createShield({
apiKey: process.env.NEWTON_API_KEY!,
walletClient,
rpc: process.env.BASE_SEPOLIA_RPC_URL!,
publicClient,
policy,
policyAddress: '0xCompositePolicyAddress',
vault: '0xMetaMorphoVaultAddress',
bypassDelaySeconds: 7n * 24n * 60n * 60n,
})).extend(morphoActions)
console.log(shield.policyClientAddress)
```
`createShield(...)` is idempotent. If a previous run did not finish writing policy params, use `attachWithoutVerify: true` only for a recovery run that immediately calls `setParams(...)`, then remove the flag and call `reverify()`.
## Configure Params and Secrets
Write policy thresholds onchain and upload oracle secrets through Newton Gateway:
```typescript
await shield.setParams({
vaultsfyi: {
apy_z_max: 3,
risk_score_floor: 80,
tvl_drawdown_24h_max_pct: 25,
tvl_drawdown_7d_max_pct: 40,
deny_on_allocation_change: true,
deny_on_critical_flag: true,
deny_on_corrupted: true,
},
})
await shield.uploadSecrets({
vaultsfyi: {
VAULTS_FYI_API_KEY: process.env.VAULTSFYI_API_KEY!,
},
})
```
For a Vaults.fyi + Chainalysis composite, include the Chainalysis params and secrets required by that pack. The OFAC sanctions endpoint is public, but the policy schema may still require a non-empty secret value; use the key or placeholder required by your deployed policy.
## Grant the Shield a Morpho Role
The Shield has two separate authorization gates:
* Shield's delegate gate controls who can call `execute` or `executeDirect`.
* MetaMorpho's manager roles control what the forwarded call can do on the vault.
The initial Shield owner is approved as a delegate at clone initialization. After an ownership handoff or when using a separate hot key, approve the executor explicitly:
```typescript
await walletClient.writeContract({
address: shield.policyClientAddress,
abi: shieldAbi,
functionName: 'setApprovedDelegate',
args: [account.address, true],
})
```
MetaMorpho protects `reallocate` with its allocator-role check. The Shield clone must be one of:
* the vault curator,
* the vault owner, or
* an approved allocator.
Grant the role from the vault owner before running the gated action. Most curator integrations use `setCurator(shield.policyClientAddress)` or `setIsAllocator(shield.policyClientAddress, true)`.
If the Shield can configure policy params but `reallocate` reverts, check this role first. Newton can approve the intent, but MetaMorpho will still reject the forwarded call if the Shield clone is not authorized on the vault.
## Execute `reallocate`
With the Morpho overlay attached, call the typed action:
```typescript
const allocations = [
{ marketParams: marketParamsA, assets: 1_000_000n },
{ marketParams: marketParamsB, assets: 0n },
]
const result = await shield.morpho.reallocate(
'0xMetaMorphoVaultAddress',
allocations,
{
prepareQueryOptions: {
vaultsfyi: { previousAllocationHash: '0x...' },
},
},
)
```
`reallocate` takes positional arguments: `(vault, allocations, options?)`. Each allocation uses Morpho's `marketParams` shape plus `assets`. For a Vaults.fyi + Chainalysis composite, include the per-call screening input your deployed pack expects under `prepareQueryOptions.chainalysis`. Omit it for a Vaults.fyi-only policy.
The returned result includes the onchain transaction hash and Newton task context. A successful run with `allow: true` means the full path worked: exact calldata, policy evaluation, attestation, and Shield execution.
Use `shield.sendCall(...)` only as an escape hatch for vendors or actions that VaultKit does not wrap yet. With `sendCall`, your integration owns calldata integrity.
## Read-Only Versus Full Reproduce
You can validate most of a Morpho integration before granting a vault role:
| Path | What it proves | Requirements |
| --- | --- | --- |
| Read-only inspection | Policy source, params, and deployed Shield configuration are coherent. | Public RPC and deployed addresses. |
| Type-check and encode | Your app compiles against the published package surface and produces MetaMorpho calldata. | Node, package install, and Morpho SDK. |
| Setup stages | `definePolicy`, `createShield`, `setParams`, and `uploadSecrets` work. | Funded curator key and Newton/API secrets. |
| Full execution | MetaMorpho accepts the Shield-forwarded `reallocate`. | Shield clone has curator, owner, or allocator permissions. |
## Vaults.fyi Testnet Data
Vaults.fyi indexes production networks, not testnets. When testing a Shield on Base Sepolia, a Vaults.fyi policy can fail closed because there is no testnet vault data for the lookup target.
For demos, point the Vaults.fyi lookup at a real mainnet vault through the policy pack's data-source override fields while the Shield still executes on Base Sepolia. In production, remove the override so the policy describes the same vault the Shield gates.
## Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `401` from Newton Gateway | API key was minted for a different Newton environment. | Create a key for the same stack as `env`, usually `prod` for Base Sepolia testnet production. |
| `UnsupportedChainError` | Wallet client is not on a VaultKit-supported chain. | Use a supported chain such as Base Sepolia (`84532`) for testnet Morpho flows. |
| `ShieldDeploymentNotFoundError` | No Shield factory is registered for the selected chain and environment. | Check `env`, chain id, and RPC configuration. |
| Module-set mismatch or composite policy mismatch | The modules declared with `definePolicy().with(...)` do not match the policy address. | Use a policy deployed with exactly the selected modules. |
| `PolicyMismatchError` | An existing Shield clone is bound to different policy data. | Verify the clone, write the expected params in a recovery run, or use a new `version`. |
| MetaMorpho role revert | The Shield clone is not curator, owner, or allocator. | Grant the clone a qualifying vault role, then rerun the action. |
| `PolicyDeniedError` | Newton evaluated the policy and denied the intent. | Inspect the error cause, adjust params, or choose inputs that satisfy the policy. |
| Vaults.fyi oracle errors or unexpected denials | Missing Vaults.fyi secret or no data for the lookup target. | Upload the API key and use a production-network data-source override for testnet demos. |
| `AttestationTimeoutError` or `GatewayError` | Gateway was slow or unavailable. | Retry with backoff and a fresh task. |
Install VaultKit, configure a Shield, and execute a policy-gated vault action.
Review typed errors and retry guidance.
# Morpho Blue \[Use VaultKit's typed Morpho Blue position module for managed lending sleeves.]
`vendors/morpho-blue` wraps position operations on the Morpho Blue singleton. It is distinct from `vendors/morpho`, which wraps MetaMorpho curator and allocator governance.
## Install and Extend
```bash
pnpm add @newton-xyz/vaultkit @morpho-org/blue-sdk @morpho-org/blue-sdk-viem
```
```typescript
import { morphoBlueActions } from '@newton-xyz/vaultkit/vendors/morpho-blue'
const client = (await createShield(config)).extend(morphoBlueActions)
```
## Supply Collateral
```typescript
await client.morphoBlue.supplyCollateral(
morphoBlueSingleton,
{
marketParams,
assets: 1_000_000n,
onBehalf: managedVault,
},
{
prepareQueryOptions: {
venue_allowlist: { venue: morphoBlueSingleton },
},
},
)
```
The first argument is the singleton address. The market is identified by `marketParams` inside calldata.
## Wrapped Operations
| Method | Purpose |
| --- | --- |
| `supplyCollateral` | Post collateral for a managed position. |
| `borrow` | Borrow assets or shares against collateral. |
| `supply` | Supply loan-token liquidity. |
| `repay` | Repay assets or shares. |
| `withdraw` | Withdraw supplied loan-token liquidity. |
| `withdrawCollateral` | Withdraw posted collateral. |
Methods that accept an amount require exactly one of:
```typescript
{ assets: 1_000_000n }
{ shares: 1_000_000n }
```
Both-zero and both-specified inputs are rejected before submission.
## Safety Constraints
* Callback data is always `0x`; the client contract is not a Morpho callback receiver.
* Position owners and receivers cannot be the zero address, singleton, or an address copied from `marketParams`.
* The module does not prove that `onBehalf` is the managed vault or that `receiver` is approved. Bind those constraints in the policy.
* `marketParams.lltv` uses 1e18 WAD scale. Euler EVault LTV values use a different 1e4 scale.
Use this module only for positions operated as part of a managed vault or sleeve. It is not intended to interpose on direct end-user Morpho positions.
# Euler Guide \[How to gate Euler Vault Kit EVault governor actions with Newton VaultKit.]
Euler Vault Kit EVaults can route governor actions through a Newton Shield before the EVault receives the call. VaultKit's EVault overlay encodes the governor calldata, asks Newton operators to evaluate the configured policy, and forwards the call only when the policy returns an approval attestation.
This guide covers `shield.eulerVault.*`, which wraps EVault governor functions. For Euler Earn aggregation-vault curator and allocator actions such as `reallocate`, use the [Euler Earn Guide](/developers/vaults/sdk/euler).
## Install Packages
```bash
pnpm add @newton-xyz/vaultkit @newton-xyz/policy-core viem zod
pnpm add @newton-xyz/policy-pack-vaultsfyi
```
Add any other policy-pack modules your Shield enforces, such as RedStone or Chainalysis:
```bash
pnpm add @newton-xyz/policy-pack-redstone @newton-xyz/policy-pack-chainalysis
```
## Attach the EVault Overlay
Create or attach to a Shield, then extend the client with `eulerVaultActions`:
```typescript
import { createShield, definePolicy } from '@newton-xyz/vaultkit'
import { eulerVaultActions } from '@newton-xyz/vaultkit/vendors/euler-vault'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '8453',
env: 'prod',
}).with(vaultsfyi)
const shield = (await createShield({
apiKey: process.env.NEWTON_API_KEY!,
walletClient,
rpc: process.env.RPC_URL!,
publicClient,
policy,
policyAddress: '0xCompositePolicyAddress',
vault: '0xEVault',
})).extend(eulerVaultActions)
```
`shield.eulerVault.*` produces calldata from EVault's governor ABI and routes the call through `shield.sendCall(...)`. The composite policy pack still owns `wasmArgs`; pass per-call policy inputs through `prepareQueryOptions`.
## Grant the Shield EVault Governance
EVault governor actions are manager actions. Borrowers and suppliers continue through normal EVault flows and do not route through the Shield.
Every wrapped action is callable by the EVault governor, also called `governorAdmin`. To use the Shield as the policy gate, the current governor should set the EVault governor to the Shield clone or route the relevant governance authority through the clone:
```typescript
await walletClient.writeContract({
address: '0xEVault',
abi: eVaultAbi,
functionName: 'setGovernorAdmin',
args: [shield.policyClientAddress],
})
```
After the Shield clone is the governor, `shield.eulerVault.setGovernorAdmin(...)` can rotate governance again through the same policy-gated path.
Unlike Euler Earn, EVault governor setters are not a two-step onchain timelock flow. A policy approval plus the EVault governor check is the control surface. `setLTV` includes a `rampDuration` argument for lowered liquidation LTVs, but that is a parameter ramp, not a governance timelock.
## Configure LTV
```typescript
await shield.eulerVault.setLTV(
'0xEVault',
{
collateral: '0xCollateralEVault',
borrowLtv: 8000,
liquidationLtv: 8500,
rampDuration: 0,
},
{
prepareQueryOptions: {
vaultsfyi: { previousAllocationHash: '0x...' },
},
},
)
```
`borrowLtv` and `liquidationLtv` use 1e4 scale, so `8000` means 80%. `liquidationLtv` must be greater than or equal to `borrowLtv`.
## Configure Caps
Pass caps as asset-denominated amounts in the vault asset's smallest unit, or `"unlimited"`. VaultKit encodes them to EVault's packed `AmountCap` format before building calldata.
```typescript
await shield.eulerVault.setCaps('0xEVault', {
supplyCap: 10_000_000_000n,
borrowCap: 5_000_000_000n,
})
await shield.eulerVault.setCaps('0xEVault', {
supplyCap: 'unlimited',
borrowCap: 2_500_000_000n,
})
```
Do not pre-encode the packed `uint16` cap yourself when using `shield.eulerVault.setCaps(...)`; the helper expects human asset amounts and rounds up to the nearest representable EVault cap.
## Wrapped Actions
| Category | Helpers |
| --- | --- |
| Governor administration | `setGovernorAdmin` |
| Risk parameters | `setLTV`, `setMaxLiquidationDiscount`, `setLiquidationCoolOffTime`, `setInterestRateModel`, `setCaps` |
| Vault configuration | `setHookConfig`, `setConfigFlags` |
| Fees | `setInterestFee`, `setFeeReceiver` |
VaultKit intentionally does not wrap end-user `deposit`, `mint`, `withdraw`, `redeem`, `borrow`, or `repay` actions.
## Other Examples
```typescript
await shield.eulerVault.setInterestRateModel('0xEVault', '0xInterestRateModel')
await shield.eulerVault.setMaxLiquidationDiscount('0xEVault', 1200)
await shield.eulerVault.setHookConfig('0xEVault', {
hookTarget: '0xHookContract',
hookedOps: 0b101,
})
await shield.eulerVault.setInterestFee('0xEVault', 1000)
await shield.eulerVault.setFeeReceiver('0xEVault', '0xFeeReceiver')
```
Scale fields follow the EVault ABI. LTVs, liquidation discounts, and interest fees use 1e4 scale.
## Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| EVault governor revert | The Shield clone is not the EVault governor for that action. | Grant or route `governorAdmin` to the Shield clone before executing. |
| Wrong caps onchain | Raw packed caps were passed instead of asset-denominated amounts. | Pass asset amounts or `"unlimited"` to `setCaps`; VaultKit handles packing. |
| `PolicyDeniedError` | Newton evaluated the configured policy and denied the intent. | Inspect the policy result, params, and per-call `prepareQueryOptions`. |
| Unexpected calldata | The wrong overlay or vault address was used. | Use `shield.eulerVault.*` only for EVaults; use `shield.euler.*` for Euler Earn vaults. |
Gate Euler Earn curator and allocator actions with `shield.euler.*`.
Review the generic `sendCall` API and expected error types.
# Euler Earn Guide \[How to gate Euler Earn manager and allocator actions with Newton VaultKit.]
Euler Earn vaults can route curator and allocator actions through a Newton Shield before the Euler Earn vault receives the call. VaultKit's Euler Earn overlay encodes the Euler calldata, packages that exact call as an intent, asks Newton operators to evaluate the configured policy, and forwards the call only when the policy returns an approval attestation.
This guide covers `shield.euler.*`, which wraps Euler Earn. For Euler Vault Kit base lending vault governor actions such as `setLTV`, `setCaps`, and `setInterestRateModel`, use the [Euler Guide](/developers/vaults/sdk/euler-vault).
## Install Packages
```bash
pnpm add @newton-xyz/vaultkit @newton-xyz/policy-core viem zod
pnpm add @newton-xyz/policy-pack-vaultsfyi
```
Add any other policy-pack modules your Shield enforces, such as Chainalysis:
```bash
pnpm add @newton-xyz/policy-pack-chainalysis
```
## Attach the Euler Earn Overlay
Create or attach to a Shield, then extend the client with `eulerActions`:
```typescript
import { createShield, definePolicy } from '@newton-xyz/vaultkit'
import { eulerActions } from '@newton-xyz/vaultkit/vendors/euler'
import { vaultsfyi } from '@newton-xyz/policy-pack-vaultsfyi'
const policy = definePolicy({
chainId: '8453',
env: 'prod',
}).with(vaultsfyi)
const shield = (await createShield({
apiKey: process.env.NEWTON_API_KEY!,
walletClient,
rpc: process.env.RPC_URL!,
publicClient,
policy,
policyAddress: '0xCompositePolicyAddress',
vault: '0xEulerEarnVault',
})).extend(eulerActions)
```
`shield.euler.*` produces calldata from Euler Earn's ABI and routes the call through `shield.sendCall(...)`. The composite policy pack still owns `wasmArgs`; pass per-call policy inputs through `prepareQueryOptions`.
## Grant the Shield an Euler Earn Role
Euler Earn manager actions are not end-user deposit or withdrawal flows. The Shield clone must hold a role that can call the forwarded function on the Euler Earn vault:
| Role | Typical actions |
| --- | --- |
| Owner | `setCurator`, `setIsAllocator`, `setFeeRecipient`, `setFee`, `submitTimelock`, `submitGuardian`, `setName`, `setSymbol` |
| Curator | `submitCap`, `acceptCap`, `revokePendingCap`, `submitMarketRemoval`, `revokePendingMarketRemoval` |
| Allocator | `reallocate`, `setSupplyQueue`, `updateWithdrawQueue` |
| Guardian | `revokePendingTimelock`, `revokePendingGuardian`, and other veto-style pending changes |
A Shield clone holding the curator role can also call allocator actions. Most integrations grant the clone the curator role or mark it as an allocator before executing policy-gated actions.
## Reallocate
Use the typed reallocation helper for the allocator hot path:
```typescript
const allocations = [
{ id: '0xMarketA', assets: 1_000_000n },
{ id: '0xMarketB', assets: 0n },
]
const result = await shield.euler.reallocate(
'0xEulerEarnVault',
allocations,
{
prepareQueryOptions: {
vaultsfyi: { previousAllocationHash: '0x...' },
},
},
)
```
`reallocate` takes positional arguments: `(vault, allocations, options?)`. Each allocation uses Euler Earn's `{ id, assets }` shape, where `id` is the market address and `assets` is the amount in the vault asset's smallest unit.
## Manage Queues and Caps
Allocator queue updates:
```typescript
await shield.euler.setSupplyQueue('0xEulerEarnVault', [
'0xMarketA',
'0xMarketB',
])
await shield.euler.updateWithdrawQueue('0xEulerEarnVault', [1n, 0n])
```
Curator cap changes use Euler Earn's timelocked two-step flow:
```typescript
await shield.euler.submitCap('0xEulerEarnVault', '0xMarketA', 5_000_000n)
// After the Euler Earn timelock matures:
await shield.euler.acceptCap('0xEulerEarnVault', '0xMarketA')
```
Pending cap and market-removal changes can be revoked through the matching typed helpers.
## Wrapped Actions
| Category | Helpers |
| --- | --- |
| Role setup | `setCurator`, `setIsAllocator` |
| Allocator operations | `reallocate`, `setSupplyQueue`, `updateWithdrawQueue` |
| Market caps | `submitCap`, `acceptCap`, `revokePendingCap` |
| Market removal | `submitMarketRemoval`, `revokePendingMarketRemoval` |
| Timelock | `submitTimelock`, `acceptTimelock`, `revokePendingTimelock` |
| Guardian | `submitGuardian`, `acceptGuardian`, `revokePendingGuardian` |
| Fees and metadata | `setFee`, `setFeeRecipient`, `setName`, `setSymbol` |
VaultKit intentionally does not wrap end-user ERC-4626 `deposit`, `mint`, `withdraw`, or `redeem` actions. Ownership transfer helpers are also omitted; use direct governance operations or the raw `shield.sendCall(...)` escape hatch only when your policy explicitly models that action.
## Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Euler Earn role revert | The Shield clone is not owner, curator, allocator, or guardian for that action. | Grant the clone the correct Euler Earn role, then rerun the action. |
| `PolicyDeniedError` | Newton evaluated the configured policy and denied the intent. | Inspect the policy result, params, and per-call `prepareQueryOptions`. |
| Module-set mismatch | The modules declared with `definePolicy().with(...)` do not match the deployed policy. | Use the module set from the policy's onchain `getPolicyData()`. |
| Unexpected calldata | The wrong overlay or vault address was used. | Use `shield.euler.*` only for Euler Earn vaults; use `shield.eulerVault.*` for EVault governor actions. |
Gate Euler Vault Kit EVault governor actions with `shield.eulerVault.*`.
Review the generic `sendCall` API and expected error types.
# Superform \[Gate Superform SuperVault strategy-manager actions and build typed hook bundles with VaultKit.]
The Superform module targets the `SuperVaultStrategy` manager surface. It does not wrap depositor `deposit`, `mint`, `withdraw`, `redeem`, or request flows.
## Extend the Client
```typescript
import { superformActions } from '@newton-xyz/vaultkit/vendors/superform'
const client = (await createShield(config)).extend(superformActions)
```
Superform does not publish a calldata-builder SDK for this surface, so VaultKit maintains typed encoders and checks their selectors against the strategy ABI.
## Typed Hook Execution
```typescript
await client.superform.executeHooks(
strategy,
[
{
kind: 'approveDeposit',
hook: approveAndDeposit4626Hook,
yieldSourceOracleId: '0x...',
yieldSource: sleeveVault,
token: sleeveAsset,
amount: 1_000_000n,
usePrevHookAmount: false,
expectedAssetsOrSharesOut: 990_000n,
globalProof,
strategyProof,
},
],
0n,
{
prepareQueryOptions: {
vaultsfyi: { vault: sleeveVault },
},
},
)
```
The `approveDeposit` variant builds the allowance and ERC-4626 deposit fields together. Use it when the strategy does not already have a standing allowance.
Typed hook kinds are:
* `swap`
* `deposit`
* `approveDeposit`
* `redeem4626`
`executeHooksRaw(...)` accepts prebuilt bytes for hooks not yet modeled. That path gives the caller responsibility for inner-calldata integrity.
## Other Manager Actions
| Method | Purpose |
| --- | --- |
| `fulfillRedeemRequests` | Settle queued redeem requests as a manager. |
| `fulfillCancelRedeemRequests` | Settle cancelled redeems. |
| `skimPerformanceFee` | Skim accrued performance fees. |
| `manageYieldSource` | Add, update, or remove a yield source. |
| `proposeVaultFeeConfigUpdate` | Propose performance and management fees. |
| `executeVaultFeeConfigUpdate` | Apply the pending fee configuration. |
| `managePPSExpiration` | Propose, execute, or cancel a PPS-expiration update. |
Some operations require the strategy's primary-manager role rather than a secondary-manager role. Confirm the target strategy's authorization before building the intent.
## Hook Authorization
Superform validates hook bundles with its own hook registry and Merkle proofs. Newton policy evaluation is an additional gate; it does not replace Superform's authorization.
Policies that constrain the hook target, token, venue, amount, or receiver must inspect the decoded intent and fail closed. A data oracle alone cannot prove those exact calldata fields because oracle query inputs are caller-supplied.
## Pure Hook Utilities
The subpath also exports builders and decoders:
```typescript
import {
buildApproveAndDepositHookData,
buildSwapHookData,
decodeHook,
makeHookRegistry,
} from '@newton-xyz/vaultkit/vendors/superform'
```
Use these for authoring, dashboards, and inspection. Runtime enforcement remains in the deployed policy.
# VaultKit Legal Terms \[VaultKit supplement to the Magic Developer Terms of Service.]
Effective Date: June 23, 2026
This VaultKit Supplement (this "Supplement") supplements the Magic Developer Terms of Service (the "Developer Terms") and, as applicable, the Magic Terms of Service (together, the "Existing Terms"). It governs your access to and use of VaultKit by Magic Labs, Inc. ("Magic"). Capitalized terms not defined here have the meanings given in the Existing Terms. In the event of a conflict between this Supplement and the Existing Terms with respect to VaultKit, this Supplement controls.
## 1. Definitions
* "Data Provider" means a third-party data service whose outputs inform Policy evaluation.
* "Newton Protocol" means the decentralized policy-based transaction authorization protocol.
* "Policy / Policy Pack" means the policy rules and parameters you configure through VaultKit.
* "Secrets" means API credentials or keys uploaded by you via VaultKit for use by operators during Policy evaluation.
* "Shield Contract" means a smart contract that requires a valid attestation before executing a Vault Action.
* "Vault Action" means a vault management operation executed through a Shield Contract.
* "VaultKit" means the software tools and documentation provided by Magic under this Supplement. VaultKit does not include the Gateway.
## 2. Newton Protocol
Newton Protocol is stewarded by the Magic Newton Foundation and its subsidiares. Magic is not responsible for the availability, performance, or accuracy of Newton Protocol, the Gateway (except as stated in Section 6), the operator network, or the production of attestations. Your use of Newton Protocol is governed solely by the Magic Newton Foundation's applicable terms.
## 3. Shield Contracts
Shield Contracts are deployed by you and are owned and controlled by you once deployed. Magic has no ability to access, modify, pause, or interact with a deployed Shield Contract. You are solely responsible for Shield Contract configuration, security, and operation, including selection and configuration of Policy Packs and all associated parameters. Magic does not audit, certify, or guarantee the correctness or security of Shield Contract code or Policy Pack logic.
## 4. Policy Evaluation; No Compliance Guarantee
A policy evaluation is not a legal compliance determination. You retain all independent compliance obligations and may not satisfy those obligations by relying solely on VaultKit policy evaluation. VaultKit does not replace your obligation to maintain appropriate compliance programs. Magic makes no warranty that any Policy or Policy Pack will correctly identify sanctioned addresses, high-risk counterparties, unsafe vault conditions, or any other category of restricted activity. You are responsible for reviewing, testing, and validating all Policy configurations prior to and during production use.
## 5. Third-Party Data Providers
Policy Packs may use outputs from Data Providers to inform policy decisions. Policy evaluation is only as accurate and timely as the Data Provider's API response at the time of evaluation. Magic does not control, verify, or guarantee the accuracy, completeness, timeliness, or availability of any Data Provider output. Magic is not responsible for losses or compliance failures arising from Data Provider unavailability or incorrect outputs. You are solely responsible for obtaining and maintaining any agreements required by applicable Data Providers, complying with their terms, and paying any associated fees.
## 6. Gateway
Magic stores encrypted Secrets in the Gateway, providing limited access to authorized Newton Protocol operators for the purpose of Policy evaluation and to Magic personnel for Gateway maintenance. Magic's obligations do not extend to Secrets after retrieval by Newton Protocol operators. Magic may change or remove the Gateway's functionality at its discretion.
## 7. Vault Management; Curator Responsibility
VaultKit is designed for use by vault curators who manage vault strategies on behalf of depositors. Magic is not a party to any arrangement between you and vault depositors or any third-party, does not owe any duty to vault depositors or any third party, and is not responsible for losses suffered by vault depositors or any third-party as a result of Vault Actions taken or not taken through VaultKit, including losses arising from policy denials, policy approvals, attestation timeouts, or misconfigured Policies.
Nothing in VaultKit or its outputs constitutes investment advice or any recommendation regarding the management or allocation of vault assets. You are solely responsible for all investment and management decisions made in connection with vault operations.
## 8. Bypass Mechanism
When you execute a Vault Action through the bypass mechanism, Policy controls, Data Provider checks, and all associated guardrails do not apply. You assume full responsibility for all consequences of any Vault Action executed through the bypass mechanism, including financial, legal, and regulatory exposure. Magic has no liability for losses arising from your use of the bypass.
## 9. Additional Disclaimers
In addition to the disclaimers in the Existing Terms, and without limiting them, Magic disclaims all representations and warranties with respect to VaultKit, including that: (a) any Policy evaluation will correctly identify sanctioned persons, prohibited transactions, or violations of applicable law; (b) the Gateway will be available on an uninterrupted or error-free basis; (c) Newton Protocol or the operator network will be available at any particular time; (d) any Data Provider output is accurate, current, or complete; (e) any Shield Contract will execute correctly; or (f) use of VaultKit will satisfy any legal, regulatory, fiduciary, or contractual obligation you owe to any person, including vault depositors. VaultKit is provided "as is" to the maximum extent permitted by applicable law.
## 10. General
This Supplement is incorporated into and forms part of the Developer Terms. All provisions of the Existing Terms that are not modified by this Supplement continue to apply to your use of VaultKit, including without limitation the limitation of liability, indemnification, termination, and governing law provisions of the Developer Terms. Magic reserves the right to modify this Supplement in accordance with Section 13.3 of the Developer Terms. Questions regarding this Supplement should be directed to legal@magic.link.
# What is Newton? \[Newton Protocol is a decentralized policy engine for onchain transaction authorization, built as an EigenLayer AVS. Enforce spend limits, sanctions screening, fraud prevention, and compliance rules in smart contracts.]
Newton Protocol is a [policy engine](/developers/overview/core-concepts) that lets you encode, verify, and enforce rules — spend limits, sanctions screening, fraud prevention — directly within your smart contracts.
### The problem
Smart contracts are **blind to offchain context**: whether a user is sanctioned, whether an AI agent is hallucinating, or whether a transaction violates a corporate spend policy. Traditional security relies on frontend filters or centralized API checks, leaving protocols vulnerable to unauthorized actions from third-party aggregators, autonomous agents, or direct contract calls.
### The solution
Newton bridges this gap with real-time offchain data — KYC status, market feeds, proof of reserves — evaluated by a [decentralized operator network](/developers/concepts/consensus-security) and enforced at the smart contract level. Your protocol stays protected regardless of where the transaction originates.
### Modular and chain-agnostic
Newton integrates with [most EVM-compatible networks](/developers/concepts/multichain) including Ethereum, Base, and Arbitrum. Non-EVM support is on the roadmap.
See a live example of a Newton-powered sanctions-checked transfer app
Get in touch to discuss your integration or partnership opportunity
## What it looks like
```ts twoslash
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
import { newtonWalletClientActions } from '@newton-xyz/sdk'
// ---cut---
async function simulate() {
const client = createWalletClient({
account: privateKeyToAccount('0x' + '1'.repeat(64) as `0x${string}`),
chain: sepolia,
transport: http(),
}).extend(newtonWalletClientActions({ apiKey: 'test-api-key' }))
const sender = '0x0000000000000000000000000000000000000000' as `0x${string}`
const recipient = '0x0000000000000000000000000000000000000001' as `0x${string}`
const calldata = '0x' as `0x${string}`
const policyId = '0x' + '0'.repeat(64) as `0x${string}`
const policyAddress = '0x1234567890123456789012345678901234567890' as `0x${string}`
// Simulate a policy evaluation against a transaction intent
const result = await client.simulateTask({
intent: { from: sender, to: recipient, value: '0x0', data: calldata, chainId: 11155111, functionSignature: '0xa9059cbb' },
policyTaskData: { policyId, policyAddress, policy: '0x', policyData: [] },
})
console.log(result.result?.allow) // true or false
}
```
Run this example end-to-end in 5 minutes
## Walkthrough
## Why Newton
* **Verifiable Trust:** Every compliance decision is backed by a [BLS attestation](/developers/overview/core-concepts#attestation), not reputation.
* **Privacy-Preserving:** Only hashes and commitments are onchain — no PII or sensitive data exposure. See the [privacy layer](/developers/concepts/privacy-layer).
* **Composable:** Works with wallets, dApps, AI agents, and DeFi protocols through a [standard SDK interface](/developers/reference/sdk-reference).
* **Chain-Agnostic:** Newton proofs can be verified across [any supported EVM chain](/developers/concepts/multichain) via lightweight verifier contracts.
* **Extensible:** Supports both open-source policies and enterprise modules for regulated financial institutions.
## Next Steps
Simulate your first policy evaluation in 5 minutes
Understand policies, intents, tasks, and attestations
# Core Concepts \[Understand Newton Protocol's key concepts — policies, intents, tasks, attestations, and the evaluation lifecycle. How onchain policy authorization works end-to-end.]
Newton Protocol is a policy engine for onchain transaction authorization, built as an EigenLayer Actively Validated Service (AVS). This page explains how the core pieces fit together. Refer back here as you work through the [integration guide](/developers/guides/integration-guide).
## How Newton Works
You write rules (policies) that define when a transaction should be allowed. When a user submits a transaction (intent), Newton's decentralized operator network evaluates it against your policy, and returns a cryptographic proof (attestation) that your smart contract verifies before executing.
```mermaid
flowchart LR
A[User submits Intent] --> B[Newton Gateway]
B --> C[AVS Operators evaluate policy set]
C --> D[BLS Attestation returned]
D --> E[Smart contract verifies proof]
E --> F[Transaction executes]
```
## Key Concepts
### Policy
A **Policy** is a Rego program that defines the conditions an Intent must meet to be approved. Policies are stored on IPFS (referenced by CID) and are reusable across multiple PolicyClients.
Policies can reference two data sources:
* **`data.params`** — configuration parameters set by the PolicyClient owner (e.g., spend limits, allowlists)
* **`data.wasm`** — runtime data fetched by PolicyData WASM oracles (e.g., token prices, KYC status)
You write policies in [Step 2 of the integration guide](/developers/guides/writing-policies). See the [Rego syntax guide](/developers/advanced/rego-syntax-guide) for language reference.
### Policy set
A client owns an ordered list of policy specs. Each spec contains a deployed Policy address and `{ policyParams, expireAfter }`. Every policy must allow for the aggregate `allowed` result to be true. Order and repeated entries matter.
Owner `setPolicies` atomically replaces the entire list. Every successful replacement increments the revision, including identical replacements. The set ID commits to chain, client, revision, and ordered configuration, so reconfiguration invalidates previous attestations. Read `getPolicySetSnapshot()` for a consistent view.
Each Policy carries at most one optional WASM module. Pure-Rego policies use no WASM; they still need an empty `wasmArgs` slot. Outputs are not merged across policies.
### Intent
An **Intent** is a proposed transaction submitted for policy evaluation. It contains standard EVM fields:
| Field | Description |
|-------|-------------|
| `from` | Transaction sender address |
| `to` | Transaction recipient address |
| `value` | Wei value to transfer |
| `data` | Transaction calldata |
| `chain_id` | Target chain ID |
| `function_signature` | ABI-encoded function signature |
You construct Intents when calling [`simulatePolicy`](/developers/reference/sdk-reference) or [`submitEvaluationRequest`](/developers/reference/sdk-reference) in the SDK.
### Task
A **Task** pairs an Intent with a frozen ordered policy set, its ID, and its revision for evaluation. Tasks are the atomic unit of evaluation in Newton — each Task is either **compliant** or **non-compliant**. You can track task status via the [RPC API](/developers/reference/rpc-api), [Newton Explorer](/developers/resources/newton-explorer), or programmatically with the [Explorer API](/developers/reference/explorer-api).
### Attestation
An **Attestation** is a cryptographic proof (BLS aggregate signature) that Newton operators evaluated a policy and approved or rejected the Intent. Attestations contain the `taskId`, `policyId`, evaluation result, and expiration block. Learn how attestations are produced in [Consensus & Security](/developers/concepts/consensus-security).
### PolicyClient
A **PolicyClient** is a smart contract that validates attestations before executing transactions. You integrate Newton into your contract by inheriting `NewtonPolicyClient` and calling `_validateAttestation()` or `_validateAttestationDirect()` in your function modifiers. See [Smart Contract Integration](/developers/guides/smart-contract-integration) for implementation.
### PolicyData
A **PolicyData** oracle is a WASM component that fetches or computes external data at evaluation time. The output is fed to the Rego policy as `data.wasm`. Examples include fetching token prices, checking sanctions lists, or verifying KYC status. See [Writing Data Oracles](/developers/guides/writing-data-oracles) to build one.
### Operator
An **Operator** is an EigenLayer node registered with the Newton AVS. Operators independently evaluate tasks, and their individual BLS signatures are aggregated into a single consensus proof once quorum is reached. See [Consensus & Security](/developers/concepts/consensus-security) for details on quorum and slashing.
### Gateway
The **Gateway** is the JSON-RPC 2.0 endpoint that receives tasks and coordinates operator evaluation. All SDK and CLI interactions go through the Gateway. See the [RPC API reference](/developers/reference/rpc-api) for available methods.
## Evaluation Lifecycle
```mermaid
flowchart TD
A[Deploy Policy] --> B[Configure PolicyClient]
B --> C[Submit Task]
C --> D[Operators Evaluate]
D --> E[Attestation Returned]
```
:::steps
### Developer deploys a Policy
Publish each reusable Policy with Rego, schema, metadata, and optional WASM and secrets-schema CIDs. The policy is stored on IPFS and referenced by CID. See [Deploying with CLI](/developers/guides/deploying-with-cli).
### User configures a PolicyClient
Configure a PolicyClient with `setPolicies`: an ordered list of independently deployed policies, each with its own parameters and expiration. See [Smart Contract Integration](/developers/guides/smart-contract-integration).
### Caller submits a Task
An Intent is paired with the PolicyClient and sent to the Newton Gateway via the [SDK](/developers/reference/sdk-reference) or [RPC API](/developers/reference/rpc-api).
### Operators evaluate
AVS operators independently fetch PolicyData, evaluate the Rego policy, and produce individual BLS signatures. The Aggregator collects signatures into a single consensus proof once quorum is reached. See [Consensus & Security](/developers/concepts/consensus-security).
### Attestation returned
The aggregated attestation is returned to the caller, who submits it on-chain. The contract validates the proof and executes or blocks the transaction. See the [Frontend SDK Integration](/developers/guides/frontend-sdk-integration) for how to handle this in your app.
:::
## Architecture Layers
| Layer | Purpose | Components |
|-------|---------|------------|
| **Policy Layer** | Defines policies, schemas, rules, thresholds, offchain inputs | Policy Registry, Policy Library |
| **Compute & Consensus Layer** | Offchain policy evaluation by AVS operators, outputs verifiable proofs | AVS Operators, Aggregator, Consensus Proofs |
| **Verification & Execution Layer** | Onchain proof verification and outcome enforcement | NewtonVerifier, PolicyClient, SDKs |
## Validation Methods
When your smart contract receives an attestation, you validate it using one of two methods:
| Method | Function | Trade-off |
|--------|----------|-----------|
| **Standard** | `_validateAttestation(attestation, proof)` | Checks an attestation already committed by the aggregator. See [Smart Contract Integration](/developers/guides/smart-contract-integration). |
| **Direct** | `_validateAttestationDirect(attestation, proof)` | Verifies the task, response, and BLS signatures directly on-chain. See [Smart Contract Integration](/developers/guides/smart-contract-integration). |
## Supported Chains
See [Contract Addresses](/developers/reference/contract-addresses) for deployed contract addresses on each chain.
| Chain | Chain ID | Status |
|-------|----------|--------|
| Ethereum Mainnet | `1` | Active |
| Ethereum Sepolia | `11155111` | Active |
| Base | `8453` | Active |
| Base Sepolia | `84532` | Active |
## Next Steps
Build and deploy a full Newton integration end-to-end
Explore the three-layer architecture in detail
Full TypeScript SDK documentation
Gateway JSON-RPC methods and parameters
# Quickstart \[Simulate a configured Newton policy set with the TypeScript SDK.]
Evaluate a configured policy set and inspect the aggregate decision and each policy result. This example uses two pure-Rego policies; no funded signer is needed for simulation.
## Prerequisites
* Node.js 20 or later and a Newton API key associated with the configured client’s owner (or explicitly authorized for the gateway’s read-only admin bypass).
* A policy-set-capable client configured with two pure-Rego policies on Sepolia. Obtain a test client from your team or follow [Smart Contract Integration](/developers/guides/smart-contract-integration).
* An intent fixture accepted by those policies. The sample below is a zero-value, empty-calldata intent; use the exact function encoding for your application.
## Install
```bash
npm install @newton-xyz/sdk viem
npm install --save-dev tsx
```
## Simulate
Save as `quickstart.ts`. Supply `NEWTON_API_KEY` and `POLICY_CLIENT` in your process environment; keep the API key on the server or in this local script.
```typescript
import { createWalletClient, http, isAddress } from 'viem';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { sepolia } from 'viem/chains';
import { newtonWalletClientActions } from '@newton-xyz/sdk';
const apiKey = process.env.NEWTON_API_KEY;
const policyClient = process.env.POLICY_CLIENT;
if (!apiKey || !policyClient || !isAddress(policyClient)) {
throw new Error('Set NEWTON_API_KEY and a valid POLICY_CLIENT');
}
// Simulation only: this account is never funded and sends no transaction.
const account = privateKeyToAccount(generatePrivateKey());
const client = createWalletClient({ account, chain: sepolia, transport: http() })
.extend(newtonWalletClientActions({ apiKey }));
async function main() {
const result = await client.simulatePolicy({
policyClient: policyClient as `0x${string}`,
chainId: sepolia.id,
intent: {
from: account.address,
to: policyClient as `0x${string}`,
value: '0x0', data: '0x',
chainId: sepolia.id, functionSignature: '0x',
},
wasmArgs: ['0x', '0x'],
});
if (!result.success) throw new Error(result.error ?? 'Simulation failed');
console.log('Policy set:', result.policy_id, 'Allowed:', result.allowed);
for (const policy of result.policies ?? []) {
console.log(policy.policy_index, policy.policy, policy.allowed, policy.result);
}
}
main().catch(console.error);
```
```bash
npx tsx quickstart.ts
```
## Understand the result
`success` reports whether simulation completed. `allowed` is true only when every policy allows. `policies` retains set order and exposes each policy's index, address, result, verdict, and expiration window. A denial is a completed evaluation, not a transport error.
```mermaid
sequenceDiagram
participant Script
participant Gateway
participant Chain
Script->>Gateway: simulatePolicy(client, intent, wasmArgs[])
Gateway->>Chain: Resolve current policy-set snapshot
Gateway->>Gateway: Run optional WASM and evaluate each Rego policy
Gateway-->>Script: Aggregate allowed and ordered policy results
```
Simulation produces no BLS signatures and sends no application transaction. For live authorization, sign the exact intent, call `evaluateIntentDirect`, and validate the returned task and attestation in your contract before executing. See [Frontend SDK Integration](/developers/guides/frontend-sdk-integration).
Use `simulatePolicy` as shown here rather than the older `simulateTask` wrapper, which still targets the old gateway schema. See [RPC API](/developers/reference/rpc-api#newt_simulatetask) for the provided-output alternative.
## Next steps
Build and deploy a full Newton integration — policy, contract, and frontend
Turn a product brief into a locally tested policy, PolicyClient, and optional Next.js demo
Understand policies, intents, tasks, and attestations
Full TypeScript SDK documentation
See a live sanctions-checked transfer app built with the Newton SDK
# Build with an Agent \[Use Newton Agent Skills to create, test, and optionally deploy a policy, PolicyClient, and local application from a product brief.]
Newton Agent Skills give coding agents a versioned workflow for building with Newton. Start from a plain-language brief, validate the policy and contract locally, and decide later whether to deploy or submit a live transaction.
:::warning
Newton Agent Skills are under active dogfood testing. Review generated code and report friction instead of silently working around it.
:::
## What do you want to build?
| Goal | Skill | Result |
|---|---|---|
| Create or test policy logic | `newton-policy` | Rego, WASM, schemas, allow/deny simulations, and `policy-handoff.json` |
| Protect a Solidity action | `newton-policy-client` | A `NewtonPolicyClient` integration, adversarial Foundry tests, and `client-handoff.json` |
| Build an end-to-end local demo | `newton-demo` | Policy, PolicyClient, handoff files, and a local Next.js application |
`newton-demo` coordinates the other two skills. Use it when your brief includes a policy, contract, and user interface.
## Step 1 — Install the skills
Clone the public skills repository:
```bash
git clone https://github.com/newt-foundation/newton-agent-skills.git
cd newton-agent-skills
```
Create the local discovery link, then open the repository as your agent workspace:
```bash
mkdir -p .agents
ln -sfn ../skills .agents/skills
```
To use the skills in an existing project, copy or link the required skill folders into that project's `.agents/skills/` or `.cursor/skills/` directory.
Copy the skill folders into the project or user-level Claude skills directory:
```bash
mkdir -p .claude/skills
cp -R skills/newton-policy skills/newton-policy-client skills/newton-demo .claude/skills/
```
Copy the required folders under `skills/` into the Agent Skills directory supported by your coding agent. Each folder is self-contained and begins with a `SKILL.md` file.
Install and verify `newton-cli` before policy work:
```bash
curl -L cli.newton.xyz | sh
newtup
newton-cli --version
newton-cli doctor
```
Record the chain, TaskManager, configured factory, and gateway deployment before live work. For multiple policies, preserve their order and per-policy params, expiry, and WASM input slots in the handoff files, along with the set ID and revision.
Local scaffold, build, and policy simulation do not require dashboard login, a wallet, or deployment credentials.
## Step 2 — Write a brief
Give the agent the decisions that define the integration. Include:
* What must be allowed and denied
* The protected action and its arguments
* The target chain and downstream contract or token, when known
* Who signs the intent and later calls the PolicyClient
* Whether you want only a policy, a Solidity integration, or a local UI
* Whether deployment, gateway evaluation, or live transactions are in scope
Use this template:
```text
Use newton-demo to build a local Newton demo.
Policy:
- Allow when:
- Deny when:
- Configurable parameters:
- External data or secrets:
Protected action:
- Chain:
- Target:
- Function:
- intent.from / msg.sender:
Scope:
- Build and test locally first.
- Run at least one allow and one deny policy simulation.
- Run adversarial Foundry tests before any deployment.
- Do not deploy, call the gateway, submit a transaction, or host the app
until I explicitly approve that checkpoint.
- Use credentials already configured on this machine. Never ask me to paste
private keys, API keys, JWTs, or RPC credentials into chat.
```
If a policy rule, chain, target, or protected action is missing, the agent should ask instead of inventing it.
## Step 3 — Review the local result
The agent works locally before requesting live access:
```mermaid
flowchart LR
A["Product brief"] --> B["Policy build"]
B --> C["Allow + deny simulation"]
C --> D["Policy handoff"]
D --> E["PolicyClient + adversarial tests"]
E --> F["Client handoff"]
F --> G["Local Next.js demo"]
```
The handoff files make the workflow resumable:
| File | Purpose |
|---|---|
| `policy-handoff.json` | Records chain, intent encoding, policy inputs, and deployed addresses when available |
| `client-handoff.json` | Records the protected function, PolicyClient wiring, ownership, and frontend inputs |
| `demo-config.json` | Configures the generated UI without embedding credentials |
Review the agent's reported paths, allow/deny results, Foundry test results, and any unresolved questions. A partial handoff may contain `null` addresses before deployment; that is expected.
## Step 4 — Approve live operations separately
The agent must stop before each operation that changes external state:
1. Uploading or deploying a policy
2. Deploying or wiring a PolicyClient
3. Transferring PolicyClient ownership
4. Uploading policy secrets
5. Calling the gateway for a live evaluation
6. Sending the first attested application transaction
7. Hosting the generated application
Provide credentials outside chat through the process environment or `~/.newton/.env`. The generated Next.js app is the exception: it reads `NEWTON_API_KEY` from its gitignored `.env.local` on the server. Never use `NEXT_PUBLIC_NEWTON_API_KEY`.
:::note
The user's browser wallet signs the EIP-712 intent. The generated server Route Handler calls `evaluateIntentDirect` with `NEWTON_API_KEY`; it does not need the user's private key.
:::
## Step 5 — Resume instead of starting over
When continuing later, point the agent at the handoff files:
```text
Resume this Newton integration. Read the existing policy-handoff.json and
client-handoff.json as the source of truth. Do not redeploy completed pieces.
Continue locally and stop before the next live checkpoint.
```
Browse the skills, templates, sample briefs, and current preview status
Understand each policy, contract, and frontend step in detail
# Dashboard & API Keys \[Create and manage Newton Protocol API keys for authenticating with the Newton Gateway. Dashboard setup and key rotation guide.]
## Getting an API Key
Go to [dashboard.newton.xyz](https://dashboard.newton.xyz/), sign in, and click **API Keys** in the left navigation — your key is already generated and ready to use.
***
The Newton Dashboard API at `dashboard.api.newton.xyz` also provides self-service API key management via API. You can authenticate with Sign-In with Ethereum (SIWE) or email OTP, then create API keys that are immediately usable with the Newton Gateway.
## Authentication
The Dashboard API uses a factor-based authentication flow: you first request a challenge, then verify it to receive session tokens.
Sign-In with Ethereum (SIWE) links your wallet address to your Dashboard account.
:::steps
### Request a challenge
```bash
curl -X POST https://dashboard.api.newton.xyz/v1/auth/user_factor/siwe/challenge \
-H "Content-Type: application/json" \
-d '{"address": "0xYourWalletAddress"}'
```
Returns a `verify_flow_id` and `nonce`.
### Sign the SIWE message
Construct a SIWE message with the returned `nonce` and sign it with your wallet (e.g., via MetaMask or viem).
### Verify and get session
```bash
curl -X POST https://dashboard.api.newton.xyz/v1/auth/user_factor/verify \
-H "Content-Type: application/json" \
-d '{"verify_flow_id": "", "challenge_response": ""}'
```
Returns an `access_token` and `refresh_token`.
:::
Email-based authentication sends a one-time password to your email.
:::steps
### Request OTP
```bash
curl -X POST https://dashboard.api.newton.xyz/v1/auth/user_factor/email/challenge \
-H "Content-Type: application/json" \
-d '{"value": "you@example.com", "challenge": ""}'
```
Returns a `verify_flow_id`.
### Verify OTP
```bash
curl -X POST https://dashboard.api.newton.xyz/v1/auth/user_factor/verify \
-H "Content-Type: application/json" \
-d '{"verify_flow_id": "", "challenge_response": "123456"}'
```
Returns an `access_token` and `refresh_token`.
:::
## Create an API Key
Once authenticated, create an API key for use with the Newton Gateway.
:::steps
### Authenticate
Use SIWE or email OTP as described above to obtain an access token.
### Create an API key
```bash
curl -X POST https://dashboard.api.newton.xyz/v1/api_key \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{"name": "my-app-key", "permissions": ["rpc_read", "rpc_write"]}'
```
### Use the API key
Include the key in the `Authorization` header when calling the Newton Gateway:
```bash
curl -X POST https://gateway-avs.sepolia.newton.xyz/rpc \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{"jsonrpc":"2.0","method":"newt_createTask","params":{...},"id":"7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"}'
```
The same key also reads task and policy data from the [Explorer API](/developers/reference/explorer-api):
```bash
curl https://explorer.api.newton.xyz/v1/task/ \
-H "Authorization: Bearer "
```
:::
## API Key Management
| Operation | Method | Endpoint |
|-----------|--------|----------|
| Create | `POST` | `/v1/api_key` |
| List | `GET` | `/v1/api_key` |
| Get | `GET` | `/v1/api_key/:id` |
| Update | `PUT` | `/v1/api_key/:id` |
| Rotate | `POST` | `/v1/api_key/:id/rotate` |
| Delete | `DELETE` | `/v1/api_key/:id` |
All management endpoints require an authenticated session (access token in `Authorization` header).
## Permissions
| Permission | Description |
|------------|-------------|
| `admin` | Full access — manage projects, keys, and settings |
| `rpc_write` | Write operations — secrets management via `newt_storeEncryptedSecrets` |
| `rpc_read` | Read operations — task submission and simulation |
| `rpc` | Combined `rpc_read` + `rpc_write` |
:::note
Most integrations need `rpc` permission (combined read + write). Use `rpc_read` for frontend-only applications that submit tasks but do not manage secrets.
:::
## Policy Client Ownership
The Dashboard API verifies PolicyClient ownership by reading the on-chain `getOwner()` method. This ensures that only the contract owner can:
* Upload encrypted secrets via `newt_storeEncryptedSecrets`
* Access stored secrets via `newt_simulatePolicyDataWithClient`
Ownership is transferable on-chain via `setOwner()` or `transferOwnership()` on the PolicyClient contract.
## Session Management
| Operation | Method | Endpoint |
|-----------|--------|----------|
| Refresh token | `POST` | `/v1/auth/refresh` |
| Logout | `POST` | `/v1/auth/logout` |
Access tokens expire after a short period. Use the refresh token to obtain a new access token without re-authenticating.
## Alternative: Email Request
If you prefer not to use the Dashboard API, you can request an API key by emailing [support@newton.xyz](mailto\:support@newton.xyz).
## Next Steps
Use your API key to simulate a policy evaluation
Full Gateway API reference
Query tasks and policies with your API key
# Overview \[Integrate user identity and KYC data into Newton policies using Newton Verifiable Credentials.]
Newton Verifiable Credential (Newton VC) lets developers bring user identity data into Newton policies. Instead of managing identity checks separately, you can write Rego policies that enforce KYC requirements—like age, country, or approval status—as part of the same policy evaluation that governs your transactions.
## Use Cases
**Use case A — Use your own KYC data**
Developers collect KYC data from users via a third-party vendor and register it with Newton. Policies can then enforce compliance requirements based on that data (e.g., only allow transactions from users aged 18+ in approved countries).
**Use case B — Use another developer's KYC data**
Developers can leverage KYC data collected by another developer in a privacy-preserving manner. You can ensure that a user's identity meets your policy requirements without ever seeing the underlying personal data.
## How It Works
At a high level, Newton VC follows this flow:
```mermaid
flowchart LR
A[Collect KYC data] --> B[Register with Newton]
B --> C[User confirms & links identity]
C --> D[Submit task with signed intent]
D --> E[Policy evaluates identity]
```
1. **Collect** — Your app collects KYC data from users via a third-party vendor.
2. **Register** — You call the Newton SDK to register the KYC data for the user, scoped to your identity domain.
3. **Link** — The user confirms the link between their identity and your policy client contract.
4. **Submit** — Your app submits a task with a signed intent. Newton evaluates the Rego policy, which checks identity data using built-in functions.
## Policy Client Requirements
Your policy client contract must:
* Inherit from both `NewtonPolicyClient` and `EIP712`
* Be registered with the `PolicyClientRegistry`
* Have owner `setPolicies()` configure the identity policy entry with params that include your identity domain: `{"identity_domain": "0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4"}`
The `identity_domain` is the output of `keccak256("kyc")`.
## Next Steps
Step-by-step walkthrough for integrating Newton VC into your app
SDK methods and contract function signatures
Built-in Rego functions for writing identity-aware policies
IdentityRegistry and other deployed contract addresses
# Integration Guide \[Step-by-step guide for integrating Newton Verifiable Credentials into your application.]
This guide walks through the end-to-end integration of Newton Verifiable Credentials into your app. Before starting, read the [overview](/developers/verified-credential/overview) to understand how Newton VC works.
Clone the repo and follow the README to run the full VC flow locally
Try the live demo
## Prerequisites
| Requirement | Details |
|-------------|---------|
| **Newton SDK** | `@newton-xyz/sdk` installed in your JavaScript/TypeScript app |
| **Newton API key** | [Create one in the dashboard](/developers/overview/dashboard-api-keys) |
| **Policy client contract** | Must inherit `NewtonPolicyClient` + `EIP712` and be registered with `PolicyClientRegistry` |
| **Identity domain** | A fixed `bytes32` hex value for your application — the output of `keccak256("kyc")` |
| **KYC vendor** | A third-party vendor to collect user identity data. The vendor's output must map to Newton's [IdentityData fields](/developers/verified-credential/identity-policy-reference#kyc-data-fields). |
## Step 1: Write and Deploy a Policy
### 1a. Write your Rego policy
Write a Rego policy that uses Newton identity built-in functions. For example, to enforce that a user is approved and at least 18 years old:
```rego
package example
import future.keywords.if
authorized if {
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(18)
newton.identity.kyc.address_in_countries(["US","CA","DE"])
}
```
See the [Identity Policy Reference](/developers/verified-credential/identity-policy-reference) for all available built-in functions.
### 1b. Write your `params_schema.json`
Define the schema for your policy params, including the `identity_domain`:
```json
{
"type": "object",
"description": "params including domain",
"required": [],
"properties": {
"identity_domain": {
"type": "string",
"description": "the identity domain, must be hex"
}
}
}
```
### 1c. Write your `policy_metadata.json`
```json
{
"name": "Newton Verifiable Credential Policy",
"version": "1.0.0",
"author": "Newton",
"link": "https://newton.xyz",
"description": "This policy enforces KYC compliance"
}
```
:::note
Verifiable Credential policies do not use a `secrets_schema.json` or `policy_data_metadata.json`. You only need `policy_metadata.json` and `params_schema.json`.
:::
### 1d. Deploy your policy using newton-cli
Your policy directory should look like this before deploying:
```
policy_files/
├── params_schema.json
├── policy.rego
└── policy_metadata.json
```
Generate your `policy_cids.json` file with the Newton CLI command documented [here](/developers/guides/deploying-with-cli#step-1-generate-cids-and-upload-to-ipfs).
**Deploy the policy contract**
Once you have `policy_cids.json`, run:
```bash
newton-cli policy deploy --cids policy_cids.json
```
After deploying, note your policy contract address — you'll need it in Step 2.
## Step 2: Deploy a Policy Client Contract
Your policy client must inherit from both `NewtonPolicyClient` and `EIP712`. See the [Reference](/developers/verified-credential/reference#sample-policy-client) for a complete sample contract.
Key requirements:
* Inherit `NewtonPolicyClient` and `EIP712`
* Call `_initNewtonPolicyClient(policyTaskManager, owner)` in the constructor
* Register the contract with the `PolicyClientRegistry` — see [Smart Contract Integration](/developers/guides/smart-contract-integration#identityregistry-integration)
**After registering**, call the following on your deployed policy client contract:
Call owner `setPolicies` with the identity policy address and its configuration. Encode the following JSON into that entry’s `policyParams`, and set a nonzero `expireAfter`. Include every other desired policy in the same ordered replacement. See the [configuration example](/developers/guides/smart-contract-integration#configure-the-policy-set):
```json
{
"identity_domain": "0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4"
}
```
## Step 3: Collect KYC Data
Collect identity data from your users using a third-party KYC vendor. The data you receive from the vendor must map to Newton's [IdentityData fields](/developers/verified-credential/identity-policy-reference#kyc-data-fields) — fields like `status`, `birthdate`, `address_country_code`, etc.
## Step 4: Register and Confirm KYC Data
### 4a. Register KYC Data
Once you have the KYC data from your vendor, call `registerUserData` to register it with Newton, scoped to your identity domain:
```typescript
const walletClient = createWalletClient({ ... }).extend(
newtonWalletClientActions({ apiKey: 'YOUR_API_KEY' })
)
await walletClient.registerUserData({
userData: kycDataFromVendor,
appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
policyClient: '0xYourPolicyClientAddress',
})
```
:::note
When the Newton identity popup opens, Newton creates a new wallet for the user to manage their identity. The user will need to fund this wallet with ETH to continue.
:::
### 4b. User Confirms KYC Data
The user sees a confirmation prompt in the Newton identity popup to verify the KYC data being registered. This is handled automatically by the popup opened in step 4a — no additional SDK calls are required.
## Step 5: Link Identity to Your App
### 5a. Initiate Identity Link
Call `linkApp` to link the user's registered identity to your policy client contract:
```typescript
await walletClient.linkApp({
appWalletAddress: userWalletAddress,
appClientAddress: yourPolicyClientAddress,
appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
})
```
This creates an association between the user's Newton identity wallet and your policy client contract in the `IdentityRegistry`. After this link is established, Newton's policy engine can access the user's registered KYC data when evaluating tasks submitted through your policy client.
You can find the `IdentityRegistry` contract address in [Contract Addresses](/developers/reference/contract-addresses).
### 5b. User Confirms the Link
The user completes the link confirmation in the Newton identity popup. Under the hood, this calls `linkIdentityAsUser` on the `IdentityRegistry` contract — a transaction signed by the user's Newton identity wallet.
```typescript
// This is called internally by the SDK when the user confirms — shown here for reference
function linkIdentityAsUser(
address _identityOwner, // user's Newton identity wallet
address _policyClient, // your policy client contract
bytes32[] _identityDomains, // [your identity domain]
bytes _signature, // user's authorization signature
uint256 _nonce,
uint256 _deadline
)
```
See [SDK & Contract Reference](/developers/verified-credential/reference#linkidentityasuser) for the full function signature and return value.
## Step 6: Submit a Task
Submit a task with a signed intent from the user's dapp wallet. The intent must be constructed and signed using the EIP712 domain of your policy client.
**1. Construct the intent**
```typescript
const intent = {
from: userAddress,
to: yourPolicyClientAddress, // the contract that will execute the tx
value: '0x0' as Hex, // ETH value as hex string
data: '0x...' as Hex, // encoded calldata for the target call
chainId: 11155111, // your chain ID
functionSignature: '0x' as Hex,
}
```
**2. Sign the intent using your policy client's EIP712 domain**
```typescript
// The EIP712 domain name and version must match your policy client's constructor args
const intentSignature = await walletClient.signTypedData({
account: userAddress,
domain: {
name: 'NewtonPolicyWallet', // must match EIP712("NewtonPolicyWallet", "1")
version: '1',
chainId: 11155111,
verifyingContract: yourPolicyClientAddress,
},
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: {
...intent,
value: BigInt(intent.value),
chainId: BigInt(intent.chainId),
},
})
```
**3. Submit for evaluation**
```typescript
const { result } = await newtonWalletClient.evaluateIntentDirect({
policyClient: yourPolicyClientAddress,
intent,
intentSignature,
timeout: 60,
identityDomain: IDENTITY_DOMAIN,
wasmArgs: ['0x'], // One pure-Rego identity policy; align with your actual set
})
// result.allowed — true if the policy passed
// result.task, result.taskResponse, result.blsSignature — needed for Step 7
```
## Step 7: Execute On-Chain Transaction with Attestation
Use the attestation returned in Step 6 to execute the transaction on-chain through your policy client.
```typescript
if (result.allowed !== true) throw new Error("Policy set denied the intent");
const { task, taskResponse, blsSignature } = result
await walletClient.writeContract({
address: yourPolicyClientAddress,
abi: policyClientAbi,
functionName: 'validateAndExecuteDirect',
args: [
intent.to,
BigInt(intent.value),
intent.data,
{
...task,
intent: { ...task.intent, value: BigInt(task.intent.value), chainId: BigInt(task.intent.chainId) },
initializationTimestamp: BigInt(task.initializationTimestamp),
},
{
...taskResponse,
intent: { ...taskResponse.intent, value: BigInt(taskResponse.intent.value), chainId: BigInt(taskResponse.intent.chainId) },
initializationTimestamp: BigInt(taskResponse.initializationTimestamp),
},
blsSignature,
],
account: userAddress,
chain: sepolia,
})
```
`validateAndExecuteDirect` verifies the attestation on-chain and executes the call — only if the policy passed.
## Unlinking
To remove a user's identity link from your app, call `unlinkApp`:
```typescript
await walletClient.unlinkApp({
appWalletAddress: userWalletAddress,
appClientAddress: yourPolicyClientAddress,
appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
})
```
## Reference Pages
Full SDK method signatures and contract functions
All Rego built-in functions for identity checks
Get in touch with someone from the Newton team to discuss your integration
# SDK & Contract Reference \[SDK methods and contract functions for Newton Verifiable Credentials.]
This page covers the SDK methods and contract functions used to register, link, and manage user identity data with Newton.
## SDK Methods
The following methods are available on the wallet client actions extension (`newtonWalletClientActions`).
### `registerUserData`
Registers KYC data for a user, scoped to your application's identity domain. Opens the Newton identity popup to securely store the data.
**Signature:**
```typescript
registerUserData(args: {
userData: KycUserData
appIdentityDomain: Hex
policyClient: Address
}): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `userData` | `KycUserData` | KYC data object from your identity vendor |
| `appIdentityDomain` | `Hex` | Output of `keccak256("kyc")` |
| `policyClient` | `Address` | Policy client contract address (used for HPKE encryption AAD binding) |
**Example:**
```typescript
await walletClient.registerUserData({
userData: kycDataFromVendor,
appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
policyClient: '0xYourPolicyClientAddress',
})
```
***
### `linkApp`
Links a user's registered identity to your policy client contract. The user must confirm the link in the Newton identity popup, which triggers a signed transaction from their wallet.
**Signature:**
```typescript
linkApp(args: {
appWalletAddress: Address
appClientAddress: Address
appIdentityDomain: Hex
}): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `appWalletAddress` | `Address` | The user's dapp wallet address |
| `appClientAddress` | `Address` | Your deployed policy client contract address |
| `appIdentityDomain` | `Hex` | Output of `keccak256("kyc")` |
**Example:**
```typescript
await walletClient.linkApp({
appWalletAddress: '0xUserWallet...',
appClientAddress: '0xYourPolicyClient...',
appIdentityDomain: '0xabc123...',
})
```
***
### `unlinkApp`
Removes a user's identity link from your policy client contract.
**Signature:**
```typescript
unlinkApp(args: {
appWalletAddress: Address
appClientAddress: Address
appIdentityDomain: Hex
}): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `appWalletAddress` | `Address` | The user's dapp wallet address |
| `appClientAddress` | `Address` | Your deployed policy client contract address |
| `appIdentityDomain` | `Hex` | Output of `keccak256("kyc")` |
**Example:**
```typescript
await walletClient.unlinkApp({
appWalletAddress: '0xUserWallet...',
appClientAddress: '0xYourPolicyClient...',
appIdentityDomain: '0xabc123...',
})
```
***
## Contract Functions
### `linkIdentityAsUser`
Called on the `IdentityRegistry` contract to link a user's identity to a policy client. This is invoked under the hood when the user confirms the link via `linkApp`. See [Contract Addresses](/developers/reference/contract-addresses) for the `IdentityRegistry` address.
**Signature:**
```solidity
function linkIdentityAsUser(
address _identityOwner,
address _policyClient,
bytes32[] calldata _identityDomains,
bytes calldata _signature,
uint256 _nonce,
uint256 _deadline
)
```
**Parameters:**
The following are the output of the link SDK method.
| Parameter | Type | Description |
|-----------|------|-------------|
| `_identityOwner` | `address` | The identity owner being used to authorize access to the existing identity |
| `_policyClient` | `address` | The policy client where the data is to be associated |
| `_identityDomains` | `bytes32[]` | The identity domains specifying what type of data is associated |
| `_signature` | `bytes` | Signature by the `_identityOwner` to authorize the link |
| `_nonce` | `uint256` | Nonce for the signature |
| `_deadline` | `uint256` | Deadline for the signature |
***
## Sample Policy Client
Your policy client contract must inherit both `NewtonPolicyClient` and `EIP712`. Below is a complete example:
```solidity
// 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";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
contract NewtonPolicyWallet is NewtonPolicyClient, EIP712 {
event Executed(address indexed to, uint256 value, bytes data, bytes32 taskId);
error InvalidAttestation();
error ExecutionFailed();
constructor(
address policyTaskManager,
address owner
) payable EIP712("NewtonPolicyWallet", "1") {
_initNewtonPolicyClient(policyTaskManager, owner);
}
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return super.supportsInterface(interfaceId);
}
function validateAndExecuteDirect(
address to,
uint256 value,
bytes calldata data,
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
bytes calldata signatureData
) external payable returns (bytes memory) {
require(
_validateAttestationDirect(task, taskResponse, signatureData),
InvalidAttestation()
);
require(to == taskResponse.intent.to, "Target mismatch");
require(value == taskResponse.intent.value, "Value mismatch");
require(keccak256(data) == keccak256(taskResponse.intent.data), "Calldata mismatch");
(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 {}
}
```
:::note
After deploying, register this contract with the `PolicyClientRegistry`. See [Smart Contract Integration](/developers/guides/smart-contract-integration#identityregistry-integration) for instructions.
:::
## Next Steps
Rego built-in functions for writing identity-aware policies
IdentityRegistry and other deployed contract addresses
# Identity Policy Reference \[Newton identity Rego built-in functions for writing policies that check user identity data.]
Newton provides Rego built-in functions for checking user identity data within policy evaluation. These functions let you enforce KYC requirements — like age, country, and document status — directly in your Rego policies.
Identity built-ins are domain-namespaced under `newton.identity..*`. The KYC domain is the first implemented domain. A generic `newton.identity.get(field_name)` accessor works across any domain for ad-hoc field access.
:::info
For the full Rego syntax reference including all Newton extensions, see the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide#newton-identity-extensions).
:::
## KYC Data Fields
The following fields are available from the KYC identity data passed into the policy engine:
| Field | Type | Description |
|-------|------|-------------|
| `status` | `string` | One of: `created`, `pending`, `completed`, `approved`, `failed`, `expired`, `declined`, `needs review` |
| `selected_country_code` | `string` | The country code selected by the user during the KYC process |
| `address_subdivision` | `string` | The state/subdivision from the document address |
| `address_country_code` | `string` | The country from the document address |
| `birthdate` | `string` | The user's birthdate as a `YYYY-MM-DD` string |
| `expiration_date` | `string` | The expiration date of the identity document |
| `issue_date` | `string` | The issuance date of the identity document |
| `issuing_authority` | `string` | The country or state that issued the document |
## KYC Built-in Functions
| Function | Description |
|----------|-------------|
| [`newton.identity.kyc.check_approved`](#newtonidentitykyccheck_approved) | Check that the identity data has been approved |
| [`newton.identity.kyc.address_in_countries`](#newtonidentitykycaddress_in_countries) | Check that the document country is in an allowed list |
| [`newton.identity.kyc.address_in_subdivision`](#newtonidentitykycaddress_in_subdivision) | Check that the document address is in an allowed list of subdivisions |
| [`newton.identity.kyc.address_not_in_subdivision`](#newtonidentitykycaddress_not_in_subdivision) | Check that the document address is not in an excluded list of subdivisions |
| [`newton.identity.kyc.age_gte`](#newtonidentitykycage_gte) | Check that the user is at least a minimum age |
| [`newton.identity.kyc.not_expired`](#newtonidentitykycnot_expired) | Check that the identity document has not expired |
| [`newton.identity.kyc.valid_for`](#newtonidentitykycvalid_for) | Check that the document remains valid for a minimum number of days |
| [`newton.identity.kyc.issued_since`](#newtonidentitykycissued_since) | Check that the document was issued at least a minimum number of days ago |
***
### `newton.identity.kyc.check_approved`
Requires `identity_data.status` to equal `"approved"`.
**Signature:**
```rego
result := newton.identity.kyc.check_approved()
```
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if `identity_data.status` is `"approved"`, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the identity data is approved
authorized if {
newton.identity.kyc.check_approved()
}
```
***
### `newton.identity.kyc.address_in_countries`
Requires `identity_data.address_country_code` to be found within the provided list of country codes.
**Signature:**
```rego
result := newton.identity.kyc.address_in_countries(country_code_array)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `country_code_array` | `string[]` | Array of 2-letter ISO country codes to check against |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the address country is within the list, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the identity data matches address requirements
authorized if {
newton.identity.kyc.address_in_countries(["US","DE","CA"])
}
```
***
### `newton.identity.kyc.address_in_subdivision`
Requires the ISO subdivision location code (combining `address_country_code` and `address_subdivision`) to be found within the provided list.
**Signature:**
```rego
result := newton.identity.kyc.address_in_subdivision(iso_code_array)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `iso_code_array` | `string[]` | Array of `XX-XX` or `XX-XXX` ISO subdivision codes to match against |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the address is within the list, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the identity data matches address requirements
authorized if {
newton.identity.kyc.address_in_subdivision(["US-CA","US-OR","US-WA"])
}
```
***
### `newton.identity.kyc.address_not_in_subdivision`
Requires the ISO subdivision location code to **not** be found within the provided list. Useful for excluding specific subdivisions while allowing all others in a country (avoids submitting a long allowlist).
**Signature:**
```rego
result := newton.identity.kyc.address_not_in_subdivision(iso_code_array)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `iso_code_array` | `string[]` | Array of `XX-XX` or `XX-XXX` ISO subdivision codes to exclude |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the address is not in the list, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Allow all US users except specific states
authorized if {
newton.identity.kyc.address_in_countries(["US"])
newton.identity.kyc.address_not_in_subdivision(["US-NY","US-NC","US-HI"])
}
```
***
### `newton.identity.kyc.age_gte`
Requires `identity_data.birthdate` to be at least the required number of years ago, relative to `reference_date`.
**Signature:**
```rego
result := newton.identity.kyc.age_gte(min_age)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `min_age` | `number` | Minimum age in years (must be positive) |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the user's age meets the minimum, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the user meets age requirements
authorized if {
newton.identity.kyc.age_gte(21)
}
```
***
### `newton.identity.kyc.not_expired`
Requires `identity_data.expiration_date` to be after the current date (`reference_date`).
**Signature:**
```rego
result := newton.identity.kyc.not_expired()
```
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the document expiration date is in the future, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the identity document isn't expired
authorized if {
newton.identity.kyc.not_expired()
}
```
***
### `newton.identity.kyc.valid_for`
Requires `identity_data.expiration_date` to be at least the required number of days in the future, relative to `reference_date`.
**Signature:**
```rego
result := newton.identity.kyc.valid_for(min_days)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `min_days` | `number` | Minimum number of days the document must still be valid (must be positive) |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the document will be valid for at least `min_days`, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the document is valid for at least one more year
authorized if {
newton.identity.kyc.valid_for(365)
}
```
***
### `newton.identity.kyc.issued_since`
Requires `identity_data.issue_date` to be at least the required number of days in the past, relative to `reference_date`.
**Signature:**
```rego
result := newton.identity.kyc.issued_since(min_days)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `min_days` | `number` | Minimum number of days since the document was issued (must be positive) |
**Returns:**
| Type | Description |
|------|-------------|
| `bool` | `true` if the document was issued at least `min_days` ago, otherwise `false` |
**Example:**
```rego
package example
import future.keywords.if
# Verify the document was issued at least 90 days ago
authorized if {
newton.identity.kyc.issued_since(90)
}
```
***
## Generic Field Accessor
### `newton.identity.get`
Works across any identity domain. Returns the raw field value by name. Returns `undefined` if the field does not exist.
**Signature:**
```rego
result := newton.identity.get(field_name)
```
**Arguments:**
| Argument | Type | Description |
|----------|------|-------------|
| `field_name` | `string` | The field name to look up |
**Returns:**
| Type | Description |
|------|-------------|
| any / undefined | The field value, or `undefined` if the field does not exist |
**Example:**
```rego
package example
import future.keywords.if
# Use generic accessor for ad-hoc field checks
authorized if {
newton.identity.get("status") == "approved"
newton.identity.get("address_country_code") == "US"
}
```
***
## Error Handling
* **Address functions** (`address_in_countries`, `address_in_subdivision`, `address_not_in_subdivision`) return an error (and the rule evaluates to `undefined`) when:
* Provided an empty array
* Provided full country/state names instead of ISO codes
* The stored country code or subdivision is empty
* **Date functions** (`age_gte`, `valid_for`, `issued_since`) return an error if given negative numbers or if dates cannot be parsed.
When a built-in function errors, the policy rule containing it evaluates to `undefined`, which Newton treats as a denial.
## Next Steps
Step-by-step walkthrough for integrating Newton VC
SDK methods and contract function signatures
# Integration Guide \[End-to-end guide for adding transaction policies to your smart contracts with Newton Protocol. Deploy Rego policies, build WASM data oracles, and verify attestations on-chain.]
This guide walks through the full Newton Protocol integration: a [WASM data oracle](/developers/guides/writing-data-oracles), a [Rego policy](/developers/guides/writing-policies), a [PolicyClient smart contract](/developers/guides/smart-contract-integration), and a [frontend](/developers/guides/frontend-sdk-integration) that submits intents and executes attested transactions. Choose your starting point based on what you need:
If a policy is already deployed (e.g., you completed the [quickstart](/developers/overview/quickstart) or someone shared a policy address with you), skip directly to [Step 4: Smart Contract Integration](#step-4-smart-contract-integration) or [Step 5: Frontend SDK Integration](#step-5-frontend-sdk-integration). You only need **Node.js** and optionally **Foundry**.
Follow all five steps below to create a data oracle, write a Rego policy, deploy it, and integrate with a smart contract and frontend.
Start with the [Agent Quickstart](/developers/overview/agent-quickstart). Newton Agent Skills turn a brief into locally tested policy and contract artifacts, preserve state in handoff files, and pause before deployment, gateway evaluation, and live transactions.
:::note
The agent and manual routes produce the same Newton components. The agent route adds resumable `policy-handoff.json`, `client-handoff.json`, and `demo-config.json` files; it does not bypass local simulation, Foundry tests, or deployment approvals.
:::
## Overview
A complete Newton integration involves five steps:
```mermaid
flowchart LR
A[Write Data Oracle] --> B[Write Policy]
B --> C[Deploy with CLI]
C --> D[Smart Contract]
D --> E[Frontend SDK]
style D stroke-dasharray: 5 5
style E stroke-dasharray: 5 5
```
:::note
Steps 4 and 5 (dashed) can be done independently if you already have a deployed policy. You do not need the full toolchain to integrate via the SDK.
:::
## Prerequisites
Install only what you need for the steps you plan to follow:
| Requirement | Steps | Use |
| -------------------------------------- | ----- | -------------------------------------------------------------------------------------- |
| **Node.js >= 20 + npm** | All | SDK, frontend, WASM tooling |
| **Sepolia ETH** | 3–5 | Gas fees on testnet |
| **Newton API key** | 4–5 | Authenticate SDK requests — [create one here](/developers/overview/dashboard-api-keys) |
| **Foundry** (`forge`, `cast`, `anvil`) | 4 | Compiling and deploying Solidity contracts |
| **Rust + Cargo** | 1–3 | Building and running `newton-cli` |
| **newton-cli** | 3 | Uploading policy files, generating CIDs, deploying policies |
| **Pinata account** | 3 | IPFS pinning (you will need a JWT and a gateway URL) |
::::details[Install all tooling (Steps 1–5)]
```bash
# 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
curl -L https://foundry.paradigm.xyz | bash
foundryup
# newton-cli (via newtup version manager)
curl -L cli.newton.xyz | sh
newtup
# jco (WASM componentization)
npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js
```
:::warning
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 a Data Oracle (~30 min)
Build a WebAssembly component that fetches external data (e.g., price feeds, sanctions screening, KYC status) for [policy evaluation](/developers/overview/core-concepts#policydata).
Define the WIT interface, implement in JavaScript, build WASM, test locally
## Step 2: Write a Rego Policy (~20 min)
Create a [Rego policy](/developers/overview/core-concepts#policy) that evaluates transaction intents using data from your oracle and configuration parameters.
Write Rego rules, define parameter schemas, organize policy files
## Step 3: Deploy with CLI (~15 min)
Upload each policy’s Rego, schema, metadata, and optional WASM artifacts, then deploy independent [Policy](/developers/overview/core-concepts#policy) contracts. Configure the client’s ordered set with owner `setPolicies`.
:::note
Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
:::
Generate artifacts, deploy policies, configure the client set, and verify ownership
## Step 4: Smart Contract Integration (~30 min)
Deploy a [PolicyClient](/developers/overview/core-concepts#policyclient) smart contract that validates Newton [attestations](/developers/overview/core-concepts#attestation) before executing transactions.
Inherit NewtonPolicyClient, configure validation, deploy with Foundry
## Step 5: Frontend SDK Integration (~30 min)
Build a Next.js application that submits evaluation requests via the [SDK](/developers/reference/sdk-reference) and executes attested transactions.
Create Newton client, submit evaluations, execute with attestations
Use a product brief to coordinate the same policy, PolicyClient, and frontend flow
## End-to-End Flow
Once everything is deployed:
1. Your frontend submits an [Intent](/developers/overview/core-concepts#intent) via the Newton [SDK](/developers/reference/sdk-reference)
2. The Newton [Gateway](/developers/overview/core-concepts#gateway) forwards it to AVS [operators](/developers/overview/core-concepts#operator)
3. Operators run the WASM [data oracle](/developers/overview/core-concepts#policydata), evaluate the [Rego policy](/developers/overview/core-concepts#policy), and produce BLS-signed attestations
4. The [attestation](/developers/overview/core-concepts#attestation) is returned to your app
5. Your app submits the transaction + attestation to the [PolicyClient](/developers/overview/core-concepts#policyclient) on-chain
6. The contract validates the attestation and executes the transaction
For the full architecture, see [Architecture](/developers/concepts/architecture).
## Troubleshooting
:::details['command not found' after installing tools]
Restart your shell after installing Rust, Node via Homebrew, or Foundry:
```bash
source ~/.zshrc # or source ~/.bashrc
```
:::
:::details[Policy build fails during componentization]
Run the CLI toolchain check and install any missing componentization packages it reports:
```bash
newton-cli doctor
npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js
```
Re-run `newton-cli policy build -p ` after fixing the reported dependency or source error.
:::
:::details[InvalidAttestation error (0xbd8ba84d)]
Most common cause: wrong Task Manager address. The wallet **must** use `0xecb741F4875770f9A5F060cb30F6c9eb5966eD13` on Sepolia. BLS signatures are bound to this address.
Other causes: intent parameter mismatch, policy ID mismatch, incorrect struct passthrough from `evaluateIntentDirect`.
:::
:::details[ExecutionFailed error (0xacfdb444)]
The attestation passed but the inner call reverted. Common causes:
* Wallet contract has no ETH (fund it directly)
* Target contract reverted
* Malformed calldata
:::
:::details[WebSocket connection fails]
Use `wss://` protocol, not `https://`:
```bash
NEXT_PUBLIC_SEPOLIA_ALCHEMY_WS_URL=wss://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
```
:::
:::details[Policy evaluation times out]
* Test WASM locally with `newton-cli policy-data simulate`
* Verify `wasmArgs` format matches your WASM expectations
* Increase the `timeout` value in the evaluation request
* Check external APIs your WASM calls are responding
:::
:::details[Find out why a live task was non-compliant]
Fetch the task from the [Explorer API](/developers/reference/explorer-api) with your Newton API key, using the `task_id` returned by the Gateway:
```bash
curl -sS https://explorer.api.newton.xyz/v1/task/$TASK_ID \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet" \
| jq '{evaluation_result, error, operator_errors, policy_task_data}'
```
* `error` or `operator_errors` set: the data oracle or evaluation failed. Test the oracle with `newton-cli policy-data simulate`.
* `policy_task_data` preserves ordered evidence. Inspect it alongside the task’s frozen set and revision. `newt_simulateTask` evaluates the current set with supplied outputs, so it cannot replay a historical set after reconfiguration.
:::
## Factory Pattern
:::details[Advanced: Factory pattern for deploying multiple PolicyClients]
If you need to deploy multiple PolicyClient instances (e.g., one per user), use a factory contract:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {NewtonPolicyWallet} from "./NewtonPolicyWallet.sol";
contract WalletFactory {
address public immutable taskManager;
address public immutable policy;
event WalletCreated(address indexed owner, address wallet);
constructor(address _taskManager, address _policy) {
taskManager = _taskManager;
policy = _policy;
}
function createWallet() external returns (address) {
NewtonPolicyWallet wallet = new NewtonPolicyWallet();
wallet.initialize(taskManager, policy, msg.sender);
emit WalletCreated(msg.sender, address(wallet));
return address(wallet);
}
}
```
This deploys pre-configured wallet instances bound to your policy. Each user gets their own wallet with the same policy enforcement.
:::
Get in touch with someone from the Newton team to discuss your integration
# Writing Data Oracles \[Build WebAssembly data oracles that fetch external data for Newton Protocol policy evaluation. JavaScript, Rust, and Python examples with the WIT component model.]
:::note
Attach an oracle's WASM module and optional secrets schema directly to its Policy. A policy set passes `wasm_args` as an ordered array, with one input for each Policy; individual WASM simulation uses one scalar input. See [Deploying with CLI](/developers/guides/deploying-with-cli) and the [RPC API](/developers/reference/rpc-api) for deployment and simulation.
:::
Data oracles are WebAssembly (WASM) 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.wasm`.
## What is a Data Oracle?
A data oracle is a JavaScript (or Rust/Python) program compiled to WASM that:
1. Receives its Policy's `wasm_args` entry as a JSON string
2. Optionally fetches external data via HTTP
3. Returns a JSON string that becomes available to your Rego policy as `data.wasm`
## WIT Interface
Every data oracle implements the `newton-provider` WIT (WebAssembly Interface Types) contract. Create `newton-provider.wit`:
```
package newton:provider@0.2.0;
// HTTP host interface
interface http {
record http-request {
url: string,
method: string,
headers: list>,
body: option>,
}
record http-response {
status: u16,
headers: list>,
body: list,
}
fetch: func(request: http-request) -> result;
}
// Secrets host interface
interface secrets {
record secret-response {
// Raw bytes of the decrypted secrets JSON object.
// The scope (policy client and Policy) is bound by the host execution context.
value: list,
}
get: func() -> result;
}
// [NEW] TLSNotary verification interface
interface tlsn {
record verified-data {
/// Authenticated server name (e.g. "api.x.com")
server-name: string,
/// Unix timestamp (seconds) of the TLS connection
connection-time: u64,
/// Sent transcript bytes (unauthenticated bytes masked as 0x58 'X')
sent-transcript: list,
/// Received transcript bytes (unauthenticated bytes masked as 0x58 'X')
received-transcript: list,
/// SHA-256 fingerprint of the presentation notary verifying key, hex-encoded.
notary-key-fingerprint: string,
}
/// Download a TLSNotary Presentation from IPFS by CID, verify it, and return authenticated data.
///
/// The host implementation:
/// 1. Downloads from IPFS (5 MiB cap — returns error if exceeded)
/// 2. Re-verifies the CID multihash against the downloaded bytes (defends against malicious IPFS gateways)
/// 3. BCS-deserializes the bytes into a Presentation
/// 4. Calls verify_presentation() from newton-tls-notary
///
/// This bypasses the WASM HTTP 1 MiB limit since the host fetches directly.
verify-from-cid: func(proof-cid: string) -> result;
/// Verify a TLSNotary Presentation from raw BCS-serialized bytes.
///
/// Useful for small proofs or testing where bytes are already available.
verify: func(presentation-bytes: list) -> result;
}
world newton-provider {
import http;
import secrets;
import tlsn;
export run: func(input: string) -> result;
}
```
The `http` import provides the `fetch` function for making HTTP requests from within the WASM sandbox. The `run` export is your oracle's entry point.
## Implement in JavaScript
Create `policy.js`:
```js
import { fetch as httpFetch } from "newton:provider/http@0.2.0";
export function run(wasm_args) {
const args = JSON.parse(wasm_args);
// Example: fetch an external API
const result = httpFetch({
url: `https://api.example.com/data?symbol=${args.base_symbol}`,
method: "GET",
headers: [["Accept", "application/json"]],
body: null,
});
if (result.tag === "err") {
return JSON.stringify({ error: result.val });
}
const response = result.val;
if (response.status !== 200) {
return JSON.stringify({ error: "API request failed" });
}
const body = JSON.parse(
new TextDecoder().decode(new Uint8Array(response.body))
);
return JSON.stringify({
price: body.price,
symbol: args.base_symbol,
timestamp: Date.now(),
});
}
```
:::note
Import `httpFetch` from `newton:provider/http@0.2.0` at the top level of your module. The function returns a tagged result: when `result.tag === "err"`, `result.val` contains the error string; otherwise `result.val` is the HTTP response with `status`, `headers`, and `body`.
:::
:::warning
**WASM sandbox network restrictions**: Newton operators execute WASM in sandboxed Wasmtime. Private IPs (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.1`), and link-local (`169.254.0.0/16`) are blocked. Any HTTP endpoint your WASM calls must be on a public URL.
:::
## Declare the input schema
The `wasm_args` your oracle receives are documented and validated by a `wasm_args_schema.json` file — a JSON Schema describing the inputs passed to `run(input)` at evaluation time. Ship it alongside your oracle so callers know exactly what to send.
```json
{
"type": "object",
"description": "Inputs passed to the oracle WASM at evaluation time",
"properties": {
"network": {
"type": "string",
"description": "Network name (e.g. 'mainnet', 'base')."
},
"vaultAddress": {
"type": "string",
"description": "Vault contract address (0x-prefixed) to inspect."
}
},
"required": ["network", "vaultAddress"]
}
```
An oracle ships up to three schemas, each describing a distinct input:
| Schema file | Describes | Read in Rego as |
|-------------|-----------|-----------------|
| `wasm_args_schema.json` | Per-call inputs passed to the oracle's `run(input)` (the `wasm_args` on each task) | — (consumed by the WASM, not Rego) |
| `secrets_schema.json` | Scoped credentials the oracle reads via the secrets host interface (see [Deploying with CLI](/developers/guides/deploying-with-cli)) | — (read inside the WASM) |
| `params_schema.json` | Policy thresholds the contract owner configures | `data.params.*` |
For task creation and full-policy simulation, callers pass this oracle's hex-encoded JSON input in its position in the `wasm_args` array. Individual WASM simulation accepts the same value as a scalar. Validating against this schema before submission catches malformed inputs early.
## Build WASM
Compile the JavaScript into a WASM component using `jco`:
```bash
# Install jco globally (if not already)
npm install -g @bytecodealliance/jco @bytecodealliance/componentize-js
# Build the WASM component
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.
:::note
If you installed `jco` locally (without `-g`), run it via `npx jco componentize ...` instead.
:::
## Test Locally
Test your WASM oracle before deploying:
```bash
# Simulate the policy that contains this WASM module
newton-cli policy simulate -p policies/my-oracle
# Change configs/wasm_args.json to exercise specific oracle input,
# then run the same command again.
```
You can also test via the Gateway RPC:
```bash
curl -X POST https://gateway.testnet.newton.xyz/rpc \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{
"jsonrpc": "2.0",
"method": "newt_simulatePolicyData",
"params": {
"policy_address": "0x...",
"chain_id": 11155111,
"wasm_args": "0x7b22626173655f73796d626f6c223a22425443227d"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}'
```
## Alternative Languages
While JavaScript is the most common choice, you can also write data oracles in:
* **Rust** — see the [Rust WASM Guide](/developers/advanced/rust-wasm-guide)
* **Python** — see the [Python WASM Guide](/developers/advanced/python-wasm-guide)
All languages compile to the same WIT interface and produce interchangeable WASM components.
## Next Steps
Write a Rego policy that uses your oracle's output
Build, simulate, and deploy a Policy with an optional WASM module
# Writing Rego Policies \[Write Rego policies for blockchain transaction authorization with Newton Protocol. Examples for spend limits, allowlists, sanctions screening, and fraud prevention rules.]
A Newton policy is a Rego program that evaluates whether an Intent should be approved. Policies reference data from two sources: configuration parameters (`data.params`) and runtime data from WASM oracles (`data.wasm`).
:::info
Newton supports **Rego v1 syntax** through its fork of **Regorus 0.9.0**. The runtime is an **OPA v1.2.0-compatible subset plus Newton-specific extensions**, not a complete implementation of every OPA built-in. Review the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide#language-and-engine-compatibility) before porting an existing OPA policy.
:::
## What Makes Up a Policy
Every policy deployment requires these files:
| File | Purpose |
|------|---------|
| `policy.rego` | Rego policy logic — the core evaluation rules |
| `policy.wasm` | Compiled WASM data oracle (see [Writing Data Oracles](/developers/guides/writing-data-oracles)) |
| `params_schema.json` | JSON Schema defining configurable parameters |
| `policy_metadata.json` | Human-readable policy metadata |
| `policy_data_metadata.json` | Human-readable oracle metadata |
## Data References
Your Rego policy can access three data namespaces:
| Path | Source | Description |
|------|--------|-------------|
| `input` | Intent | The transaction intent being evaluated (from, to, value, data, chain\_id, function\_signature) |
| `data.params` | PolicyClient | Configuration parameters set by the contract owner (thresholds, allowlists) |
| `data.wasm` | PolicyData WASM | Runtime data returned by your WASM oracle(s) (prices, KYC status). With multiple oracles, each is namespaced under its pack id — see [Consuming Multiple Oracles](#consuming-multiple-oracles) |
## Your First Policy
Create `policy.rego`:
```rego
package sanctions_check
default allow := false
# Allow the transaction if the oracle reports no sanctions match
allow if {
data.wasm.is_sanctioned == false
}
# Also allow if the sender is on the explicit allowlist
allow if {
input.from == data.params.admin
}
```
This policy:
1. Defaults to **deny** (`allow := false`)
2. Allows transactions where the oracle reports no sanctions match
3. Always allows transactions from the configured admin address
## Using Intent Fields
The `input` object contains the Intent fields:
```rego
package spend_limit
default allow := false
# Allow if transfer value is under the configured limit
allow if {
input.value <= data.params.max_value
}
# Block transfers to specific addresses
deny if {
input.to == data.params.blocked_address
}
allow if {
not deny
input.chain_id == 11155111
}
```
## Using Oracle Data
The `data.wasm` path contains whatever your WASM oracle returned:
```rego
package price_check
default allow := false
# Only allow trades when the price is below the configured maximum
allow if {
data.wasm.price < data.params.max_price
data.wasm.symbol == "BTC"
}
```
:::note
A single oracle's fields appear directly under `data.wasm` (e.g. `data.wasm.price`). When a policy reads multiple oracles, each oracle namespaces its output under a pack id (`data.wasm..price`) — see [Consuming Multiple Oracles](#consuming-multiple-oracles).
:::
## Consuming Multiple Oracles
A policy can read from more than one PolicyData oracle. When it does, each oracle namespaces its output under a unique **pack id**, and the network merges them into a single `data.wasm` object. Your Rego then reads `data.wasm..*` and `data.params..*`:
```rego
package vault_gate
import rego.v1
default allow := false
allow if count(deny) == 0
deny contains "risk floor" if {
data.wasm.vaultsfyi.risk_score < data.params.vaultsfyi.risk_score_floor
}
deny contains "token collapsed" if {
data.wasm.webacy.is_collapsed
}
```
See [Composing Policy Sets](/developers/guides/composing-policy-sets) for the full merge model and the deny-set pattern, and [Policy Packs](/developers/guides/policy-packs) for prebuilt oracles you can compose.
## Parameter Schema
Create `params_schema.json` to define which parameters contract owners can configure:
```json
{
"type": "object",
"description": "Sanctions check policy parameters",
"properties": {
"admin": {
"type": "string",
"description": "Admin address that bypasses sanctions check"
},
"max_value": {
"type": "number",
"description": "Maximum transfer value in wei"
}
}
}
```
Leave properties empty if your policy has no configurable parameters:
```json
{
"type": "object",
"description": "",
"properties": {}
}
```
## Metadata Files
Create `policy_metadata.json`:
```json
{
"name": "Sanctions Check Policy",
"version": "0.0.1",
"author": "Your Name",
"link": "https://github.com/your-org/your-policy",
"description": "Checks transaction counterparties against sanctions lists"
}
```
Create `policy_data_metadata.json`:
```json
{
"name": "Sanctions Oracle",
"version": "0.0.1",
"author": "Your Name",
"link": "",
"description": "Fetches sanctions data from screening API"
}
```
## Directory Structure
Organize all files into a `policy-files/` directory:
```bash
policy-files/
├── policy.rego
├── policy.wasm
├── params_schema.json
├── policy_metadata.json
└── policy_data_metadata.json
```
```bash
cp policy.wasm policy.rego params_schema.json \
policy_data_metadata.json policy_metadata.json policy-files/
```
## Testing Locally
Test your policy with the CLI before deploying:
```bash
newton-cli policy simulate \
--wasm-file policy-files/policy.wasm \
--rego-file policy-files/policy.rego \
--intent-json intent.json \
--entrypoint "sanctions_check.allow" \
--policy-params-data policy_params.json
```
:::note
The `--entrypoint` value must match your Rego package name + rule name. For `package sanctions_check` with rule `allow`, use `sanctions_check.allow`.
:::
For a full reference on supported Rego syntax, see the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide).
## Using Identity Data in Policies
Newton provides built-in functions for checking user identity data (KYC status, age, location) within your policies. Identity data is injected by operators from the on-chain IdentityRegistry — you only see boolean check results, never raw personal data.
```rego
package kyc_gated_transfer
default allow = false
allow if {
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(18)
newton.identity.kyc.address_in_countries(["US", "CA"])
newton.identity.kyc.not_expired()
input.value <= data.params.max_value
}
```
Identity built-ins are domain-namespaced (e.g., `newton.identity.kyc.*`). A generic `newton.identity.get("field_name")` accessor is also available for ad-hoc field access across any domain. See the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide#newton-identity-extensions) for the full reference.
## Next Steps
Deploy your policy to IPFS and register it on-chain
Integrate the policy into your smart contract
# Composing Policies and Oracles
A policy-set client composes independent policies. Each Policy stores its Rego artifacts and at most one optional WASM oracle. The aggregate decision is allow only when every policy allows. Pure-Rego policies need no oracle.
This replaces the legacy design in which one policy referenced multiple PolicyData contracts and shallow-merged their outputs. Do not apply the legacy merge or deployment instructions to policy-set contracts.
## When to compose policies
| Requirement | Policy-set shape |
|---|---|
| Vault risk check | One risk policy with its optional oracle |
| Risk and sanctions | Two independent policies, each with its own params and optional oracle |
| Risk, sanctions, and a local rule | Three policies; the local pure-Rego rule retains an empty WASM input slot |
Each policy reads its own `data.params` and `data.wasm`. There is no automatic cross-policy merge. Pack-specific namespaces remain part of that pack's own output schema. If one rule needs several external sources, implement their collection in that policy's single WASM module or keep the checks independent where appropriate.
## Configure and preserve order
Deploy the independent policies and call `setPolicies` with their ordered specs. See [Smart Contract Integration](/developers/guides/smart-contract-integration#configure-the-policy-set). The owner's params and expiry belong to each entry. Never sort or deduplicate entries, including repeated addresses.
## Simulating a set with oracle-backed policies
For the policy-set implementation, simulate the complete **set**:
```typescript
const result = await client.simulatePolicy({
policyClient,
chainId: 11155111,
intent,
wasmArgs: [riskInputHex, sanctionsInputHex, '0x'],
});
console.log(result.success, result.allowed);
for (const policy of result.policies ?? []) {
console.log(policy.policy_index, policy.policy, policy.allowed, policy.result);
}
```
Upload required secrets per Policy address and client before simulation. `newt_simulatePolicy` runs optional WASM and Rego in-process, with no BLS signatures. Use `newt_simulateTask` only with precomputed outputs; both resolve the current set rather than replaying a historical snapshot.
## Deployment and validation
Use [Deploying with CLI](/developers/guides/deploying-with-cli) for pinned artifacts, factory deployment, and owner configuration. Existing dashboard or pack catalogs may still describe legacy PolicyData deployments; confirm their release before treating an address as a policy-set entry.
Run the [policy-set truth table and stale-attestation checks](/developers/guides/testing-policies#policy-set-validation-checklist). An identical replacement or reorder advances the revision and invalidates old attestations, even on the same client.
# Policy Packs \[Newton policy packs are deployed data oracles with typed schemas, Rego templates, npm bindings, and onchain PolicyData addresses.]
A **policy pack** is a prebuilt data oracle published for Newton policies. Instead of writing and deploying your own WASM oracle, you can reference a pack's deployed `NewtonPolicyData` contract, configure its params, and upload any API secrets the pack requires.
Each pack ships:
* A deployed WASM oracle.
* A reference Rego template.
* JSON schemas for params, secrets, and per-call `wasmArgs`.
* A typed npm package such as `@newton-xyz/policy-pack-vaultsfyi`.
* Canonical deployment metadata in [`newton-policy-packs/deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json).
Policy packs can be composed. Each pack namespaces its output under its pack id, so Rego can safely read values such as `data.wasm.vaultsfyi.risk_score` and `data.params.chainalysis.deny_on_sanctioned` in the same policy. See [Chaining Multiple Data Oracles](/developers/guides/chaining-data-oracles) for the merge model.
## Pack Catalog
The detailed pack catalog lives in the VaultKit docs:
Browse all supported packs, production PolicyData addresses, and provider links.
Source of truth for every pack, chain, environment, PolicyData address, and WASM CID.
## Available Packs
Grouped by data provider. Full detail, params, and Rego for each is in the VaultKit docs.
| Provider | Policy pack |
| --- | --- |
| [Arkham](https://arkm.com/api) | [Entity-Aware Spending Gate](/developers/vaults/policies/arkham-entity) |
| [Arkham](https://arkm.com/api) | [Adaptive Counterparty Gate](/developers/vaults/policies/arkham-counterparty) |
| [Arkham](https://arkm.com/api) | [Explainable Risk Exposure](/developers/vaults/policies/arkham-risk) |
| [Chainalysis](https://www.chainalysis.com) | [Address Screening](/developers/vaults/policies/chainalysis) |
| [Pharos](https://pharos.watch) | [Risk-Adjusted Treasury Gate](/developers/vaults/policies/pharos-treasury) |
| [Pharos](https://pharos.watch) | [Stablecoin Safe Mode](/developers/vaults/policies/pharos-safe-mode) |
| [Pharos](https://pharos.watch) | [Redemption-Backed Asset Gate](/developers/vaults/policies/pharos-redemption) |
| [RedStone](https://redstone.finance) | [Oracle Divergence](/developers/vaults/policies/redstone) |
| [Vaults.fyi](https://vaults.fyi) | [Vault Risk Rating](/developers/vaults/policies/vaultsfyi) |
| [Webacy](https://developers.webacy.co/) | [Depeg Risk](/developers/vaults/policies/webacy) |
| [Xerberus](https://xerberus.io) | [Liquidity Exit](/developers/vaults/policies/xerberus-liquidity-exit) |
| [Xerberus](https://xerberus.io) | [Portfolio What-If](/developers/vaults/policies/xerberus-what-if) |
| [Xerberus](https://xerberus.io) | [Common Cause](/developers/vaults/policies/xerberus-common-cause) |
## How to Use Packs
There are two ways to use a pack: the [Newton Dashboard](https://dashboard.newton.xyz) (no CLI required) and [`newton-cli`](/developers/reference/command-line-tool). Both reference the same open-source packs in the [`newton-policy-packs`](https://github.com/newt-foundation/newton-policy-packs) repo and the same deployed `PolicyData` addresses in [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) — pick whichever fits your workflow.
### Using the Dashboard
The dashboard lets you browse the available data oracles, add one to your policy, configure its secrets and params, simulate, and deploy, all from the browser.
# Deploying with CLI \[Build, simulate, and deploy Newton policies with newton-cli, then wire a tested PolicyClient with Foundry and cast]
This guide starts from a scaffolded Newton policy, proves its allow and deny behavior locally, deploys independent Policy contracts, and then wires an existing PolicyClient.
:::warning
Deployment uploads artifacts and sends transactions. Finish local simulation first, review the target chain and transactions, and use a funded testnet signer before broadcasting.
:::
## Prerequisites
* `newton-cli`: `curl -L cli.newton.xyz | sh && newtup`
* A policy created with `newton-cli policy scaffold`
* A tested contract that inherits `NewtonPolicyClient`
* A target chain, funded deployer, and RPC endpoint
* Foundry (`forge` and `cast`) for PolicyClient deployment and wiring
Run the toolchain check before building:
```bash
newtup
newton-cli --version
newton-cli doctor
```
Dashboard login is not required to scaffold, build, or simulate locally.
## Credential handling
Provide deploy credentials through the current process environment or `~/.newton/.env`:
```bash
export CHAIN_ID=11155111
export RPC_URL=https://your-sepolia-rpc.example
export PRIVATE_KEY=0xYourDeploymentKey
```
Do not commit populated credentials or source a project-local `.env`. Never paste private keys, JWTs, API keys, or credential-bearing RPC URLs into chat with a coding agent.
Provide any CLI upload credentials through your secret manager or protected shell environment. Gateway SDK examples use `NEWTON_API_KEY`, a separate application environment-variable name.
## Step 1 — Build the policy
Given a scaffolded directory such as `policies/transfer-limit`:
```bash
newton-cli policy build -p policies/transfer-limit
```
Use the CLI build command instead of invoking `jco componentize` directly. It validates the expected policy files and writes compiled artifacts under `dist/`.
## Step 2 — Simulate allow and deny
Configure `configs/intent.json`, `configs/wasm_args.json`, and `configs/params.json`, then run:
```bash
newton-cli policy simulate -p policies/transfer-limit
```
Run at least one expected allow case and one expected deny case. When the WASM provider calls `getSecrets()`, pass a non-production fixture stored outside tracked source:
```bash
newton-cli policy simulate -p policies/transfer-limit \
--secrets-file /secure/local-fixtures/transfer-limit-secrets.json
```
Local WASM output is available to Rego under `data.wasm.*`; policy parameters are under `data.params.*`; the transaction intent is `input`.
Do not deploy until both cases match the intended policy.
## Step 3 — Review deployment configuration
`policy scaffold` creates the non-secret file `configs/deployment.toml`. Review its chain and expiration settings before deployment. Environment variables and explicit CLI flags override the file.
The policy directory should now include:
```text
policies/transfer-limit/
policy.js
policy.rego
params_schema.json
configs/
deployment.toml
intent.json
params.json
wasm_args.json
dist/
policy.wasm
policy.rego
```
## Step 4 — Deploy each policy
Deploy each independent policy and record its artifact CIDs, code hash, address, receipt, and deployed bytecode.
```bash
newton-cli policy deploy -p policies/transfer-limit
```
### Pure-Rego artifacts
A pure-Rego policy needs no dummy WASM or separately deployed PolicyData contract. The pinned factory takes these fields:
| Field | Value |
|---|---|
| `entrypoint` | The policy's Rego entrypoint |
| `policyCid` | CID of the exact Rego bytes |
| `schemaCid` | Policy parameter schema CID |
| `wasmCid` | Empty string for pure-Rego |
| `secretsSchemaCid` | Empty string for pure-Rego |
| `metadataCid` | Policy metadata CID |
| `owner` | Policy owner address |
| `policyCodeHash` | Keccak-256 of the exact Rego bytes |
For an oracle-backed policy, `wasmCid` and optional `secretsSchemaCid` belong to that Policy contract. Each policy owns at most one WASM module.
:::warning
Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
:::
## Step 5 — Deploy the PolicyClient
The PolicyClient is application code, not something `registerClient` creates. Implement and test it with Foundry first, then deploy it with the local deployer as initial owner:
```bash
forge build
forge test
forge script script/DeployPolicyClient.s.sol:DeployPolicyClient \
--rpc-url "$RPC_URL" \
--broadcast
```
Resolve current protocol addresses for the target chain rather than copying constants from an old guide:
```bash
newton-cli --chain-id "$CHAIN_ID" deployments show
```
## Step 6 — Bind policy and parameters
While the local deployer owns the client, replace its complete policy set with one call. Set `POLICY_0`, `POLICY_1`, and the client address to reviewed deployments. Each params file must match its policy schema.
```bash
PARAMS_0=$(cast from-utf8 "$(cat policies/first/configs/params.json)")
PARAMS_1=$(cast from-utf8 "$(cat policies/second/configs/params.json)")
cast send "$POLICY_CLIENT" \
'setPolicies((address,(bytes,uint32))[])' \
"[($POLICY_0,($PARAMS_0,25)),($POLICY_1,($PARAMS_1,25))]" \
--rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
cast call "$POLICY_CLIENT" \
'getPolicySetSnapshot()(bytes32,uint64,(address,(bytes,uint32))[])' \
--rpc-url "$RPC_URL"
```
The expiry of 25 blocks is illustrative. Choose an application-appropriate window. Record the returned ID, revision, and ordered specs together. Every replacement, including an identical one, advances the revision and invalidates old attestations.
## Step 7 — Transfer ownership for dashboard operations
Gateway secrets and client-scoped live evaluation authorize against `PolicyClient.getOwner()`. If the dashboard login wallet must own the client, transfer after policy binding is complete:
```bash
cast send 0xPOLICY_CLIENT \
"setPolicyClientOwner(address)" 0xDASHBOARD_LOGIN_WALLET \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY"
```
Verify `getOwner()` on-chain. `PolicyClientRegistry.setClientOwner` changes the registry record; it is not a substitute for `setPolicyClientOwner` on the contract.
:::warning
After ownership transfer, only the new owner can call `setPolicies`. That owner can replace the complete set and its parameters on the same client; a new client is not required for routine configuration changes.
:::
## Step 8 — Register with the dashboard user (coming soon)
:::note
PolicyClient registration is temporarily omitted from this guide. After [newt-foundation/newton-prover-avs#771](https://github.com/newt-foundation/newton-prover-avs/pull/771) lands in a stable `newton-cli` release, `newton-cli policy-client register` will open the dashboard so the dashboard user can sign and register the client. The exact released command and verification steps will be added here then.
:::
## Step 9 — Upload secrets when used
For oracle-backed policies, verify the Policy contract's `secretsSchemaCid`, client ownership, and schema-valid secrets before upload. Secrets are scoped to the policy address and client. Use [the gateway secrets API](/developers/reference/rpc-api#newt_storeencryptedsecrets) for the selected policy-set release.
Pure-Rego policies skip this step. Keep secrets outside tracked source and never print them while diagnosing uploads.
## Step 10 — Verify before live evaluation
Check these values on-chain:
* PolicyClient TaskManager matches the target Newton deployment
* `getPolicySetSnapshot()` returns the expected nonzero ID, revision, and ordered policies
* Each policy has the intended params and nonzero `expireAfter`
* `getOwner()` is the expected wallet
* Registry record matches the dashboard user when registration is available and required
* Client-scoped secrets exist when the WASM provider requires them
Use `@newton-xyz/sdk` `evaluateIntentDirect` for a live direct-attestation flow. The browser wallet signs the exact EIP-712 intent; a server Route Handler supplies `NEWTON_API_KEY`. See [Frontend SDK Integration](/developers/guides/frontend-sdk-integration).
After the first live evaluation, confirm the task was recorded against the right PolicyClient with the [Explorer API](/developers/reference/explorer-api), using the same `NEWTON_API_KEY`:
```bash
curl -sS https://explorer.api.newton.xyz/v1/task/$TASK_ID \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet" \
| jq '{policy_client_address, policy_id, evaluation_result, error}'
```
`policy_client_address` and `policy_id` should match what you bound in Step 6.
## Agent-assisted deployment
Newton Agent Skills automate the same local-first sequence and preserve policy and client state in handoff files. They still pause before uploads, broadcasts, ownership transfer, dashboard registration when available, gateway evaluation, and the first attested application transaction.
Start from a product brief and review each live checkpoint
Implement and test the PolicyClient before deployment
Sign in the browser, evaluate on the server, and submit only on allow
# Smart Contract Integration \[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.
## Overview
Newton provides the `NewtonPolicyClient` Solidity mixin that handles:
* Attestation validation (standard and direct methods)
* Policy configuration and parameter management
* On-chain ownership and version compatibility
### When to Use
| Use Case | Example |
|----------|---------|
| Transaction authorization | Wallet contracts requiring policy approval |
| Spend limits | Daily/weekly transfer limits on treasury contracts |
| Allowlist/blocklist enforcement | Restricting interactions to approved addresses |
| Time-based access control | Operations only allowed during certain hours |
| Complex business logic | Multi-condition approval workflows |
| Off-chain data verification | Price feeds, identity verification, compliance checks |
## Core Concepts
Before writing code, understand the key data structures your contract will work with.
### Intent
An [Intent](/developers/reference/glossary#intent) describes the transaction a user wants to execute:
```solidity
struct Intent {
address from; // Signer; checked against msg.sender
address to; // Target contract
uint256 value; // ETH value
bytes data; // ABI-encoded calldata
uint256 chainId; // Chain ID for cross-chain safety
bytes functionSignature; // Human-readable function signature
}
```
### Attestation
An [Attestation](/developers/reference/glossary#attestation) is cryptographic proof that Newton operators evaluated a policy and approved the Intent:
```solidity
struct Attestation {
bytes32 taskId; // Unique task identifier
bytes32 policyId; // Policy that was evaluated
address policyClient; // Your contract address
uint32 expiration; // Block number when attestation expires
Intent intent; // The approved intent
bytes intentSignature; // User's signature on the intent
}
```
### Task Lifecycle
```mermaid
flowchart LR
subgraph User["User"]
A[Signs Intent]
end
subgraph Gateway["Gateway"]
B[Creates Task]
end
subgraph Operators["Operators"]
C[Evaluate & Sign]
end
subgraph Contract["Your Contract"]
D[Validates]
end
A --> B --> C --> D
```
1. **User signs Intent** — creates and signs an Intent describing the desired transaction
2. **Task created** — the Gateway creates a task on the TaskManager contract
3. **Operators evaluate** — operators fetch the policy, evaluate it, and BLS-sign the result
4. **Aggregator collects** — signatures are aggregated until quorum is reached
5. **Contract validates** — your contract validates the attestation and executes the transaction
## Quick Start
### Create the Foundry project
```bash
mkdir newton-policy-wallet && cd newton-policy-wallet
forge init --no-git
git init
forge install newt-foundation/newton-contracts
```
### Write the contract
Create `src/NewtonPolicyWallet.sol`:
```solidity
// 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();
address constant NEWTON_TASK_MANAGER = 0xecb741F4875770f9A5F060cb30F6c9eb5966eD13;
constructor(address owner) {
_initNewtonPolicyClient(NEWTON_TASK_MANAGER, owner);
}
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return super.supportsInterface(interfaceId);
}
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()
);
// Bind every executed field to the signed intent.
require(to == taskResponse.intent.to, "Target mismatch");
require(value == taskResponse.intent.value, "Value mismatch");
require(keccak256(data) == keccak256(taskResponse.intent.data), "Calldata mismatch");
(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 {}
}
```
The `NEWTON_TASK_MANAGER` constant should use the `NewtonProverTaskManager` address for your target network. See [Contract Addresses](/developers/reference/contract-addresses) for the current deployed addresses.
### Configure foundry.toml
:::warning
You **must** set `via_ir = true`. The newton-contracts library requires IR-based compilation. Without it, compilation fails.
:::
```toml
[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}"
```
Add Newton attestation validation to an existing contract by inheriting `NewtonPolicyClient`. Initialization is deferred to a post-deployment admin function.
```solidity
// SPDX-License-Identifier: MIT
pragma 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;
}
// The owner calls inherited setPolicies(specs) after deploying the policies.
/// @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);
}
}
```
:::warning
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
:::
Install the dependency:
```bash
forge install newt-foundation/newton-contracts
```
## Validation Methods
Newton provides two validation methods for different use cases.
### Standard Validation
```solidity
function _validateAttestation(
NewtonMessage.Attestation memory attestation
) internal returns (bool);
```
The standard method validates against an attestation that the Newton aggregator has already submitted on-chain via `respondToTask`. It verifies:
1. Policy ID matches your contract's configured policy
2. Intent sender (`from`) matches `msg.sender`
3. Chain ID matches the current chain
4. Attestation hash matches stored hash, is not expired, and has not been spent
### Direct Validation
```solidity
function _validateAttestationDirect(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
bytes calldata signatureData
) internal returns (bool);
```
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.
### Comparison
| Aspect | `_validateAttestation` | `_validateAttestationDirect` |
|--------|------------------------|------------------------------|
| **Prerequisite** | `respondToTask` called | Task created only |
| **Gas Cost** | ~50,000 - 100,000 | ~200,000 - 500,000 (BLS verification) |
| **Latency** | Wait for aggregator | Immediate |
| **Parameters** | Attestation only | Task + Response + Signatures |
| **Use Case** | Standard flow | Time-sensitive operations |
Direct validation is recommended for most use cases as it avoids waiting for the aggregator and works with the `evaluateIntentDirect` SDK method.
## API Reference
### Policy-set interface
```solidity
struct PolicySpec {
address policy;
INewtonPolicy.PolicyConfig config;
}
function setPolicies(PolicySpec[] calldata policies) external returns (bytes32);
function getPolicies() external view returns (PolicySpec[] memory);
function getPolicyId() external view returns (bytes32);
function policyRevision() external view returns (uint64);
function getPolicySetSnapshot() external view returns (bytes32, uint64, PolicySpec[] memory);
function getNewtonPolicyTaskManager() external view returns (address);
function getOwner() external view returns (address);
```
`setPolicies` is owner-only and atomically replaces the complete ordered set. Each entry has its own `policyParams` bytes and nonzero `expireAfter` in blocks. Repeated addresses remain separate entries: never sort or deduplicate the set.
Use `getPolicySetSnapshot()` to read the ID, revision, and specs together. Separate reads can straddle an owner update. The getter is `getPolicyId()`, not `policyId()`.
| Mixin function | Purpose |
|---|---|
| `_initNewtonPolicyClient(taskManager, owner)` | Initialize TaskManager and owner |
| `_initPolicies(specs)` | Initial configuration; an empty list defers configuration |
| `_setPolicies(specs)` | Internal complete-set replacement; protect any custom wrapper |
| `_policies()` / `_policyId()` | Internal reads of configured specs / set ID |
| `setPolicyClientOwner(newOwner)` | Owner-only authority transfer |
An unconfigured client is not ready to authorize actions. The pinned mixin rejects an empty replacement, more than 8 policies, unregistered policies, zero expiration, and policy parameters over 65,536 bytes. It checks `isPolicy(address)` on the TaskManager's configured factory. It does **not** perform the old factory semantic-version comparison in `_setPolicies`.
## Deployment
Create `script/Deploy.s.sol`:
```solidity
// 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 {
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();
}
}
```
:::warning
The Task Manager address **must** be `0xecb741F4875770f9A5F060cb30F6c9eb5966eD13` on Sepolia. BLS signatures are bound to this address — using any other causes `InvalidAttestation`.
:::
### Configure the policy set
Deploy two independent policies, then have the client owner submit one configuration transaction. This example uses empty JSON parameters; replace them with each policy's schema-valid configuration.
```solidity
import {INewtonPolicyClient} from "newton-contracts/src/interfaces/INewtonPolicyClient.sol";
import {INewtonPolicy} from "newton-contracts/src/interfaces/INewtonPolicy.sol";
// Inside the owner's Foundry broadcast script:
INewtonPolicyClient.PolicySpec[] memory specs = new INewtonPolicyClient.PolicySpec[](2);
specs[0] = INewtonPolicyClient.PolicySpec({
policy: firstPolicy,
config: INewtonPolicy.PolicyConfig({policyParams: bytes("{}"), expireAfter: 25})
});
specs[1] = INewtonPolicyClient.PolicySpec({
policy: secondPolicy,
config: INewtonPolicy.PolicyConfig({policyParams: bytes("{}"), expireAfter: 25})
});
bytes32 newPolicyId = client.setPolicies(specs);
```
Choose each expiration for your application's block-time and authorization window; 25 is illustrative. For exact `cast` configuration and readback commands, see [Deploying with CLI](/developers/guides/deploying-with-cli#step-6--bind-policy-and-parameters).
### Replace a set on the same client
Call `setPolicies` again with the entire desired list, including unchanged entries. Every successful replacement increments the revision, even an identical replacement. The ID commits to a domain separator, chain, client address, revision, and ordered specs. Reordering also changes the ID.
Old attestations become unusable when their ID no longer matches the client's ID. Refresh application configuration and request a new evaluation after replacement. Changing policies on a policy-set-capable client does not require deploying another client; upgrading a legacy client implementation is a separate migration.
The Task freezes `policies`, `policyRevision`, and `policyId` at admission. Preserve that snapshot and the signed response unchanged when submitting direct validation.
## PolicyClientRegistry
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`.
### Why Register?
| Benefit | Description |
|---------|-------------|
| Identity linking | `IdentityRegistry` enforces that only registered clients can have user identity data linked |
| Discoverability | `getClientsByOwner()` enables enumeration of all clients owned by an address |
| Lifecycle management | Deactivate/reactivate clients without re-deploying |
| Ownership transfer | Transfer the registry record to a new owner |
### Registration
Registration is permissionless — any address can register a policy client contract, becoming its registered owner:
```solidity
// The caller becomes the registered owner
policyClientRegistry.registerClient(address(myPolicyClient));
```
Requirements:
* The client contract must implement `INewtonPolicyClient` (verified via ERC-165)
* Each client can only be registered once
### Managing Client Status
```solidity
// Deactivate a client (prevents new identity links)
policyClientRegistry.deactivateClient(address(myPolicyClient));
// Reactivate
policyClientRegistry.activateClient(address(myPolicyClient));
// Transfer ownership of the registry record
policyClientRegistry.setClientOwner(address(myPolicyClient), newOwnerAddress);
```
### CLI Management
All registry operations are available via `newton-cli policy-client`:
```bash
# Register a client
newton-cli policy-client register \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --client 0x... --private-key $KEY --rpc-url $RPC
# Check registration status
newton-cli policy-client status \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --client 0x... --rpc-url $RPC
# List clients by owner
newton-cli policy-client list \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --owner 0x... --rpc-url $RPC
# Deactivate / activate / transfer ownership
newton-cli policy-client deactivate \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --client 0x... --private-key $KEY --rpc-url $RPC
newton-cli policy-client activate \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --client 0x... --private-key $KEY --rpc-url $RPC
newton-cli policy-client transfer-ownership \
--registry 0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd --client 0x... --new-owner 0x... --private-key $KEY --rpc-url $RPC
```
### IdentityRegistry Integration
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:
1. Deploy `PolicyClientRegistry` and initialize it
2. Deploy `IdentityRegistry` and pass the registry address to `initialize(admin, policyClientRegistry)`
3. Register approved policy clients via `registerClient()`
4. Any identity linking attempt against an unregistered or deactivated client reverts
## Version Compatibility
This guide targets the documented policy-set contracts. Use the dependency above with the deployed addresses for your target network.
Check the client interface, TaskManager, configured policy factory, gateway schema, and SDK together. A CLI version or an on-chain `version()` string alone does not prove ABI compatibility.
The owner can replace policies only with policies registered by the TaskManager's configured factory. Verify provenance with `isPolicy(policy)`. Preserve the inherited `supportsInterface` implementation instead of hardcoding the old single-policy interface ID. Legacy clients need an implementation upgrade or replacement before they expose `setPolicies`; do not send this call to a single-policy client.
## Common Patterns
### Decode Intent Data
Extract function arguments from the attestation:
```solidity
function decodeTransferIntent(
NewtonMessage.Attestation calldata attestation
) internal pure returns (address to, uint256 amount) {
// Skip the function selector (4 bytes)
(to, amount) = abi.decode(
attestation.intent.data[4:],
(address, uint256)
);
}
```
### Verify Function Selector
Ensure the attestation is for the expected function:
```solidity
bytes4 selector = bytes4(attestation.intent.data[:4]);
require(selector == IMyContract.expectedFunction.selector, "Wrong function");
```
### Execute Raw Intent
Execute the intent exactly as specified with error bubbling:
```solidity
function executeIntent(
NewtonMessage.Attestation calldata attestation
) internal returns (bytes memory) {
require(_validateAttestation(attestation), "Invalid attestation");
(bool success, bytes memory returnData) = attestation.intent.to.call{
value: attestation.intent.value
}(attestation.intent.data);
if (!success) {
if (returnData.length > 0) {
assembly {
revert(add(32, returnData), mload(returnData))
}
}
revert("Execution failed");
}
return returnData;
}
```
### Multi-Attestation Batching
Process multiple attestations in one transaction:
```solidity
function batchExecute(
NewtonMessage.Attestation[] calldata attestations
) external returns (bool[] memory results) {
results = new bool[](attestations.length);
for (uint256 i = 0; i < attestations.length; i++) {
require(_validateAttestation(attestations[i]), "Invalid attestation");
results[i] = _executeAction(attestations[i]);
}
}
```
## Security Considerations
### Always Validate Before Execution
Never execute business logic before validation completes:
```solidity
// CORRECT
function withdraw(NewtonMessage.Attestation calldata attestation) external {
require(_validateAttestation(attestation), "Invalid"); // Validate first
_transfer(attestation.intent.to, attestation.intent.value); // Then execute
}
// WRONG - DO NOT DO THIS
function withdraw(NewtonMessage.Attestation calldata attestation) external {
_transfer(attestation.intent.to, attestation.intent.value); // Executes before validation!
require(_validateAttestation(attestation), "Invalid");
}
```
### Built-in Safety Checks
The `NewtonPolicyClient` mixin automatically enforces:
* **Sender verification** — `attestation.intent.from == msg.sender` (prevents user A from using user B's attestation)
* **Chain ID verification** — `attestation.intent.chainId == block.chainid` (prevents cross-chain replay attacks)
* **Expiration** — `block.number < attestation.expiration` (attestations expire after the configured block window)
* **Replay protection** — each attestation can only be used once (marked as "spent" after validation)
### Additional Best Practices
* Always verify function selectors match expectations when decoding intent data
* Set appropriate `expireAfter` values — too short and users cannot execute; too long and the security window increases
* Protect the `policyClientOwner` key with a multisig or timelock
* Preserve the inherited `supportsInterface` implementation for the pinned interface
* Fund the contract with ETH if it needs to execute value transfers
## Troubleshooting
| Error | Cause | Resolution |
|-------|-------|------------|
| `InvalidAttestation()` | Wrong Task Manager address, expired attestation, or policy mismatch | Verify Task Manager address matches [Sepolia deployment](/developers/reference/contract-addresses) |
| `ExecutionFailed()` | The target call reverted | Debug the target transaction independently |
| `PolicyNotRegistered` | Policy is not registered with the configured factory | Verify `policyFactory()` and `isPolicy(policy)` |
| `PolicyClientNotRegistered` | Client not registered in `PolicyClientRegistry` | Register via `registerClient()` or CLI `newton-cli policy-client register` |
| `Policy ID does not match` | Attestation's policy ID differs from configured policy | Read `getPolicySetSnapshot()` and request a fresh attestation after replacement |
| `Not authorized intent sender` | `msg.sender` does not match `attestation.intent.from` | Ensure the user who signed the intent calls the function |
| `Chain ID does not match` | Attestation created for a different chain | Ensure the intent specifies the correct `chainId` |
| `Attestation expired` | `block.number >= attestation.expiration` | Request a new attestation; consider increasing `expireAfter` |
| `Attestation already spent` | Attestation has already been used | Each attestation is single-use; request a new one |
| `InterfaceNotSupported` | Contract does not implement `IERC165` correctly | Override `supportsInterface` and call `super.supportsInterface(interfaceId)` |
| Stack too deep | Missing `via_ir = true` | Add `via_ir = true` to foundry.toml |
See [Error Reference](/developers/reference/error-reference) for the full list of Newton error codes.
## Next Steps
Build a frontend that submits tasks and executes attested transactions
Full TypeScript SDK documentation
Deployed Newton contract addresses on all networks
# 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 ||
!Array.isArray(body.wasmArgs) ||
body.wasmArgs.length === 0 ||
!body.wasmArgs.every((arg) => typeof arg === "string" && /^0x(?:[0-9a-fA-F]{2})*$/.test(arg)) ||
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,
wasmArgs: body.wasmArgs,
timeout: 30, // seconds
});
return new Response(JSON.stringify(result, (_, value) =>
typeof value === "bigint" ? `0x${value.toString(16)}` : value
), { headers: { "Content-Type": "application/json" } });
} catch (error) {
const message = error instanceof Error ? error.message : "Evaluation failed";
return NextResponse.json({ error: message }, { status: 502 });
}
}
```
Read `getPolicySetSnapshot()` to confirm the expected set and argument count. Keep each argument at its policy index, even for repeated policies. Reconfiguration requires a fresh evaluation; never rewrite the returned task or signed response.
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,
wasmArgs: ["0x", "0x"], // Example: two pure-Rego policies, in set order
}),
});
const evaluation = await response.json();
if (!response.ok) throw new Error(evaluation.error ?? "Evaluation failed");
if (evaluation.allowed !== true) {
// 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 `allowed`. 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
Scaffold this server/client split from a brief and existing handoff files
Review `evaluateIntentDirect` and response types
Review the underlying gateway wire format
# Using the Dashboard \[Build, simulate, and deploy Newton policies from the browser — no CLI required. Sign in, generate an API key, add data oracles, configure secrets and params, simulate, and deploy a policy bound to your PolicyClient.]
The [Newton Dashboard](https://dashboard.newton.xyz) is the no-CLI path for building on Newton. It lets you generate an API key, author a Rego policy in the browser, attach one or more data oracles, configure their parameters and secrets, simulate against sample intents, and deploy the policy on-chain — all from a web UI. Everything in this guide can also be done with [`newton-cli`](/developers/reference/command-line-tool) and the [SDK](/developers/reference/sdk-reference); the dashboard packages the same operations into a guided flow.
## Sign in
Go to [dashboard.newton.xyz](https://dashboard.newton.xyz) and sign in. You'll need an **email** (for the one-time sign-in code) and a **wallet** (to sign the on-chain transactions for registering and deploying).
## Generate an API key
Open **API Keys** in the left navigation. Your key is generated and ready to use — copy it for SDK or CLI integration. API keys authenticate calls to the Newton Gateway and authorize secrets management. See [Dashboard & API Keys](/developers/overview/dashboard-api-keys) for permissions, rotation, and programmatic key management.
## Create a project
Creating a project walks through naming it, choosing a chain (Sepolia or Base Sepolia), setting a PolicyClient, and selecting data oracles. Pick oracles from the built-in [policy packs](/developers/guides/policy-packs) (Chainalysis, vaults.fyi, Webacy, RedStone) or add a custom one by its deployed PolicyData address. Selecting a built-in oracle pre-fills a Rego template, default parameters, and the list of secrets it requires, so you start from a working baseline.
:::note
A PolicyClient is required to get into the editor. Either enter the address of an existing one (a contract that inherits `NewtonPolicyClient` — see [Smart Contract Integration](/developers/guides/smart-contract-integration)), or click **Deploy a sample one** to have the dashboard deploy a template `NewtonPolicyWallet` for you.
:::
## Edit the policy
The policy editor is a three-pane workspace:
* **Rego editor** — a Monaco editor with syntax highlighting for your `policy.rego`. Edit the rules directly.
* **Oracle sidebar** — add or remove data oracles. Adding more than one produces a [multi-oracle policy](/developers/guides/chaining-data-oracles); each oracle's output is namespaced under its pack id, so reference it in Rego as `data.wasm..*`.
* **Configuration** — per oracle, set:
* **Parameters** — the thresholds your Rego reads from `data.params..*` (e.g. `risk_score_floor`).
* **Secrets** — the API key(s) the oracle needs (e.g. `VAULTS_FYI_API_KEY`). The dashboard encrypts these client-side and uploads them per PolicyData address, exactly like [`newton-cli secrets upload`](/developers/guides/deploying-with-cli).
## Simulate
Before deploying, run a simulation from the editor. Provide:
* **WASM args** — the per-call inputs each oracle expects (e.g. `{ "network": "mainnet", "vaultAddress": "0x..." }`).
* **Intent** — the transaction being evaluated (`from`, `to`, `value`, `data`, `chain_id`).
* **Params** — your namespaced parameters.
The dashboard runs the full pipeline — executes every oracle, merges outputs into `data.wasm`, and evaluates your Rego — then shows the allow/deny result and the merged data the policy saw. Use this to confirm your Rego references resolve and the decision is what you expect. This is the same evaluation `newt_simulatePolicy` performs; see [Testing Policies & Oracles](/developers/guides/testing-policies).
## Deploy
When the policy behaves correctly, deploy it. The dashboard:
:::warning
Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
:::
1. Generates CIDs for your Rego and schemas and uploads them to IPFS.
2. Deploys the PolicyData contract(s) and the `NewtonPolicy` contract — bound to **all** selected oracle addresses, so multi-oracle policies deploy in one step.
3. Binds the policy to your PolicyClient and records the deployed addresses.
This is the recommended way to deploy a [multi-oracle policy](/developers/guides/chaining-data-oracles) today, since the CLI currently binds a single PolicyData address per `policy deploy`.
## Integrate with Newton Shield
For projects using the Newton Shield template, the dashboard generates a ready-to-paste integration snippet — the `NewtonShield` setup with your API key, PolicyClient address, and chain — plus the deployed contract addresses and links to inspect transactions. Copy it into your application to start submitting intents. For custom integrations, wire the deployed policy address into your smart contract per [Smart Contract Integration](/developers/guides/smart-contract-integration).
## Next Steps
Permissions, rotation, and programmatic key management
The oracles available in the editor
How multiple oracles compose under one policy
The same operations from the command line
# Testing Policies & Oracles \[Unit-test Newton Rego policies with opa test, test WASM data oracles locally and via the Gateway, and run end-to-end policy simulations before deploying.]
Test at every layer before you deploy: unit-test your Rego rules with `opa test`, run your WASM oracle in isolation, then simulate the full policy end-to-end against the Newton network. This guide covers all three. For diagnosing failures after deployment, see [Testing & Debugging](/developers/resources/testing-debugging).
:::warning
Newton evaluates Rego v1 with its fork of Regorus 0.9.0, using an OPA v1.2.0-compatible subset plus Newton-specific extensions. Passing `opa test` validates policy logic against OPA, but it does not prove that every built-in used by the policy exists in Newton. Run the CLI Regorus checks and an end-to-end Newton simulation before deployment.
:::
## Unit-test your Rego with `opa test`
Rego rules are pure functions of `input`, `data.params`, and `data.wasm`, which makes them easy to unit-test without the network. Write a companion `policy_test.rego` and mock the data with `with`.
Given a policy like this:
```rego
package vault_risk_rating
import future.keywords
default allow := false
t := data.params
# Each oracle's output is namespaced under its pack id.
v := data.wasm.vaultsfyi
deny contains "apy_spike" if v.apy_z_score > t.apy_z_max
deny contains "risk_score_below_floor" if {
v.risk_score != null
v.risk_score < t.risk_score_floor
}
allow if count(deny) == 0
```
Write `policy_test.rego` alongside it. Mock `data.params` and `data.wasm` with `with`, and assert on both `allow` and membership in the `deny` set:
```rego
package vault_risk_rating_test
import data.vault_risk_rating
default_params := {
"apy_z_max": 4,
"risk_score_floor": 60,
}
clean_data := {
"apy_z_score": 0.5,
"risk_score": 90,
}
# The policy reads data.wasm.vaultsfyi.*, so wrap fixtures under the pack id.
wrap(inner) := {"vaultsfyi": inner}
with_data(overrides) := wrap(object.union(clean_data, overrides))
test_allow_when_all_clean if {
d := wrap(clean_data)
vault_risk_rating.allow with data.params as default_params with data.wasm as d
}
test_deny_apy_spike if {
d := with_data({"apy_z_score": 5})
"apy_spike" in vault_risk_rating.deny with data.params as default_params with data.wasm as d
not vault_risk_rating.allow with data.params as default_params with data.wasm as d
}
test_deny_risk_score_below_floor if {
d := with_data({"risk_score": 30})
"risk_score_below_floor" in vault_risk_rating.deny with data.params as default_params with data.wasm as d
}
# A null risk score must not deny — guards against a fail-closed regression.
test_risk_score_null_does_not_deny if {
d := with_data({"risk_score": null})
not "risk_score_below_floor" in vault_risk_rating.deny with data.params as default_params with data.wasm as d
}
# Two simultaneous failures must coexist in the deny set, not fail open.
test_multiple_denies_do_not_fail_open if {
d := with_data({"apy_z_score": 99, "risk_score": 10})
deny := vault_risk_rating.deny with data.params as default_params with data.wasm as d
count(deny) >= 2
not vault_risk_rating.allow with data.params as default_params with data.wasm as d
}
```
Run the tests:
```bash
opa test ./policy.rego ./policy_test.rego -v
```
Recommended coverage:
* **Happy path** — clean data allows.
* **Each deny rule** — one test per rule, asserting the expected reason is in `deny` and `allow` is false.
* **Param toggles** — a deny flag set to `false` does not deny.
* **Edge values** — `null`, empty, and out-of-range fields behave as intended.
* **No fail-open under multiple failures** — two deny conditions at once still denies.
* **Multiple policies** — test each policy with its own params and oracle fixture, then test the aggregate decision. There is no cross-policy `data.wasm` merge.
### Quick local checks with the CLI
The CLI uses the same Newton Regorus engine family as operator evaluation, including Newton's crypto and identity built-ins. Use it to evaluate a query and validate runtime syntax and built-in availability:
```bash
# Evaluate a query against policy + data + input files
newton-cli regorus eval \
-d policy.rego \
-d data.json \
-i intent.json \
--non-strict \
"data.vault_risk_rating.allow"
# Validate syntax / inspect the policy
newton-cli regorus parse policy.rego
newton-cli regorus lex policy.rego
```
:::note
Pass `--non-strict` for OPA-style non-strict evaluation. This does not add unsupported OPA built-ins or make the engine fully OPA-compatible. `-d` (data/policy) can be repeated; `-i` is the input (intent) file. See the [CLI Reference](/developers/reference/command-line-tool#regorus).
:::
## Test your WASM oracle
Run the compiled oracle in isolation to confirm it produces the data shape your Rego expects.
```bash
# Local execution in a sandbox with the Newton HTTP host
newton-cli --chain-id 11155111 policy-data simulate \
--wasm-file ./dist/policy.wasm \
--input-json '{"network":"mainnet","vaultAddress":"0x..."}'
```
For an oracle that needs secrets, test through the Gateway:
| Method | Secrets source | Ownership |
|--------|----------------|-----------|
| `newt_simulatePolicyData` | Provided inline by the caller | None required |
| `newt_simulatePolicyDataWithClient` | Read from stored, uploaded secrets | PolicyClient owner |
```bash
curl -X POST https://gateway.testnet.newton.xyz/rpc \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "newt_simulatePolicyData",
"params": {
"policy_address": "0xYourPolicy",
"chain_id": 11155111,
"wasm_args": "0x7b226e6574776f726b223a226d61696e6e6574227d"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}'
```
See [Deploying with CLI](/developers/guides/deploying-with-cli) for the current secrets workflow.
## Simulate the full policy set
After deploying policies and configuring `setPolicies`, call SDK `simulatePolicy` with `policyClient`, `chainId`, `intent`, and an ordered `wasmArgs` array. The gateway runs each policy's optional WASM and then Rego in-process. It reads params and entrypoints from the configured set; do not send inline Rego or `policy_data` lists.
```typescript
const simulation = await client.simulatePolicy({
policyClient,
chainId: 11155111,
intent,
wasmArgs: ['0x', '0x'], // Two pure-Rego policies
});
if (!simulation.success) throw new Error(simulation.error ?? 'Simulation failed');
console.log(simulation.policy_id, simulation.allowed);
console.table(simulation.policies);
```
For oracle-backed policies, replace each empty slot with that policy's hex-encoded oracle input. Use `newt_simulateTask` via raw RPC only when you already have oracle outputs; it skips WASM and resolves the current set, so it is not historical replay. See [RPC API](/developers/reference/rpc-api#newt_simulatetask).
## Policy-set validation checklist
Use two independent deterministic test policies, each with allow and deny fixtures. Keep the same client and record its ordered set before each run.
| Policy 0 | Policy 1 | Aggregate | Application state |
|---|---|---|---|
| Allow | Allow | Allow | Changes only after successful attested transaction |
| Allow | Deny | Deny | Unchanged |
| Deny | Allow | Deny | Unchanged |
| Deny | Deny | Deny | Unchanged |
* Check every input and result maps to its policy-set position; preserve repeated entries.
* Replace with a reordered set, then an identical set. Each successful replacement advances the revision and changes the ID. Old attestations must fail.
* Reject empty sets, more than 8 entries, unregistered policies, zero expiry, oversized params, and unauthorized updates on the pinned contract.
* Reject reused attestations and changes to sender, target, chain, value, calldata, or protected arguments. Verify expired responses and mismatched task snapshots fail.
* Read state before and after denial and failed validation. An API denial alone does not prove the contract rejects a deny attestation.
Record evidence separately:
| Level | Evidence |
|---|---|
| Local policy tests | Inputs, expected and actual Rego verdicts |
| Local contract tests | Configuration, intent binding, deny, stale response, and replay assertions |
| Gateway simulation | Endpoint, current set ID, per-policy results, aggregate verdict; no signatures |
| Live operator evaluation | Gateway task ID, frozen task, aggregate `allowed`, signed response |
| On-chain validation | Successful BLS validation or expected revert |
| Application execution | Transaction hash, receipt, and resulting application state |
Do not use wall-clock conditions for a deterministic tutorial. An allow decision applies to evaluation time; block-based expiration is not a promise that external conditions remain true until execution.
## Recommended workflow
1. Test each Rego policy locally with OPA and Newton Regorus.
2. Test optional WASM locally, then deploy each policy with its artifacts.
3. Configure the client's ordered set and upload required policy-scoped secrets.
4. Simulate with `newt_simulatePolicy` and cover every truth-table case.
5. Evaluate through live operators, then validate and execute an allowed transaction on-chain.
6. Verify denial, stale attestations, and replay cannot change application state.
## Next Steps
Diagnose failures after deployment
Simulate and test multi-oracle policies
Pre-launch verification steps
Rego language reference
# Privacy Flows \[Three modes of privacy-preserving data in Newton Protocol — identity, confidential, and ephemeral — with end-to-end integration examples]
Newton supports three distinct privacy data flows, each designed for a different use case. All three use HPKE encryption (X25519 + ChaCha20-Poly1305) and share the same `encrypted_data_refs` storage table, but they differ in who uploads data, how it's referenced, and which Rego namespace it appears in.
## At a Glance
| Flow | Who Uploads | Lifecycle | On-Chain Contract | Rego Namespace | SDK Method |
|------|------------|-----------|-------------------|----------------|------------|
| **Identity** | End user | Persistent, registered on-chain | `IdentityRegistry` | `newton.identity.*` | `registerIdentityData` |
| **Confidential** | Provider | Persistent, versioned, granted per client | `ConfidentialDataRegistry` | `newton.privacy.*` | `uploadConfidentialData` |
| **Ephemeral** | Client (per-task) | Single task only | None | `data.privacy.*` | Inline in `wasm_args` |
***
## 1. Identity Data (User-Uploaded)
For long-lived user data like KYC credentials, identity documents, or verifiable credentials. The user encrypts and uploads once, registers a reference on-chain, and operators decrypt it at task time for any policy that requires identity verification.
### Flow
```mermaid
sequenceDiagram
participant U as End User
participant G as Gateway
participant C as IdentityRegistry
participant O as Operator
U->>U: HPKE encrypt identity data
U->>G: newt_uploadIdentityEncrypted
G->>U: data_ref_id + gateway_signature
U->>C: registerIdentityData(domain, data_ref_id, gatewaySig)
Note over C: On-chain reference stored
Note over O: At task time...
O->>C: identityData(owner, domain) → data_ref_id
O->>G: Fetch encrypted blob by data_ref_id
O->>O: HPKE decrypt locally
O->>O: Inject into newton.identity.* Rego builtins
```
### Integration Steps
1. **Encrypt and upload** identity data to the gateway. The gateway stores the encrypted blob and returns a `data_ref_id` (content hash) plus an EIP-712 co-signature.
2. **Register on-chain** by calling `IdentityRegistry.registerIdentityData(domain, data_ref_id, gatewaySig, deadline)`. The gateway signature prevents impersonation.
3. **Link to a policy client** by calling `linkIdentityAsSignerAndUser(policyClient, clientUser, domain)` so the policy can look up the user's identity data at task time.
4. **Write a Rego policy** that uses `newton.identity.*` builtins:
```rego
package kyc_gate
default allow := false
allow if {
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(18)
newton.identity.kyc.address_in_countries(["US", "CA", "GB"])
}
```
### SDK Usage
```ts twoslash
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
import { newtonWalletClientActions, registerIdentityData, identityDomainHash, linkIdentityAsSignerAndUser } from '@newton-xyz/sdk'
// ---cut---
async function registerAndLink() {
const client = createWalletClient({
account: privateKeyToAccount('0x' + '1'.repeat(64) as `0x${string}`),
chain: sepolia,
transport: http(),
}).extend(newtonWalletClientActions({
apiKey: 'test-api-key',
policyContractAddress: '0x1234567890123456789012345678901234567890',
}))
// Step 1: Upload is handled by the newton-identity popup (not the SDK)
// Step 2: Register on-chain
await registerIdentityData(client, {
identityDomain: identityDomainHash('kyc'),
dataRefId: 'content-hash-from-upload',
gatewaySignature: '0x' + '0'.repeat(130) as `0x${string}`,
deadline: 1735689600n, // Unix timestamp as bigint
})
// Step 3: Link to policy client
await linkIdentityAsSignerAndUser(client, {
policyClient: '0x1234567890123456789012345678901234567890' as `0x${string}`,
identityDomains: [identityDomainHash('kyc')],
})
}
```
See [Verified Credentials](/developers/verified-credential/overview) for the full identity integration guide.
***
## 2. Confidential Data (Provider-Managed)
For provider-managed datasets like blacklists, allowlists, or sanctions lists. A provider encrypts and uploads the data, publishes a versioned reference on-chain, and grants access to specific policy clients. When a provider updates the data, they publish a new version — all granted clients automatically see the latest version.
### Flow
```mermaid
sequenceDiagram
participant P as Provider
participant G as Gateway
participant C as ConfidentialDataRegistry
participant O as Operator
P->>C: registerProvider()
P->>P: HPKE encrypt blacklist data
P->>G: newt_uploadConfidentialData
G->>P: data_ref_id (content hash)
P->>C: publishData(domain, data_ref_id)
P->>C: grantClient(domain, policyClient)
Note over O: At task time...
O->>C: getConfidentialData(policyClient, domain)
O->>G: Fetch encrypted blob by data_ref_id
O->>O: HPKE decrypt locally
O->>O: Inject into newton.privacy.* Rego builtins
```
### Integration Steps
1. **Register as a provider** by calling `ConfidentialDataRegistry.registerProvider()` (self-registration, no gatekeeping).
2. **Encrypt and upload** via the SDK:
```ts twoslash
import { uploadConfidentialData } from '@newton-xyz/sdk'
// ---cut---
async function upload() {
const result = await uploadConfidentialData(
11155111, // chainId
'your-api-key',
{
provider: '0x1234567890123456789012345678901234567890' as `0x${string}`,
domain: '0x6f389bcc6254c2c2ecf54a9060af8b4c2ab49cd62cc796cc0e1000c43635b53a' as `0x${string}`, // keccak256("newton.privacy.blacklist")
plaintext: {
addresses: ['0x1111111111111111111111111111111111111111', '0x2222222222222222222222222222222222222222'],
},
chainId: 11155111,
},
)
// result.data_ref_id → use this for publishData
}
```
3. **Publish on-chain** by calling `ConfidentialDataRegistry.publishData(domain, dataRefId)`. Each call increments the version.
4. **Grant access** per policy client: `ConfidentialDataRegistry.grantClient(domain, policyClientAddress)`.
5. **Write a Rego policy** using `newton.privacy.*` builtins. The policy's `policyParams` must include a `confidential_domain` field matching the domain hash:
```rego
package sanctions_check
default allow := false
# Deny if sender is on the provider's blacklist
allow if {
not newton.privacy.blacklist.contains(input.from)
}
```
### Access Control
Providers manage per-client access:
| Method | Description |
|--------|-------------|
| `grantClient(domain, policyClient)` | Grant a policy client access to a domain |
| `revokeClient(domain, policyClient)` | Revoke a specific client's access |
| `revokeGlobal(domain)` | Revoke all clients from a domain |
| `getProviders(policyClient, domain)` | List all providers that granted access |
| `hasGrant(provider, domain, policyClient)` | Check if a specific grant exists |
### Versioning
Each `publishData` call creates a new version. Operators always resolve the latest version via `getConfidentialData`. There is no explicit version pinning — all granted clients see the most recent data.
***
## 3. Ephemeral Privacy Data (Per-Task Inline)
For data that should only exist for a single task evaluation — no upload step, no on-chain registration. The client embeds encrypted data directly in the task request's `wasm_args`.
### How It Works
The following JSON illustrates one decoded oracle argument blob, not the top-level task request. In policy-set requests, encode each blob and retain its policy position in the `wasm_args` array. Verify ephemeral-envelope handling against the selected gateway release before using this legacy privacy convention.
The client places HPKE-encrypted data in a reserved `_newton` namespace:
```json
{
"base_symbol": "ETH",
"_newton": {
"privacy": [
{
"enc": "...",
"ciphertext": "...",
"policy_client": "0x...",
"chain_id": 11155111,
"recipient_pubkey": "..."
}
]
}
}
```
The gateway extracts the `_newton` object from `wasm_args` before passing the remaining fields to operators. Operators decrypt the embedded envelopes locally and inject the plaintext into `data.privacy.*` in the Rego context.
### When to Use
* One-time privacy data that doesn't need to be stored or reused
* Testing and development before committing to the persistent upload flow
* Data that changes every transaction (e.g., real-time portfolio snapshots)
### Differences from Persistent Flows
| Aspect | Ephemeral | Persistent (Identity/Confidential) |
|--------|-----------|-----------------------------------|
| Storage | None (inline in request) | Gateway database |
| On-chain | None | `IdentityRegistry` or `ConfidentialDataRegistry` |
| Reusability | Single task only | Referenced by any future task |
| Authorization | Part of task request | Dual-signature (identity) or provider grant (confidential) |
| Rego namespace | `data.privacy.*` | `newton.identity.*` or `newton.privacy.*` |
***
## Encryption Primer
All three flows use the same HPKE encryption scheme:
| Component | Algorithm |
|-----------|-----------|
| KEM | X25519-HKDF-SHA256 |
| KDF | HKDF-SHA256 |
| AEAD | ChaCha20-Poly1305 |
The gateway's HPKE public key is available via `newt_getPrivacyPublicKey` or through the SDK:
```ts twoslash
import { getPrivacyPublicKey } from '@newton-xyz/sdk'
// ---cut---
async function getKey() {
const { public_key } = await getPrivacyPublicKey(11155111, 'your-api-key')
}
```
Encryption is always client-side. The `SecureEnvelope` binds ciphertext to a specific `policy_client` and `chain_id` via AAD (additional authenticated data), preventing replay across different policy contexts.
***
## Rego Namespace Summary
| Namespace | Access Pattern | Data Source |
|-----------|---------------|-------------|
| `newton.identity.kyc.*` | Domain-specific builtins | `IdentityRegistry` (user identity) |
| `newton.identity.get(field)` | Generic field accessor | Any identity domain |
| `newton.privacy.blacklist.*` | Domain-specific builtins | `ConfidentialDataRegistry` (provider blacklists) |
| `newton.privacy.allowlist.*` | Domain-specific builtins | `ConfidentialDataRegistry` (provider allowlists) |
| `newton.privacy.get(field)` | Generic field accessor | Any confidential domain |
| `newton.time.*` | Date arithmetic | Reference dates in policy data |
See the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide) for the full builtin reference.
## Next Steps
HPKE encryption, threshold decryption, and the security model
Full identity data integration with the newton-identity popup
All Newton Rego extensions including privacy and time builtins
# zkTLS Twitter/X Example \[Generate a TLSNotary proof for a Twitter/X follower count and submit it to Newton for policy evaluation.]
This guide mirrors the repository tutorial pack for the zkTLS Twitter/X follower-count example. It is intended for developers who want to see how a browser-generated TLSNotary proof becomes a Newton task input.
:::info
The package in this example is `@newton-protocol/zktls-twitter-example`. It is an example SDK/demo surface for the zkTLS Twitter flow, not the production Newton SDK package.
:::
## What you build
The example flow:
1. Opens a browser demo that talks to the TLSNotary extension.
2. Creates an attester WebSocket session through the Newton sidecar.
3. Generates a proof for a Twitter/X follower-count response from `api.x.com`.
4. Stores the proof and receives a CID.
5. Submits a Newton task with `proofCid`, `wasmArgs`, and a transaction `intent`.
6. Evaluates whether the account meets the configured follower threshold.
## Prerequisites
* Node.js 20+
* pnpm 9+
* A Newton API key
* A running Newton gateway and zkTLS attester sidecar
* A TLSNotary-compatible browser extension bridge
* A deployed policy client address for the Twitter/X follower policy
:::info
This example sub-package is `"private": true` in `package.json` and is not published to npm. To use it, clone the SDK repo and build locally. Production integrations should depend on `@newton-xyz/sdk` instead.
:::
## Run the example locally
Clone the SDK repository and build the example workspace:
```bash
git clone https://github.com/newt-foundation/newton-sdk.git
cd newton-sdk/examples/zktls-twitter/sdk
pnpm install
pnpm build
pnpm test
cd ../demo
pnpm install
pnpm build
pnpm dev
```
Configure the demo with environment variables:
```bash
VITE_GATEWAY_URL=http://localhost:8080
VITE_SIDECAR_URL=http://localhost:7047
VITE_NEWTON_API_KEY=your_newton_api_key
VITE_POLICY_CLIENT=0xYourPolicyClient
VITE_INTENT_FROM=0xYourFromAddress
VITE_INTENT_TO=0xYourToAddress
```
SDK requests authenticate with:
```http
Authorization: Bearer your_newton_api_key
```
## Task payload shape
The demo submits a task with a shallow camelCase client shape. The SDK converts top-level gateway params to snake\_case while preserving the nested `intent` object in camelCase because the gateway accepts it.
```typescript
import { createNewtonSDK, encodeWasmArgs } from "@newton-protocol/zktls-twitter-example";
const sdk = createNewtonSDK({
gatewayUrl: "http://localhost:8080",
attesterUrl: "http://localhost:7047",
apiKey: process.env.NEWTON_API_KEY,
});
const result = await sdk.task.createTask({
policyClient: "0x1111111111111111111111111111111111111111",
intent: {
from: "0x2222222222222222222222222222222222222222",
to: "0x3333333333333333333333333333333333333333",
value: "0x0",
data: "0x",
chainId: "0xaa36a7",
functionSignature: "0x",
},
proofCid: "bafy...",
wasmArgs: encodeWasmArgs({
min_followers: 1000,
twitter_username: "newton_protocol",
}),
timeout: 60,
useTwoPhase: true,
});
```
## Proof retrieval and integrity
The example proof client retrieves proof bytes by CID and verifies that the returned bytes match the CID multihash before returning them to callers. The same verification runs after `store()`: the SDK re-derives the CID locally from the bytes it sent and rejects any gateway response whose CID does not match. This keeps the client boundary stable while proof storage is still IPFS-backed and prevents a malicious or misconfigured gateway from routing downstream `proofCid` submissions to content the client never authored.
Verification rejects unknown multihash algorithms — only sha2-256 (multicodec `0x12`) is accepted. CIDs using other hash functions throw before any byte comparison runs.
The planned backend migration moves proof persistence to gateway-owned Postgres. The public store/retrieve API should remain the handoff point for the demo even when the storage backend changes.
For the full set of client-side invariants (CID integrity, WebSocket lifecycle, bounded body reads, hex caps), see the [SDK Invariants](https://github.com/newt-foundation/newton-sdk/blob/main/docs/zktls-twitter/SDK_INVARIANTS.md) reference.
## Identity integration roadmap
Future iterations should bind this proof flow to Newton identity records:
* Upload encrypted identity data before proof submission.
* Include identity references alongside `proofCid` in policy-specific WASM args.
* Validate identity domain, proof freshness, and task chain ID together in the policy.
* Move address fields to checksum-validated branded address types when the SDK adopts them.
## More resources
* Repository tutorial pack: `docs/zktls-twitter/`
* Example SDK: `examples/zktls-twitter/sdk/`
* Browser demo: `examples/zktls-twitter/demo/`
* Config fixtures: `examples/zktls-twitter/configs/`
# Stablecoins & Payments \[Use Newton Protocol to enforce compliance policies for stablecoin transfers, payment networks, and tokenized asset transactions. Programmable authorization for VISA-like payment rails on Ethereum.]
Stablecoin issuers, payment processors, and tokenized asset platforms need transaction-level compliance without sacrificing speed or decentralization. Newton Protocol provides programmable policy enforcement that runs at the transaction layer — every transfer can be evaluated against configurable rules before execution.
## The Problem
Payment and stablecoin protocols face competing demands:
* **Regulatory compliance** — sanctions screening, jurisdiction checks, transfer limits
* **Fraud prevention** — velocity checks, anomaly detection, blocklist enforcement
* **Speed** — sub-second authorization decisions, not batch review
* **Decentralization** — no single point of failure or censorship
Traditional approaches force a choice: either centralized compliance (a server that can block transfers) or no compliance at all. Newton removes this tradeoff by decentralizing the compliance decision.
## How Newton Solves It
Newton evaluates every transaction against a [Rego policy](/developers/guides/writing-policies) before it executes on-chain. The evaluation is performed by a decentralized network of [EigenLayer operators](/developers/concepts/architecture), and the result is cryptographically attested via BLS signatures.
### Sanctions Screening
A policy can call an external sanctions API via a [WASM data oracle](/developers/guides/writing-data-oracles) and block transfers involving sanctioned addresses:
```rego
package newton
default allow = false
allow if {
not data.wasm.is_sanctioned_sender
not data.wasm.is_sanctioned_recipient
}
```
### Transfer Limits
Enforce per-transaction or rolling-window spend limits without modifying your token contract:
```rego
package newton
default allow = false
allow if {
input.intent.value <= data.params.max_transfer_amount
}
cap := data.params.max_transfer_amount if {
input.intent.value > data.params.max_transfer_amount
}
```
### Jurisdiction Gating
Use oracle data to restrict transfers based on sender/recipient jurisdiction, enabling compliant cross-border payments.
## Use Cases
| Scenario | Policy approach |
|----------|----------------|
| Stablecoin issuer compliance | Sanctions screening + jurisdiction checks on every mint/transfer |
| Payment network authorization | Per-transaction limits + velocity checks + merchant allowlists |
| RWA token transfers | Accredited investor verification + transfer restrictions |
| Cross-border remittance | Country-level policy rules + amount thresholds |
| Card-to-crypto on-ramp | KYC status verification + daily limits |
## Architecture for Payment Protocols
```mermaid
sequenceDiagram
participant User
participant PaymentContract as Payment Contract
participant Newton as Newton AVS
participant Oracle as Compliance Oracle
User->>PaymentContract: transfer(to, amount)
PaymentContract->>Newton: Evaluate policy
Newton->>Oracle: Fetch sanctions/KYC data
Oracle-->>Newton: Compliance result
Newton-->>PaymentContract: BLS attestation (allow/reject/cap)
PaymentContract->>PaymentContract: Execute or revert
```
The payment contract inherits from [NewtonPolicyClient](/developers/guides/smart-contract-integration), which validates the attestation before executing the transfer. No off-chain server sits in the critical path.
## Why Newton Over Centralized Compliance
| | Centralized server | Newton Protocol |
|---|---|---|
| Single point of failure | Yes | No — distributed operators |
| Censorship risk | Yes — operator can block arbitrarily | No — policy rules are transparent and auditable |
| Latency | Depends on server | Sub-second (parallel operator evaluation) |
| Auditability | Server logs (opaque) | On-chain attestations (verifiable) |
| Customization | Vendor-defined rules | Your own Rego policies |
## Get Started
Simulate a sanctions screening policy in 5 minutes
Author Rego rules for transfer limits and compliance checks
Add NewtonPolicyClient to your payment or token contract
# AI Agent Security \[Enforce transaction guardrails for autonomous AI agents using Newton Protocol. Prevent unauthorized spending, set per-action limits, and maintain human oversight for agent commerce on Ethereum.]
Autonomous AI agents that transact on-chain need guardrails. Without policy enforcement, a compromised or hallucinating agent can drain a wallet, interact with malicious contracts, or exceed its intended scope. Newton Protocol provides programmable, verifiable transaction authorization that runs before any agent-initiated transaction executes.
## The Problem
AI agents operating on-chain face unique risks:
* **Unbounded spending** — an agent with wallet access can send any amount to any address
* **Scope creep** — an agent authorized for one task (e.g., swap tokens) might attempt unrelated actions
* **No audit trail** — you cannot easily verify why an agent made a specific transaction
* **Prompt injection / manipulation** — adversarial inputs can cause agents to take unintended actions
Current approaches rely on either no guardrails (trusting the agent) or hardcoded allowlists in the wallet (inflexible and hard to update).
## How Newton Solves It
Newton evaluates every agent-initiated transaction against a policy before execution. The policy is a [Rego program](/developers/guides/writing-policies) that encodes your guardrails — what the agent can do, how much it can spend, and which contracts it can interact with.
### Per-Transaction Spending Limits
```rego
package newton
default allow = false
allow if {
input.intent.value <= data.params.max_agent_spend
}
cap := data.params.max_agent_spend if {
input.intent.value > data.params.max_agent_spend
}
```
### Contract Allowlists
Restrict the agent to interacting with approved contracts only:
```rego
package newton
default allow = false
allow if {
input.intent.to == data.params.allowed_contracts[_]
}
```
### Function-Level Restrictions
Limit which contract functions the agent can call:
```rego
package newton
default allow = false
allow if {
input.intent.functionSignature == data.params.allowed_functions[_]
input.intent.to == data.params.allowed_contracts[_]
}
```
### Time-Based and Rate Limiting
Use a [WASM data oracle](/developers/guides/writing-data-oracles) to track recent agent activity and enforce rate limits:
```rego
package newton
default allow = false
allow if {
data.wasm.transactions_last_hour < data.params.max_hourly_transactions
data.wasm.spend_last_hour < data.params.max_hourly_spend
}
```
## Architecture for Agent Wallets
```mermaid
sequenceDiagram
participant Agent as AI Agent
participant Wallet as Agent Wallet (PolicyClient)
participant Newton as Newton AVS
participant Chain as Blockchain
Agent->>Wallet: Execute transaction
Wallet->>Newton: Evaluate against agent policy
Newton-->>Wallet: BLS attestation (allow/reject/cap)
alt Allowed
Wallet->>Chain: Execute transaction
else Rejected
Wallet->>Agent: Transaction blocked by policy
end
```
The agent wallet inherits from [NewtonPolicyClient](/developers/guides/smart-contract-integration). Every transaction the agent attempts must pass policy evaluation. The agent itself never has direct access to execute arbitrary transactions.
## Policy Patterns for AI Agents
| Pattern | What it enforces |
|---------|-----------------|
| Spending cap | Maximum value per transaction or per time window |
| Contract allowlist | Only interact with approved contract addresses |
| Function allowlist | Only call specific functions (e.g., `swap`, not `transferOwnership`) |
| Rate limiting | Maximum transactions per hour/day |
| Human approval threshold | Transactions above a value require off-chain human confirmation |
| Time windows | Agent can only transact during defined hours |
| Destination restrictions | Can only send funds to pre-approved addresses |
## Why Newton for Agent Security
| Approach | Limitation | Newton advantage |
|----------|-----------|-----------------|
| Hardcoded wallet allowlists | Inflexible, requires redeployment to change | Policies are updatable via IPFS — no contract redeploy |
| Off-chain policy server | Single point of failure, trust assumption | Decentralized evaluation with cryptographic attestations |
| No guardrails | Agent can do anything | Every action requires policy approval |
| Multisig approval | Slow, blocks autonomous operation | Policies evaluate in sub-seconds, no human in the loop (unless the policy requires it) |
## Get Started
Simulate your first policy evaluation in 5 minutes
Author Rego rules for spending limits, allowlists, and rate limiting
Add NewtonPolicyClient to your agent's smart contract wallet
# Institutional DeFi \[Enable institutional crypto adoption with programmable compliance policies. Newton Protocol provides verifiable transaction authorization for DeFi protocols, fund managers, and regulated financial products on Ethereum.]
If you manage assets for an institution — fund, custodian, DAO, or regulated entity — you need compliance guardrails before deploying capital into DeFi. Newton Protocol provides programmable, verifiable policy enforcement at the transaction layer. You participate in DeFi while maintaining compliance and risk controls.
## The Problem
Institutions face barriers that retail users do not:
* **Regulatory requirements** — mandated sanctions screening, transaction monitoring, and reporting
* **Risk controls** — exposure limits, counterparty restrictions, approved protocol lists
* **Audit requirements** — verifiable proof that every transaction was evaluated against compliance rules
* **Operational security** — multi-party authorization for large transactions, time-locked operations
* **Fiduciary duty** — fund managers must demonstrate that trades comply with investment mandates
Most DeFi protocols offer no compliance layer. Institutions either avoid DeFi entirely or build expensive, centralized middleware that creates single points of failure.
## How Newton Solves It
Newton inserts a policy evaluation step into every transaction. The policy is defined in [Rego](/developers/guides/writing-policies) and evaluated by a decentralized network of [EigenLayer operators](/developers/concepts/architecture). The result is a BLS attestation that proves the transaction was evaluated and approved.
### Exposure Limits
```rego
package newton
default allow = false
allow if {
data.wasm.current_protocol_exposure + input.intent.value <= data.params.max_protocol_exposure
}
```
### Approved Protocol Lists
Restrict interactions to audited, approved DeFi protocols:
```rego
package newton
default allow = false
allow if {
input.intent.to == data.params.approved_protocols[_]
}
```
### Transaction-Level Compliance
Combine sanctions screening, jurisdiction checks, and risk limits in a single policy:
```rego
package newton
default allow = false
allow if {
not data.wasm.is_sanctioned
data.wasm.jurisdiction_allowed
input.intent.value <= data.params.single_transaction_limit
data.wasm.daily_volume + input.intent.value <= data.params.daily_limit
}
```
## Architecture for Institutional Wallets
```mermaid
sequenceDiagram
participant Trader as Trader / Portfolio Manager
participant Vault as Institutional Vault (PolicyClient)
participant Newton as Newton AVS
participant Oracle as Compliance Oracle
participant DeFi as DeFi Protocol
Trader->>Vault: Submit trade
Vault->>Newton: Evaluate policy
Newton->>Oracle: Fetch compliance data
Oracle-->>Newton: Sanctions + exposure data
Newton-->>Vault: BLS attestation
alt Compliant
Vault->>DeFi: Execute trade
else Non-compliant
Vault->>Trader: Trade rejected (with reason)
end
```
## Compliance Patterns
| Pattern | Institutional need |
|---------|-------------------|
| Sanctions screening | Regulatory mandate for all financial transactions |
| Exposure limits | Per-protocol, per-asset, and aggregate position limits |
| Approved protocol lists | Only interact with audited DeFi protocols |
| Daily/weekly volume caps | Risk management and regulatory reporting thresholds |
| Multi-party authorization | Large transactions require multiple signers |
| Time-locked operations | Withdrawals above threshold require a delay period |
| Investment mandate compliance | Fund trades must align with stated strategy |
| Counterparty restrictions | Restrict interactions to known, verified counterparties |
## Audit Trail
Every Newton policy evaluation produces an on-chain attestation — a BLS signature proving that the transaction was evaluated by the operator network and the result. This provides:
* **Verifiable compliance proof** for regulators and auditors
* **Immutable record** of every policy decision
* **Transparent rules** — policies are content-addressed on IPFS and can be audited independently
## Why Newton Over Centralized Compliance
| | Centralized middleware | Newton Protocol |
|---|---|---|
| Trust model | Trust the compliance vendor | Trust the math — cryptographic attestations |
| Availability | Single point of failure | Distributed EigenLayer operators |
| Auditability | Vendor-provided logs | On-chain attestations + IPFS-stored policies |
| Customization | Vendor-defined rules | Your own Rego policies |
| Latency | API round-trip | Sub-second (parallel evaluation) |
| Verifiability | "Trust us" | BLS proofs verifiable by anyone |
## Get Started
Simulate a compliance policy evaluation in 5 minutes
Author Rego rules for sanctions screening, exposure limits, and approved lists
Evaluate policies on encrypted data for confidential transactions
# Architecture \[Newton Protocol system architecture — policy definition, AVS operator evaluation, and on-chain verification layers. How EigenLayer operators evaluate and attest to policy decisions.]
Newton Protocol is a three-layer system that separates policy definition, policy evaluation, and policy enforcement. This modular architecture allows each layer to evolve independently while maintaining strong security guarantees.
## System Overview
```mermaid
flowchart TD
subgraph PolicyLayer[Policy Layer]
PR[Policy Registry]
PL[Policy Library]
PD[PolicyData Oracles]
end
subgraph ComputeLayer[Compute and Consensus Layer]
GW[Gateway]
OP1[Operator 1]
OP2[Operator 2]
OP3[Operator N]
AGG[Aggregator]
end
subgraph VerificationLayer[Verification and Execution Layer]
TM[TaskManager]
AV[AttestationValidator]
PC[PolicyClient]
end
User[Calling Address] --> GW
GW --> OP1 & OP2 & OP3
OP1 & OP2 & OP3 --> AGG
OP1 & OP2 & OP3 -.-> PR & PD
AGG --> User
User --> PC
PC --> AV
AV --> TM
```
## Policy Layer
The Policy Layer defines what rules exist and how they are configured.
| Component | Purpose |
|-----------|---------|
| **Policy Registry** | On-chain registry of all deployed policies, referenced by CID |
| **Policy Library** | Reusable policy templates (spend limits, sanctions checks, KYC gates) |
| **PolicyData Oracles** | WASM components that fetch external data at evaluation time |
Developers publish policies to the registry by:
1. Writing a Rego policy and WASM oracle
2. Uploading to IPFS via `newton-cli`
3. Deploying independent Policy contracts with optional WASM CIDs
Users configure policies by deploying a PolicyClient contract with specific parameters (thresholds, allowlists, expiration).
## Compute & Consensus Layer
The Compute Layer handles offchain policy evaluation by the Newton AVS operator network.
| Component | Purpose |
|-----------|---------|
| **Gateway** | JSON-RPC endpoint that receives tasks and routes them to operators |
| **Operators** | EigenLayer nodes that independently evaluate policies |
| **Aggregator** | Collects individual BLS signatures into a single consensus proof |
When a task is submitted:
1. The Gateway receives the intent and identifies the target policy
2. Available operators fetch PolicyData (run WASM oracles)
3. Each operator evaluates the Rego policy independently
4. Each operator produces a BLS signature over the result
5. The Aggregator collects signatures and produces a consensus proof once quorum is reached
## Verification & Execution Layer
The Verification Layer handles onchain proof verification and transaction execution.
| Component | Purpose |
|-----------|---------|
| **NewtonProverTaskManager** | Core task management — creates tasks, stores responses, manages challenge windows |
| **AttestationValidator** | Validates BLS aggregate signatures against the operator set |
| **PolicyClient** | Developer's smart contract that calls validation before executing transactions |
| **PolicyClientRegistry** | Tracks registered PolicyClient contracts |
## Key Smart Contracts
| Contract | Purpose |
|----------|---------|
| `NewtonProverTaskManager` | Task creation, response storage, challenge management |
| `NewtonPolicyFactory` | Creates and registers new policies |
| `PolicyClientRegistry` | Tracks registered PolicyClient contracts |
| `IdentityRegistry` | Maps identities for policy evaluation |
| `AttestationValidator` | Validates BLS attestation proofs on-chain |
See [Contract Addresses](/developers/reference/contract-addresses) for deployed addresses on each network.
## Contract Relationships
```mermaid
flowchart LR
Factory[NewtonPolicyFactory] -->|creates| Policy[NewtonPolicy]
Policy -->|optional WASM CID| PD[Oracle]
PC[PolicyClient] -->|references| Policy
PC -->|validates via| AV[AttestationValidator]
AV -->|reads| TM[TaskManager]
PCR[PolicyClientRegistry] -->|tracks| PC
```
## Data Flow
A complete evaluation cycle:
1. **Intent submitted** — caller sends intent + PolicyClient address to Gateway
2. **Task created** — Gateway creates a task pairing the intent with the policy
3. **Data fetched** — operators execute PolicyData WASM oracles
4. **Policy evaluated** — operators run Rego policy with intent + oracle data + params
5. **Signatures produced** — each operator signs the result with their BLS key
6. **Proof aggregated** — Aggregator combines signatures into consensus proof
7. **Proof returned** — caller receives the attestation
8. **On-chain verification** — PolicyClient validates the proof via AttestationValidator
9. **Transaction executes** — if valid, the guarded transaction proceeds
## Next Steps
How Newton protects sensitive data during evaluation
BLS aggregation, quorum thresholds, and the security model
## Policy-set evaluation
The client configures an ordered set with `setPolicies`. Task admission freezes its ID, revision, and specs. Operators evaluate every policy with its own params and optional oracle; the aggregate allows only when all policies allow. Input and response positions retain set order, including repeated policies. Owner replacement invalidates attestations for the previous set ID. See [Core Concepts](/developers/overview/core-concepts#policy-set).
# Privacy Layer \[Newton Protocol privacy architecture — HPKE encryption, secure envelopes, and confidential policy evaluation. Private transactions with verifiable compliance for institutional and enterprise use cases.]
Newton's privacy layer enables end users to submit encrypted sensitive data (PII, credentials, financial records) that operators threshold-decrypt during policy evaluation. The data never appears on-chain.
## Overview
Many policies require access to sensitive data — identity documents, API keys, compliance records, or proprietary parameters. Newton's privacy layer provides:
* **End-to-end encryption** of sensitive inputs using HPKE (X25519/ChaCha20-Poly1305)
* **Threshold decryption** — operators collectively decrypt data using t-of-n key shares; no single party ever holds the full decryption key
* **Zero on-chain exposure** — only hashes and commitments go on-chain, never plaintext data
* **Dual-signature authorization** — both the end user and the dApp must sign off before operators will decrypt anything
## How It Works
```mermaid
sequenceDiagram
participant U as End User
participant D as dApp
participant G as Gateway
participant O as Operators
rect rgb(230, 240, 255)
note over U,G: UPLOAD PHASE
U->>U: Encrypt with system's threshold HPKE public key
U->>U: Sign envelope with Ed25519
U->>D: Encrypted envelope + signature
D->>G: newt_uploadEncryptedData
G->>G: Verify Ed25519 signature
G->>D: data_ref_id (UUID)
end
rect rgb(230, 255, 230)
note over D,O: TASK CREATION + DECRYPTION
D->>G: newt_createTask (with data refs + dual signatures)
G->>G: Validate dual Ed25519 signatures
G->>O: Distribute encrypted envelopes
O->>O: Threshold decrypt (t-of-n share exchange)
O->>O: Reconstruct plaintext locally
end
rect rgb(255, 240, 230)
note over G,O: CONSENSUS
O->>O: Evaluate policy with decrypted data + BLS sign
O->>G: Signed response
G->>G: BLS aggregation
G->>D: Consensus proof
end
```
The integration is three steps:
1. **Encrypt** sensitive data on the client side using the system's threshold public key (via `newt_getPrivacyPublicKey`)
2. **Upload** the encrypted envelope to the Gateway, receiving a reference ID
3. **Create a task** that includes the reference ID — operators threshold-decrypt the data during evaluation
## HPKE Encryption
Newton uses the HPKE standard (RFC 9180) with the following cipher suite:
| Component | Algorithm |
|-----------|-----------|
| KEM | DHKEM(X25519, HKDF-SHA256) |
| KDF | HKDF-SHA256 |
| AEAD | ChaCha20-Poly1305 |
| Signing | Ed25519 |
HPKE Base mode encrypts data to the system's threshold X25519 public key. This key is produced via interactive DKG (distributed key generation) across the operator set — no single operator holds the full private key. Sender authentication is handled separately via Ed25519 signatures on the serialized envelope.
## SecureEnvelope Structure
Encrypted data is wrapped in a `SecureEnvelope`:
```json
{
"enc": "",
"ciphertext": "",
"policy_client": "0x",
"chain_id": 11155111,
"recipient_pubkey": ""
}
```
| Field | Description |
|-------|-------------|
| `enc` | 32-byte X25519 ephemeral public key for HPKE decapsulation (hex) |
| `ciphertext` | HPKE ciphertext including the ChaCha20-Poly1305 authentication tag (hex) |
| `policy_client` | 0x-prefixed EVM address of the target PolicyClient |
| `chain_id` | Chain ID for context binding |
| `recipient_pubkey` | System's Ed25519 public key (hex) |
**AAD construction:** The additional authenticated data is `keccak256(abi.encodePacked(policy_client_bytes, chain_id_be_bytes))`. This binds the ciphertext to a specific PolicyClient and chain — tampering with either field causes decryption to fail.
## Threshold Decryption
Newton distributes the HPKE private key across operators using interactive DKG (FROST DKG / Pedersen VSS). During task evaluation:
1. Each operator computes a **partial decryption share** from its DKG key share
2. Operators exchange shares via encrypted NATS messages
3. Each operator **reconstructs the plaintext locally** from t-of-n shares
4. The reconstructed plaintext is used for policy evaluation and BLS signing
The threshold keypair is cryptographically independent from operators' ECDSA and BLS keys — a compromised signing key does not yield a decryption share. The combined public key is stored on-chain and readable via a single contract call (`getThresholdPublicKey()`), or via the `newt_getPrivacyPublicKey` RPC method.
DKG ceremonies run only during operator set changes, not per-task.
## Dual-Signature Authorization
Privacy-enabled tasks require two Ed25519 signatures to authorize decryption:
1. **User signature** — the end user signs `keccak256(abi.encodePacked(policy_client, intent_hash, ref_id_1, ref_id_2, ...))` with their Ed25519 key
2. **App signature** — the dApp signs `keccak256(abi.encodePacked(policy_client, intent_hash, user_signature))` with its Ed25519 key
Both signatures must be valid before operators will decrypt any data. A stolen reference ID alone is not sufficient.
## RPC Methods
:::note
These RPC methods are under active development and not yet available in production. The API surface may change.
:::
### `newt_getPrivacyPublicKey`
Retrieves the system's threshold HPKE public key for encrypting data.
```json
{
"jsonrpc": "2.0",
"method": "newt_getPrivacyPublicKey",
"params": {},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
Returns:
```json
{
"jsonrpc": "2.0",
"result": {
"public_key": "",
"key_type": "x25519",
"encryption_suite": "HPKE-Base-X25519-HKDF-SHA256-ChaCha20Poly1305"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
### `newt_uploadEncryptedData`
Uploads an encrypted data reference for later use in privacy-enabled tasks.
```json
{
"jsonrpc": "2.0",
"method": "newt_uploadEncryptedData",
"params": {
"sender_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"policy_client": "0x0000000000000000000000000000000000000001",
"envelope": "{\"enc\":\"...\",\"ciphertext\":\"...\",\"policy_client\":\"0x...\",\"chain_id\":11155111,\"recipient_pubkey\":\"...\"}",
"signature": "0x<64-byte Ed25519 signature hex>",
"recipient_pubkey": "0x<32-byte Ed25519 public key hex>",
"ttl": 3600
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `sender_address` | Address | Yes | End user's EVM address (intent sender) |
| `policy_client` | Address | Yes | PolicyClient address this data is scoped to |
| `envelope` | String | Yes | Serialized `SecureEnvelope` JSON string |
| `signature` | String | Yes | Ed25519 signature over the envelope bytes (hex, 0x-prefixed) |
| `recipient_pubkey` | String | Yes | System's Ed25519 public key (hex, 0x-prefixed) |
| `ttl` | Number | No | Time-to-live in seconds (data expires after this duration) |
Returns:
```json
{
"jsonrpc": "2.0",
"result": {
"success": true,
"data_ref_id": "550e8400-e29b-41d4-a716-446655440000",
"error": null
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
## Using Privacy in Tasks
When creating a task that requires encrypted data, include the data references and authorization signatures in the `newt_createTask` request:
* `encrypted_data_refs` — array of UUID reference IDs from `newt_uploadEncryptedData`
* `user_signature` — end user's Ed25519 authorization signature
* `app_signature` — dApp's Ed25519 authorization signature
The Gateway validates both signatures and distributes the encrypted envelopes to operators, who threshold-decrypt the data and merge plaintext into `policyTaskData` for evaluation.
For stored secrets (API keys for PolicyData oracles), see [Encrypting Secrets](/developers/advanced/encrypting-secrets).
## Use Cases
| Use Case | What's Encrypted |
|----------|-----------------:|
| **KYC-gated DeFi** | Government ID, proof of address |
| **Compliance checking** | Financial records, tax documents |
| **Private credential verification** | API keys, OAuth tokens, signed attestations |
| **Institutional policy enforcement** | Internal risk scores, portfolio data |
| **Privacy-preserving allowlists** | Membership proofs, signed approvals |
## Security Properties
| Threat | Mitigation |
|--------|------------|
| Unauthorized decryption | Dual-signature authorization required before operators decrypt |
| Single point of trust | Threshold decryption — no single operator holds the full private key |
| Ciphertext tampering | AEAD (ChaCha20-Poly1305) detects any modification |
| Context rebinding | AAD binds ciphertext to specific `policy_client` + `chain_id` |
| Replay of encrypted data | UUID-based references; TTL expiration; authorization signatures include `intent_hash` |
| Key compromise | DKG key shares are cryptographically independent from ECDSA/BLS keys |
| Data persistence | TTL-based expiration with automatic cleanup |
## Next Steps
BLS aggregation and the economic security model
Store encrypted secrets for PolicyData oracles
# Consensus & Security \[Newton Protocol consensus mechanisms, BLS aggregation, and economic security model. How EigenLayer restaking secures policy evaluation for blockchain compliance and risk management.]
Newton Protocol uses BLS signature aggregation and EigenLayer restaked ETH to provide cryptographic and economic security for policy evaluations.
## Two-Phase Consensus (Prepare-Commit)
Newton uses two-phase consensus to handle data variance when operators independently fetch time-sensitive data (e.g., prices from external APIs). All operators end up signing the same message despite receiving slightly different values.
```mermaid
sequenceDiagram
participant C as Caller
participant G as Gateway
participant O as Operators
C->>G: Submit task
G->>O: Prepare: fetch policy data
O->>O: Fetch data independently (unsigned)
O->>G: Return raw data values
G->>G: Compute median consensus
G->>O: Commit: evaluate with consensus data
O->>O: Evaluate policy + BLS sign
O->>G: BLS signatures
G->>G: Aggregate signatures
G->>C: Consensus proof
```
1. **Prepare phase:** Gateway sends `newt_fetchPolicyData` to operators. Each operator fetches external data independently and returns **unsigned** responses.
2. **Consensus computation:** Gateway computes median values across all operator responses and verifies tolerance (default 10%).
3. **Commit phase:** Gateway broadcasts the canonical consensus data via `newt_evaluateAndSign`. Operators evaluate the policy using identical data and BLS-sign the result.
4. **Aggregation:** Gateway collects BLS signatures until quorum is reached and produces the aggregate proof.
## Median Consensus
For data oracles that return numeric values (e.g., price feeds), Newton uses **median consensus** with a configurable tolerance:
1. Each operator fetches data independently during the Prepare phase
2. The Gateway collects all responses and computes the median for each numeric field
3. If any operator's value exceeds the tolerance threshold from the median, consensus **fails** with `ToleranceExceeded`
4. If all values are within tolerance, the Gateway normalizes responses to use median values
```
Operator 1: price = 100.0
Operator 2: price = 102.0
Operator 3: price = 101.0
Median: 101.0
Tolerance: 10%
All within |value - median| / median <= 10% → Consensus value: 101.0
```
:::note
Values outside the tolerance cause a `ToleranceExceeded` error — operators are not silently excluded.
:::
## Configuration Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `two_phase_prepare_timeout_ms` | 30000 | Prepare phase timeout in milliseconds |
| `two_phase_commit_timeout_ms` | 15000 | Commit phase timeout in milliseconds |
| `two_phase_consensus_tolerance_pct` | 10.0 | Tolerance percentage for median consensus |
| `quorum_threshold_percentage` | 67 | Minimum percentage of operator stake required |
| `quorum_number` | `"00"` | Quorum numbers to use (hex bytes) |
## BLS Signature Aggregation
Newton uses the **alt\_bn128** curve (EIP-196/EIP-197) for BLS signature aggregation. This is the curve supported by Ethereum's pairing precompiled contracts, enabling efficient on-chain verification.
### How It Works
1. Each operator holds a BLS private key registered with the AVS
2. Operators sign the task response hash with their individual key
3. The Gateway aggregates individual signatures into a single aggregate signature
4. The on-chain verifier validates the aggregate signature against the registered operator set
### Two-Digest System
BLS aggregation requires all operators to sign the **exact same message**. However, each operator generates a unique ECDSA attestation. Newton solves this with two digest types:
| Digest Type | Used For | Attestations |
|-------------|----------|-------------|
| **Consensus Digest** | BLS signing and verification | Excluded (zeroed out) |
| **Full Digest** | Contract storage and challenge verification | Included |
Operators BLS-sign the consensus digest (which is identical across all operators). The full digest (with unique attestations) is stored on-chain for challenge verification.
## Security Model
### Economic Security
Newton's security is backed by EigenLayer restaked ETH:
* Operators must stake ETH (via EigenLayer restaking) to participate
* Malicious behavior (incorrect evaluations) can be proven and results in **slashing** of staked ETH
* The economic cost of attacking the network exceeds the potential gain
### Challenge Window
After an attestation is submitted on-chain, there is a challenge window during which the result can be disputed:
* Anyone can submit a challenge proving an operator's evaluation was incorrect
* If the challenge succeeds, the offending operator is slashed
* The attestation is marked as `SuccessfullyChallenged`
### Slashing Conditions
Operators can be slashed for:
| Violation | Description |
|-----------|-------------|
| **Incorrect evaluation** | Signing a result that provably contradicts the policy |
| **Equivocation** | Signing conflicting results for the same task |
### Privacy Guarantees
* Only hashes and commitments go on-chain — no PII or sensitive data
* PolicyData oracle secrets are encrypted with HPKE
* Intent details are visible to operators during evaluation but not stored permanently
## Error Handling
### Consensus Failures
| Error | Cause | Resolution |
|-------|-------|------------|
| `ToleranceExceeded` | Operator values differ by more than tolerance | Increase `consensus_tolerance_pct` or investigate data source |
| `NoResponses` | No operators responded in Prepare phase | Check operator health, increase timeout |
| `InsufficientQuorum` | Not enough operators for quorum | Check operator registration and stake thresholds |
### BLS Aggregation Failures
| Error | Cause | Resolution |
|-------|-------|------------|
| `DigestMismatch` | Operators signed different messages | Verify consensus computation |
| `InvalidSignature` | Operator signature verification failed | Check operator BLS key registration |
| `QuorumNotReached` | Insufficient stake for quorum | Lower threshold or wait for more operators |
## Next Steps
How Newton works across multiple chains
Full system architecture overview
# Multichain Support \[Newton Protocol cross-chain policy verification across Ethereum, L2s, and destination chains. Enforce compliance and authorization rules across multiple blockchain networks.]
Newton Protocol supports policy evaluation across multiple chains. The AVS operators run on Ethereum (the source chain), while PolicyClient contracts can be deployed on destination chains like Base and Base Sepolia.
## Source vs Destination Chains
| Chain Type | Role | Examples |
|------------|------|---------|
| **Source chain** | Where Newton AVS operators are registered and BLS keys are managed | Ethereum Mainnet, Ethereum Sepolia |
| **Destination chain** | Where PolicyClient contracts are deployed and transactions execute | Base, Base Sepolia, Ethereum Sepolia |
The source chain provides the security foundation (operator registrations, staking, slashing). Destination chains consume attestations via lightweight verifier contracts that use cached operator state.
## How It Works
```mermaid
flowchart LR
subgraph Source[Source Chain - Ethereum]
AVS[Newton AVS]
OP[Operators]
end
subgraph Dest[Destination Chain - Base]
PC[PolicyClient]
BV[BN254CertificateVerifier]
end
User[Caller] -->|submit task| AVS
AVS --> OP
OP -->|BN254 certificate| User
User -->|verify + execute| PC
PC --> BV
```
1. **Task submitted** — a caller submits a task to the Gateway with the intent's `chain_id` set to the destination chain
2. **Operators evaluate** — operators evaluate the policy using the source chain infrastructure
3. **BN254 certificate produced** — the BLS aggregate signature is encoded as a BN254 certificate for the destination chain
4. **Attestation verified on destination** — the PolicyClient on the destination chain validates the certificate using the `BN254CertificateVerifier`
## Destination Chain Contracts
Destination chains require four contracts to verify attestations:
| Contract | Purpose |
|----------|---------|
| `ECDSAOperatorTableUpdater` | Receives operator state (BLS keys, stake weights, Merkle roots) from the source chain |
| `BN254CertificateVerifier` | Verifies BLS certificates against cached operator state |
| `DestinationTaskResponseHandler` | Decodes BN254 certificates and validates task responses |
| `NewtonProverDestTaskManager` | Task lifecycle management (creation, response storage) |
### Operator State Synchronization
The operator set from the source chain must be synced to each destination chain:
1. A **Table Syncer** service reads operator state from Ethereum (BLS keys, stake weights)
2. It computes a `BN254OperatorSetInfo` containing `operatorInfoTreeRoot`, `numOperators`, `aggregatePubkey`, and `totalWeights`
3. It calls `confirmGlobalTableRoot()` and `updateOperatorTable()` on the destination chain's `ECDSAOperatorTableUpdater`
4. The `BN254CertificateVerifier` uses this cached state for certificate verification
:::warning
All operators must be synced in a single atomic update. Sequential calls to `updateOperatorTable()` overwrite `numOperators`, `aggregatePubkey`, and `operatorInfoTreeRoot` on each call, causing intermediate states where non-signer Merkle proofs fail validation (`InvalidOperatorIndex` errors). Always batch all operator updates into one transaction.
:::
## BN254 Certificates
Cross-chain attestations use BN254 elliptic curve certificates. BN254 (alt\_bn128) is the curve used by Ethereum's pairing precompiled contracts (EIP-196/EIP-197), enabling efficient on-chain verification on any EVM chain.
A `BN254Certificate` contains:
| Field | Description |
|-------|-------------|
| `referenceTimestamp` | Timestamp binding the certificate to a specific operator set snapshot |
| `messageHash` | Hash of the task response being attested |
| `signature` | BN254 G1 point — the aggregate BLS signature |
| `apk` | BN254 G2 point — the aggregate public key of signing operators |
| `nonSignerWitnesses` | Merkle proof-based witnesses for operators who did **not** sign |
The verifier validates certificates by:
1. Checking `referenceTimestamp` freshness against `maxStalenessPeriod`
2. Loading cached `BN254OperatorSetInfo` from the synced operator table
3. Verifying each non-signer's Merkle proof against `operatorInfoTreeRoot`
4. Subtracting non-signer stakes from totals to compute signer stake
5. Performing a BLS pairing check
6. Checking quorum thresholds are met
## Cross-Chain Challenge Flow
If an attestation on a destination chain is disputed, the challenge flows across both chains:
1. **Destination chain:** Challenger calls `raiseAndResolveChallenge()` — the attestation is invalidated, but no slashing occurs (destination chains have no `ServiceManager`)
2. **Source chain:** Challenger relays the same challenge to Ethereum — actual slashing occurs via the source chain's slashing mechanism
## Tasks on Destination Chains
When creating a task for a destination chain:
```typescript
const { result } = await walletClient.evaluateIntentDirect({
policyClient: '0x...', // PolicyClient on destination chain
wasmArgs: ['0x'], // One pure-Rego policy; align with the actual set
timeout: 30,
intent: {
from: '0x...',
to: '0x...',
value: '0x0',
data: '0x...',
chainId: 8453, // Base mainnet (destination chain)
functionSignature: '0x...',
},
});
```
The SDK automatically routes the task to the correct Gateway and encodes the attestation for the destination chain's verifier.
## Single-Chain vs Multi-Chain Mode
| Aspect | Single-Chain (Source) | Multi-Chain (Destination) |
|--------|----------------------|--------------------------|
| **Task Manager** | `NewtonProverTaskManager` | `NewtonProverDestTaskManager` |
| **Signature Verification** | Direct BLS via precompile | BN254 certificate verification |
| **Operator State** | Live queries to registries | Cached `BN254OperatorSetInfo` |
| **Challenge Location** | Same chain as task | Destination (invalidation) + source (slashing) |
| **Gas Efficiency** | Higher (BLS precompiles) | Lower (cached verification) |
## Supported Networks
| Chain | Chain ID | Role | Status |
|-------|----------|------|--------|
| Ethereum Sepolia | `11155111` | Source + Destination | Active |
| Base Sepolia | `84532` | Destination | Active |
| Base | `8453` | Destination | Active |
| Ethereum Mainnet | `1` | Source | Active |
The architecture supports deployment to any EVM-compatible chain as a destination chain.
## Next Steps
Full system architecture overview
Deployed contracts on all networks
# Newton Protocol SDK Reference
Official TypeScript SDK for the Newton Protocol. This page provides a complete reference for all exported methods, types, interfaces, and utilities in the `@newton-xyz/sdk` package.
:::info
The SDK integrates with [viem](https://viem.sh) by extending `PublicClient` and `WalletClient` instances with Newton-specific actions. Make sure you are familiar with viem basics before proceeding.
:::
***
## Policy-set requests
This reference targets SDK **1.2.2**. `submitEvaluationRequest`, `evaluateIntentDirect`, `submitIntentAndSubscribe`, and `simulatePolicy` require `wasmArgs: Hex[]`, positionally aligned with the client's ordered set. Pass `['0x']` for one pure-Rego policy and `['0x', '0x']` for two. A scalar or omitted value is not supported by these SDK methods.
```ts
interface PolicyConfig { policyParams: Hex; expireAfter: number }
interface PolicySpec { policy: Address; config: PolicyConfig }
```
Read `getPolicySetSnapshot()` through the exported `NewtonPolicyClientAbi` with viem `readContract` when you need identity, revision, and specs together. There is no SDK action with that name in v1.2.2. Preserve order and repeated entries in both tasks and responses. Individual oracle simulation methods still use scalar `wasmArgs`.
## Installation
:::code-group
```bash [npm]
npm install @newton-xyz/sdk
```
```bash [pnpm (recommended)]
pnpm add @newton-xyz/sdk
```
```bash [yarn]
yarn add @newton-xyz/sdk
```
:::
**Requirements:**
* Node.js >= 20
* Package manager: pnpm >= 9 (recommended)
**Dependencies:**
| Package | Version | Purpose |
| --------------- | ------- | ------------------------------ |
| `viem` | ^2.35.1 | Ethereum client library |
| `jose` | ^6.0.13 | JWT / cryptographic operations |
| `eventemitter3` | ^4.0.4 | Event handling |
***
## Overview
The SDK is organized into two main client extensions:
| Extension | Purpose | Client Type |
| --------------------------- | ------------------------------------------------------ | -------------- |
| `newtonPublicClientActions` | Read-only operations (query tasks, policy state) | `PublicClient` |
| `newtonWalletClientActions` | Write operations (submit evaluations, manage policies) | `WalletClient` |
For a step-by-step walkthrough of integrating these into your application, see the [Integration Guide](/developers/guides/integration-guide). For the underlying JSON-RPC methods the SDK calls, see the [RPC API Reference](/developers/reference/rpc-api).
***
## Setup & Initialization
:::info
Newton Protocol currently supports **Ethereum Sepolia** (chain ID `11155111`), **Base Sepolia** (chain ID `84532`), **Ethereum Mainnet** (chain ID `1`), and **Base** (chain ID `8453`). Pass the corresponding `chain` object from `viem/chains` when constructing your clients.
:::
### newtonPublicClientActions
Extends a viem `PublicClient` with Newton Protocol read methods.
```ts twoslash
import { createPublicClient, http } from 'viem';
import { sepolia, baseSepolia, base } from 'viem/chains';
import { newtonPublicClientActions } from '@newton-xyz/sdk';
// ---cut---
const publicClient = createPublicClient({
chain: sepolia, // or baseSepolia, base, mainnet
transport: http('https://eth-sepolia.g.alchemy.com/v2/demo'),
}).extend(
newtonPublicClientActions({
policyContractAddress: '0x1234567890123456789012345678901234567890', // optional
})
);
```
**Signature:**
```ts
function newtonPublicClientActions(
options?: {
policyContractAddress?: Address;
},
overrides?: SdkOverrides
): (publicClient: PublicClient) => NewtonPublicClientMethods;
```
**Parameters:**
| Parameter | Type | Required | Description |
| ------------------------------- | -------------- | -------- | ----------------------------------------------------- |
| `options.policyContractAddress` | `Address` | No | Scopes all policy read calls to this contract address |
| `overrides` | `SdkOverrides` | No | Override default SDK configuration |
***
### newtonWalletClientActions
Extends a viem `WalletClient` with Newton Protocol write methods.
```ts twoslash
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia, baseSepolia, base } from 'viem/chains';
import { newtonWalletClientActions } from '@newton-xyz/sdk';
// ---cut---
const walletClient = createWalletClient({
account: privateKeyToAccount('0x' + '1'.repeat(64) as `0x${string}`),
chain: sepolia, // or baseSepolia, base, mainnet
transport: http('https://eth-sepolia.g.alchemy.com/v2/demo'),
}).extend(
newtonWalletClientActions({
apiKey: 'test-api-key',
policyContractAddress: '0x1234567890123456789012345678901234567890', // optional
})
);
```
**Signature:**
```ts
function newtonWalletClientActions(
config: {
apiKey: string;
policyContractAddress?: Address;
},
overrides?: SdkOverrides
): (walletClient: WalletClient) => NewtonWalletClientMethods;
```
**Parameters:**
| Parameter | Type | Required | Description |
| ------------------------------ | -------------- | -------- | ---------------------------------------------------- |
| `config.apiKey` | `string` | Yes | Newton Protocol API key for gateway authentication |
| `config.policyContractAddress` | `Address` | No | Default policy contract address for write operations |
| `overrides` | `SdkOverrides` | No | Override default SDK configuration |
:::note
Your API key authenticates requests to the Newton Gateway. Keep it secret and never expose it in client-side code.
:::
***
### SdkOverrides
Optional configuration to override default SDK endpoints and contract addresses.
```ts
interface SdkOverrides {
gatewayApiUrl?: string;
taskManagerAddress?: Address;
attestationValidatorAddress?: Address;
newtonIdpUrl?: string;
identityRegistry?: Address;
}
```
| Field | Type | Description |
| ----------------------------- | --------- | -------------------------------------------- |
| `gatewayApiUrl` | `string` | Custom Newton Gateway API URL |
| `taskManagerAddress` | `Address` | Custom TaskManager contract address |
| `attestationValidatorAddress` | `Address` | Custom AttestationValidator contract address |
| `newtonIdpUrl` | `string` | Custom Newton identity provider URL |
| `identityRegistry` | `Address` | Custom IdentityRegistry contract address |
***
## Wallet Client Methods (Write)
These methods are available after extending a `WalletClient` with `newtonWalletClientActions`.
### submitEvaluationRequest
Submits an intent evaluation request to the Newton Protocol via the gateway. The task is created on-chain and operators evaluate the intent against the policy. Returns a `PendingTaskBuilder` that can be used to await the task response.
```ts
const { result, waitForTaskResponded } = await walletClient.submitEvaluationRequest({
policyClient: '0x...',
intent: {
from: '0x...',
to: '0x...',
value: '0x0',
data: '0x...',
chainId: '0xaa36a7', // Ethereum Sepolia (11155111)
functionSignature: '0x...',
},
wasmArgs: ['0x'], // One pure-Rego policy
timeout: 60,
});
console.log(result.taskId); // Hex
console.log(result.txHash); // Hex
// Optionally wait for the on-chain response
const response = await waitForTaskResponded({ timeoutMs: 120000 });
```
**Signature:**
```ts
submitEvaluationRequest(
args: SubmitEvaluationRequestParams
): Promise<{ result: { taskId: Hex; txHash: Hex } } & PendingTaskBuilder>
```
**Parameters:**
| Parameter | Type | Required | Description |
| -------------------------- | ------------------ | -------- | --------------------------------------------------------------------------- |
| `policyClient` | `Address` | Yes | The policy client contract address |
| `intent` | `IntentFromParams` | Yes | The intent to evaluate |
| `timeout` | `number` | Yes | Request timeout in seconds |
| `proofCid` | `string` | No | IPFS CID of a TLSNotary presentation proof for zkTLS-backed evaluation |
| `includeValidateCalldata` | `boolean` | No | Include ABI-encoded `validateAttestationDirect` calldata in the response |
**Returns:** An object containing:
* `result.taskId` (`Hex`) -- The unique task identifier
* `result.txHash` (`Hex`) -- The transaction hash
* `waitForTaskResponded({ timeoutMs? })` -- A method to await the on-chain attestation result
:::note
The `validate_calldata` and `operator_errors` fields are available on the raw `GatewayCreateTaskResult` returned by the `newt_createTask` RPC method (see the [RPC API Reference](/developers/reference/rpc-api)). These fields are not surfaced by the SDK's `submitEvaluationRequest` or `evaluateIntentDirect` wrappers — call the gateway RPC directly when you need them.
:::
***
### evaluateIntentDirect
Evaluates an intent directly through the gateway without waiting for on-chain task response confirmation. Results are intended for use with `validateAttestationDirect` on `NewtonPolicyClient` (`NewtonProverTaskManagerShared`).
```ts
const domain = await readEip712Domain(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 intent = {
from: account.address,
to: '0x...',
value: '0x0',
data: '0x',
chainId: 11155111,
functionSignature: '0x',
} as const;
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: {
...intent,
value: BigInt(intent.value),
chainId: BigInt(intent.chainId),
},
});
const { result } = await walletClient.evaluateIntentDirect({
policyClient,
intent,
intentSignature,
wasmArgs: ['0x', '0x'], // Two pure-Rego policies, in set order
timeout: 30,
});
console.log(result.allowed); // boolean
console.log(result.task); // Task object
console.log(result.taskResponse); // Raw task response
console.log(result.blsSignature); // BLS signature data
```
**Signature:**
```ts
evaluateIntentDirect(
args: SubmitEvaluationRequestParams
): Promise<{
result: {
allowed: boolean;
task: Task;
taskResponse: TaskResponse;
blsSignature: unknown;
};
}>
```
**Parameters:**
| Parameter | Type | Required | Description |
| -------------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `policyClient` | `Address` | Yes | The policy client contract address |
| `intent` | `IntentFromParams` | Yes | The intent to evaluate. `chainId` must be `11155111` (Ethereum Sepolia), `84532` (Base Sepolia), or `8453` (Base). |
| `intentSignature` | `Hex` | No | EIP-712 signature of the exact intent. Optional in the SDK type, but required by policies and identity flows that read `input.intent_signature`. |
| `timeout` | `number` | Yes | Request timeout in seconds |
**Returns:**
* `result.allowed` (`boolean`) -- Whether the intent was allowed by the policy
* `result.task` (`Task`) -- The full task object
* `result.taskResponse` -- The raw task response from the operator
* `result.blsSignature` -- The BLS aggregate signature
:::warning
Some policy clients and identity-backed flows require `intentSignature` even though it is optional in the base request type. If you omit it or pass `"0x"`, the gateway can fail before policy evaluation with `Failed to encode intent signature: 0x reason: expected exactly 65 bytes`.
:::
Read the EIP-712 domain from the policy client whenever possible. Dashboard-deployed clients expose EIP-5267 `eip712Domain()`, which binds the signature to the correct `name`, `version`, `chainId`, and `verifyingContract`.
```ts
import type { 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;
}
}
```
If you deploy a custom policy client that does not implement EIP-5267, hardcoded signing domains must match the contract's `EIP712(name, version)` constructor values and the deployed policy client address.
***
### submitIntentAndSubscribe
Submits a task via `newt_sendTask` and opens a WebSocket connection to receive the evaluation result. Results are intended for use with `validateAttestation` on `NewtonProverTaskManager` (the on-chain task verification path).
```ts
const { result, ws } = await walletClient.submitIntentAndSubscribe({
policyClient: '0x...',
intent: {
from: '0x...',
to: '0x...',
value: '0x0',
data: '0x...',
chainId: 11155111,
functionSignature: '0x...',
},
wasmArgs: ['0x', '0x'], // Two pure-Rego policies, in set order
timeout: 30,
});
console.log(result.task_id); // Hex task identifier
console.log(result.subscription_topic); // WebSocket topic
// Listen for the task response
ws.onmessage = (event) => {
const taskResponse = JSON.parse(event.data);
console.log(taskResponse);
};
```
**Signature:**
```ts
submitIntentAndSubscribe(
args: SubmitEvaluationRequestParams
): Promise<{
result: SubmitIntentResult;
ws: WebSocket;
}>
```
**Parameters:**
| Parameter | Type | Required | Description |
| -------------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `policyClient` | `Address` | Yes | The policy client contract address |
| `intent` | `IntentFromParams` | Yes | The intent to evaluate. `chainId` must be `11155111` (Ethereum Sepolia), `84532` (Base Sepolia), or `8453` (Base). |
| `timeout` | `number` | Yes | Request timeout in seconds |
**Returns:**
* `result.message` (`string`) -- Status message from the gateway
* `result.subscription_topic` (`string`) -- WebSocket topic to subscribe to for task updates
* `result.task_id` (`Hex`) -- 32-byte task identifier
* `result.timestamp` (`number`) -- Task creation timestamp
* `ws` (`WebSocket`) -- Active WebSocket connection subscribed to the task topic
**`SubmitIntentResult` type:**
```ts
interface SubmitIntentResult {
message: string;
subscription_topic: string;
task_id: Hex;
timestamp: number;
}
```
:::note
Use `submitIntentAndSubscribe` when you need on-chain task verification via `NewtonProverTaskManager.validateAttestation`. Use `evaluateIntentDirect` for the simpler direct validation path via `PolicyClient.validateAttestationDirect`.
:::
***
### simulateTask
:::warning
SDK v1.2.2's `simulateTask` wrapper still sends the legacy `policy_task_data` schema shown below. It is not compatible with the policy-set gateway at the pinned revision. Use `simulatePolicy` for full WASM + Rego simulation, or call [newt\_simulateTask](/developers/reference/rpc-api#newt_simulatetask) directly with the new schema for provided-output evaluation. Neither simulation returns a BLS attestation.
:::
```ts
const result = await walletClient.simulateTask({
intent: {
from: '0x...',
to: '0x...',
value: '0x0',
data: '0x...',
chainId: 11155111, // Ethereum Sepolia; use 84532 for Base Sepolia or 8453 for Base
functionSignature: '0x...',
},
policyTaskData: {
policyId: '0x...',
policyAddress: '0x...',
policy: '0x...',
policyData: [
{
wasmArgs: '0x',
data: '0x...',
expireBlock: 999999,
},
],
},
});
console.log(result.success);
console.log(result.result?.allow);
console.log(result.result?.reason);
console.log(result.error);
```
**Signature:**
```ts
simulateTask(args: SimulateTaskParams): Promise
```
***
### simulatePolicy
Simulates the configured policy set (`newt_simulatePolicy`). The gateway runs each policy's optional WASM and evaluates its Rego, returning per-policy results and an aggregate verdict. It requires deployed policies and a configured client. The pinned gateway checks client ownership, including pure-Rego sets; explicitly authorized admin keys have a read-only bypass.
```ts
const result = await walletClient.simulatePolicy({
policyClient: '0x...',
chainId: 11155111,
intent: {
from: '0x...', to: '0x...', value: '0x0', data: '0x...',
chainId: 11155111, functionSignature: '0x...',
},
wasmArgs: ['0x', '0x'], // One slot per policy, including pure-Rego entries
});
console.log(result.success, result.allowed, result.policy_id);
for (const policy of result.policies ?? []) {
console.log(policy.policy_index, policy.policy, policy.allowed, policy.result);
}
```
```ts
simulatePolicy(args: SimulatePolicyParams): Promise
```
`success` means evaluation completed; `allowed` means every policy allowed. Errors have `allowed: null`. This is an unsigned simulation, not an operator quorum or application transaction.
***
### simulatePolicyData
Simulates one Policy’s WASM. No ownership verification is required for caller-supplied secrets. In v1.2.2 the SDK types `secrets` as a string and forwards it unchanged, while the pinned gateway expects a SecureEnvelope JSON object. Use raw RPC for encrypted inline secrets until the wrapper matches; the no-secrets example can omit that field.
```ts
const result = await walletClient.simulatePolicyData({
policyAddress: '0x...',
wasmArgs: '0x...',
chainId: 11155111, // required
});
console.log(result.success);
console.log(result.policy_data?.specifier);
console.log(result.policy_data?.data);
```
**Signature:**
```ts
simulatePolicyData(
args: SimulatePolicyDataParams
): Promise
```
***
### simulatePolicyDataWithClient
Simulates one Policy’s WASM with stored secrets. Requires client ownership. SDK v1.2.2’s wrapper omits the required top-level `chain_id`; use [raw RPC](/developers/reference/rpc-api#newt_simulatepolicydatawithclient) with the deployment chain for the pinned gateway. The signature below documents the wrapper, not a compatible request.
```ts
const result = await walletClient.simulatePolicyDataWithClient({
policyAddress: '0x...',
policyClient: '0x...',
wasmArgs: '0x...',
});
console.log(result.success);
console.log(result.policy_data?.specifier);
```
**Signature:**
```ts
simulatePolicyDataWithClient(
args: SimulatePolicyDataWithClientParams
): Promise
```
***
### initialize
Initializes a Newton Policy contract with the given configuration. This is a one-time setup call.
The contract requires `keccak256` of the Rego policy bytes as `_policyCodeHash` so the on-chain hash matches the policy referenced by `policyCid`. Provide it one of two ways:
* `policyCodeHash` -- pre-computed hex hash (from `policy_cids.json` or another out-of-band source). Takes precedence when both are set.
* `policyBytes` -- the raw Rego policy bytes; the SDK computes `keccak256` for you. Useful when fetching from IPFS via `policyCid`, reading a local file, or any other transport.
```ts
const txHash = await walletClient.initialize({
factory: '0x...',
entrypoint: 'newton/policy/allow',
policyCid: 'bafyrei...',
schemaCid: 'bafyrei...',
policyData: ['0x...', '0x...'],
metadataCid: 'bafyrei...',
owner: '0x...',
policyBytes: await fetchPolicyBytes(policyCid),
});
```
**Signature:**
```ts
type InitializePolicyArgs = {
factory: Address;
entrypoint: string;
policyCid: string;
schemaCid: string;
policyData: Address[];
metadataCid: string;
owner: Address;
} & ({ policyCodeHash: Hex } | { policyBytes: Uint8Array });
initialize(args: InitializePolicyArgs): Promise<`0x${string}`>
```
**Parameters:**
| Parameter | Type | Required | Description |
| ---------------- | ------------ | ----------------- | ------------------------------------------------------------------------ |
| `factory` | `Address` | Yes | Address of the deploying factory contract |
| `entrypoint` | `string` | Yes | Policy entrypoint path (e.g., `"newton/policy/allow"`) |
| `policyCid` | `string` | Yes | Content identifier (CID) of the policy WASM |
| `schemaCid` | `string` | Yes | Content identifier (CID) of the policy schema |
| `policyData` | `Address[]` | Yes | Array of PolicyData contract addresses |
| `metadataCid` | `string` | Yes | Content identifier (CID) of the policy metadata |
| `owner` | `Address` | Yes | Owner address of the policy contract |
| `policyCodeHash` | `Hex` | One of these two | Pre-computed `keccak256` of the Rego policy bytes |
| `policyBytes` | `Uint8Array` | One of these two | Raw Rego policy bytes; SDK computes `keccak256(policyBytes)` for you |
**Returns:** `` `0x${string}` `` -- The transaction hash.
***
### renounceOwnership
Renounces ownership of the policy contract. After calling this, no one will be able to perform owner-restricted actions.
:::note
This action is irreversible. Once ownership is renounced, administrative operations on the policy contract can never be performed again.
:::
```ts
const txHash = await walletClient.renounceOwnership();
```
**Signature:**
```ts
renounceOwnership(): Promise<`0x${string}`>
```
***
### transferOwnership
Transfers ownership of the policy contract to a new address.
```ts
const txHash = await walletClient.transferOwnership({
newOwner: '0x...',
});
```
**Signature:**
```ts
transferOwnership(args: {
newOwner: Address;
}): Promise<`0x${string}`>
```
***
### Identity Methods
These methods interact with the Newton Identity Provider for verified credential flows. They open a popup window for user interaction.
#### connectIdentityWithNewton
Connects the user's identity with Newton via the identity provider popup.
```ts
await walletClient.connectIdentityWithNewton({
appWalletAddress: '0x...',
appClientAddress: '0x...',
});
```
**Signature:**
```ts
connectIdentityWithNewton(args: {
appWalletAddress: Address;
appClientAddress: Address;
}): Promise
```
***
#### registerUserData
Registers KYC user data through the identity provider popup.
```ts
await walletClient.registerUserData({
userData: {
status: 'approved',
selected_country_code: 'US',
address_subdivision: 'CA',
address_country_code: 'US',
birthdate: '1990-01-15',
expiration_date: '2030-01-15',
issue_date: '2020-01-15',
issuing_authority: 'US_DL',
},
appIdentityDomain: '0x...',
policyClient: '0xYourPolicyClientAddress',
});
```
**Signature:**
```ts
registerUserData(args: {
userData: KycUserData;
appIdentityDomain: Hex;
policyClient: Address;
}): Promise
```
***
#### linkApp
Links an application to the user's Newton identity.
```ts
await walletClient.linkApp({
appWalletAddress: '0x...',
appClientAddress: '0x...',
appIdentityDomain: '0x...',
});
```
**Signature:**
```ts
linkApp(args: {
appWalletAddress: Address;
appClientAddress: Address;
appIdentityDomain: Hex;
}): Promise
```
***
#### unlinkApp
Unlinks an application from the user's Newton identity.
```ts
await walletClient.unlinkApp({
appWalletAddress: '0x...',
appClientAddress: '0x...',
appIdentityDomain: '0x...',
});
```
**Signature:**
```ts
unlinkApp(args: {
appWalletAddress: Address;
appClientAddress: Address;
appIdentityDomain: Hex;
}): Promise
```
***
### Privacy Methods
These methods are available on the wallet client for privacy-preserving policy evaluation using client-side HPKE encryption. They are also exported as standalone functions from the package.
#### getPrivacyPublicKey
Fetches the Newton Gateway's X25519 HPKE public key for encrypting data.
```ts
const { public_key, key_type, encryption_suite } = await walletClient.getPrivacyPublicKey();
```
**Signature:**
```ts
getPrivacyPublicKey(): Promise
```
**Returns:**
* `public_key` (`string`) -- The gateway's X25519 public key (hex-encoded)
* `key_type` (`string`) -- Key type identifier
* `encryption_suite` (`string`) -- HPKE suite identifier
***
#### createSecureEnvelope
Creates an HPKE-encrypted envelope from plaintext. Runs entirely offline with zero network calls.
```ts
const signingKeyPair = walletClient.generateSigningKeyPair();
const { envelope, signature, senderPublicKey } = await walletClient.createSecureEnvelope(
{
plaintext: { secret: 'value' },
policyClient: '0x...',
chainId: 11155111,
recipientPublicKey: '',
},
signingKeyPair.privateKey // Uint8Array from generateSigningKeyPair()
);
```
**Signature:**
```ts
createSecureEnvelope(
args: CreateSecureEnvelopeParams,
signingKey: Uint8Array
): Promise
```
**Parameters:**
| Parameter | Type | Required | Description |
| ---------------------- | -------- | -------- | ---------------------------------------------- |
| `args.plaintext` | `Uint8Array \| string \| Record` | Yes | Data to encrypt (objects are JSON-stringified) |
| `args.policyClient` | `Address`| Yes | PolicyClient address (bound via AAD) |
| `args.chainId` | `number` | Yes | Chain ID (bound via AAD) |
| `args.recipientPublicKey` | `string` | Yes | Gateway's X25519 public key |
| `signingKey` | `Uint8Array` | Yes | Ed25519 private key seed (32 bytes) |
***
#### getSecretsPublicKey
Fetches the Newton Gateway's X25519 HPKE public key for encrypting WASM secrets. In threshold DKG mode, this returns a different key than `getPrivacyPublicKey`.
```ts
const { public_key, key_type, encryption_suite } = await walletClient.getSecretsPublicKey();
```
**Signature:**
```ts
getSecretsPublicKey(): Promise
```
**Returns:**
* `public_key` (`string`) -- The gateway's X25519 public key (hex-encoded)
* `key_type` (`string`) -- Key type identifier
* `encryption_suite` (`string`) -- HPKE suite identifier
***
#### uploadIdentityEncrypted
Uploads HPKE-encrypted identity data to the Newton Gateway. The caller must provide a pre-built SecureEnvelope (via `createSecureEnvelope`) and an EIP-712 signature of the envelope JSON from the identity owner. The gateway stores the envelope and returns a `data_ref_id` + gateway signature for the on-chain `registerIdentityData` call.
```ts
const { data_ref_id, gateway_signature, deadline } = await walletClient.uploadIdentityEncrypted({
identityOwner: '0x...',
identityOwnerSig: '0x...', // EIP-712 signature of the envelope JSON
envelope: JSON.stringify(envelope),
identityDomain: identityDomainHash('kyc'),
chainId: 11155111,
});
```
**Signature:**
```ts
uploadIdentityEncrypted(
args: UploadIdentityEncryptedParams
): Promise
```
**Returns:**
* `data_ref_id` (`string`) -- Content-hash data reference ID: `keccak256(envelope_json_bytes)`
* `gateway_signature` (`string`) -- Gateway EIP-712 signature for `registerIdentityData` on-chain call
* `deadline` (`number`) -- Signature expiration (unix timestamp)
***
#### getIdentityEncrypted
Fetches encrypted identity data by its content-hash reference ID. Used to resolve a `data_ref_id` (stored on-chain in IdentityRegistry) back to the encrypted data blob (stored off-chain in the gateway).
```ts
const { envelope, identity_domain, identity_owner } = await walletClient.getIdentityEncrypted({
dataRefId: 'abc123...',
});
```
**Signature:**
```ts
getIdentityEncrypted(
args: { dataRefId: string }
): Promise
```
**Returns:**
* `envelope` (`string`) -- The HPKE-encrypted identity data as a SecureEnvelope JSON string
* `identity_domain` (`string`) -- The identity domain this data was registered under
* `identity_owner` (`string`) -- The identity owner address
***
#### generateSigningKeyPair
Generates a random Ed25519 key pair for privacy authorization signatures.
```ts
const { privateKey, publicKey } = walletClient.generateSigningKeyPair();
// Both are 32-byte hex strings without 0x prefix
```
**Signature:**
```ts
generateSigningKeyPair(): Ed25519KeyPair
```
:::note
This is a **synchronous** method. Keys are generated locally and never sent to any server.
:::
***
#### storeEncryptedSecrets
Encrypts plaintext secrets client-side with HPKE and uploads the envelope to the gateway for a PolicyClient's Policy oracle. The gateway's X25519 public key is fetched automatically if not provided.
```ts
const { success } = await walletClient.storeEncryptedSecrets({
policyClient: '0x...',
policyAddress: '0x...',
plaintext: { API_KEY: 'sk-...', ENDPOINT: 'https://api.example.com' },
chainId: 11155111,
});
```
**Signature:**
```ts
storeEncryptedSecrets(
args: StoreEncryptedSecretsParams
): Promise
```
***
#### signPrivacyAuthorization
Computes dual Ed25519 signatures (user + app) for privacy-enabled task creation. Runs entirely offline.
```ts
const { userSignature, appSignature, userPublicKey, appPublicKey } =
walletClient.signPrivacyAuthorization({
policyClient: '0x...',
intentHash: '0x...',
encryptedDataRefs: ['ref-id-1', 'ref-id-2'],
userSigningKey: '',
appSigningKey: '',
});
```
**Signature:**
```ts
signPrivacyAuthorization(
args: SignPrivacyAuthorizationParams
): PrivacyAuthorizationResult
```
:::note
This is a **synchronous** method. Signatures are computed locally.
:::
***
### Webhook Methods
#### registerWebhook
Registers a webhook URL to receive task failure notifications. The gateway sends HMAC-SHA256 signed payloads to the registered URL when tasks fail.
```ts
const { success, message } = await walletClient.registerWebhook({
url: 'https://example.com/newton-webhook',
secret: 'my-hmac-secret',
timeoutSeconds: 10,
maxRetries: 3,
failureTypes: ['timeout', 'quorum_not_reached'],
});
```
**Signature:**
```ts
registerWebhook(args: RegisterWebhookParams): Promise
```
**Parameters:**
| Parameter | Type | Required | Description |
| --------------- | ------------------- | -------- | ----------------------------------------------------------------- |
| `url` | `string` | Yes | Webhook URL (must be HTTPS, or `http://localhost` for testing) |
| `secret` | `string` | No | HMAC secret for payload signing (recommended) |
| `timeoutSeconds`| `number` | No | Request timeout in seconds (default: 10) |
| `maxRetries` | `number` | No | Maximum retry attempts (default: 3) |
| `failureTypes` | `TaskFailureType[]` | No | Failure types to notify about (default: all) |
***
#### unregisterWebhook
Removes the registered webhook for the current API key.
```ts
const { success, message } = await walletClient.unregisterWebhook();
```
**Signature:**
```ts
unregisterWebhook(): Promise
```
***
### Identity Methods
These methods handle identity data registration and identity-to-PolicyClient linking via direct contract calls to the IdentityRegistry. They are also exported as standalone functions from the package.
#### registerIdentityData
Register an identity data reference on-chain. The identity owner (caller) submits a `data_ref_id` obtained from the gateway's `newt_uploadIdentityEncrypted` RPC, along with the gateway's co-signature and deadline.
```ts
await walletClient.registerIdentityData({
identityDomain: identityDomainHash('kyc'),
dataRefId: 'QmEncryptedDataRef...',
gatewaySignature: '0x...', // from newt_uploadIdentityEncrypted response
deadline: 1700000000n, // from newt_uploadIdentityEncrypted response
})
```
**Signature:**
```ts
registerIdentityData(args: RegisterIdentityDataParams): Promise
```
The contract verifies the gateway signature against `REGISTER_IDENTITY_TYPEHASH` and checks that the signer is a registered task generator via `isTaskGenerator()`. Overwrites are allowed — users can update their identity data reference by calling this again.
***
#### linkIdentityAsSignerAndUser
Link identity data when the caller is both the identity owner and the client user. The simplest linking flow — no counterparty signatures needed.
```ts
await walletClient.linkIdentityAsSignerAndUser({
policyClient: '0xPolicyClientAddress',
identityDomains: [identityDomainHash('kyc')],
});
```
**Signature:**
```ts
linkIdentityAsSignerAndUser(args: LinkIdentityAsSignerAndUserParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes (use `identityDomainHash()`) |
**Returns:** Transaction hash (`Hex`).
#### linkIdentityAsSigner
Link identity data as the identity owner (signer). Requires a counterparty EIP-712 signature from the client user.
```ts
await walletClient.linkIdentityAsSigner({
policyClient: '0xPolicyClientAddress',
identityDomains: [identityDomainHash('kyc')],
clientUser: '0xClientUserAddress',
clientUserSignature: '0x...',
clientUserNonce: 0n,
clientUserDeadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
});
```
**Signature:**
```ts
linkIdentityAsSigner(args: LinkIdentityAsSignerParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `clientUser` | `Address` | The client user's address |
| `clientUserSignature` | `Hex` | EIP-712 signature from the client user |
| `clientUserNonce` | `bigint` | Nonce for the client user's signature |
| `clientUserDeadline` | `bigint` | Deadline timestamp for the signature |
**Returns:** Transaction hash (`Hex`).
#### linkIdentityAsUser
Link identity data as the client user. Requires a counterparty EIP-712 signature from the identity owner.
```ts
await walletClient.linkIdentityAsUser({
identityOwner: '0xIdentityOwnerAddress',
policyClient: '0xPolicyClientAddress',
identityDomains: [identityDomainHash('kyc')],
identityOwnerSignature: '0x...',
identityOwnerNonce: 0n,
identityOwnerDeadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
});
```
**Signature:**
```ts
linkIdentityAsUser(args: LinkIdentityAsUserParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `identityOwner` | `Address` | The identity owner's address |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `identityOwnerSignature` | `Hex` | EIP-712 signature from the identity owner |
| `identityOwnerNonce` | `bigint` | Nonce for the identity owner's signature |
| `identityOwnerDeadline` | `bigint` | Deadline timestamp for the signature |
**Returns:** Transaction hash (`Hex`).
#### linkIdentity
Link identity data as a 3rd party with EIP-712 signatures from both the identity owner and the client user.
**Signature:**
```ts
linkIdentity(args: LinkIdentityParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `identityOwner` | `Address` | The identity owner's address |
| `clientUser` | `Address` | The client user's address |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `identityOwnerSignature` | `Hex` | EIP-712 signature from the identity owner |
| `identityOwnerNonce` | `bigint` | Nonce for the identity owner's signature |
| `identityOwnerDeadline` | `bigint` | Deadline timestamp for the owner signature |
| `clientUserSignature` | `Hex` | EIP-712 signature from the client user |
| `clientUserNonce` | `bigint` | Nonce for the client user's signature |
| `clientUserDeadline` | `bigint` | Deadline timestamp for the user signature |
**Returns:** Transaction hash (`Hex`).
#### unlinkIdentityAsSigner
Unlink identity data as the identity owner (signer). Only the identity owner who created the link can call this.
```ts
await walletClient.unlinkIdentityAsSigner({
clientUser: '0xClientUserAddress',
policyClient: '0xPolicyClientAddress',
identityDomains: [identityDomainHash('kyc')],
});
```
**Signature:**
```ts
unlinkIdentityAsSigner(args: UnlinkIdentityAsSignerParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `clientUser` | `Address` | The client user whose link to remove |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes to unlink |
**Returns:** Transaction hash (`Hex`).
#### unlinkIdentityAsUser
Unlink identity data as the client user. Allows users to revoke links to their own account.
```ts
await walletClient.unlinkIdentityAsUser({
policyClient: '0xPolicyClientAddress',
identityDomains: [identityDomainHash('kyc')],
});
```
**Signature:**
```ts
unlinkIdentityAsUser(args: UnlinkIdentityAsUserParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes to unlink |
**Returns:** Transaction hash (`Hex`).
***
## Public Client Methods (Read)
These methods are available after extending a `PublicClient` with `newtonPublicClientActions`.
### Task Methods
#### waitForTaskResponded
Polls the on-chain `TaskManager` contract for a `TaskResponded` event for the given task ID.
```ts
const result = await publicClient.waitForTaskResponded({
taskId: '0x...',
timeoutMs: 120000,
});
console.log(result.taskResponse.allowed);
console.log(result.attestation.expiration);
```
**Signature:**
```ts
waitForTaskResponded(args: {
taskId: TaskId;
timeoutMs?: number;
abortSignal?: AbortSignal;
}): Promise
```
**Parameters:**
| Parameter | Type | Required | Description |
| ------------- | ---------------- | -------- | --------------------------------- |
| `taskId` | `TaskId` (`Hex`) | Yes | The task identifier to monitor |
| `timeoutMs` | `number` | No | Maximum wait time in milliseconds |
| `abortSignal` | `AbortSignal` | No | Signal to abort the polling |
***
#### getTaskStatus
Queries the current status of a task from the on-chain contracts.
```ts
const status = await publicClient.getTaskStatus({ taskId: '0x...' });
// status: 'Created' | 'Responded' | 'AttestationSpent'
// | 'AttestationExpired' | 'SuccessfullyChallenged'
```
**Signature:**
```ts
getTaskStatus(args: {
taskId: TaskId;
}): Promise
```
***
#### getTaskResponseHash
Retrieves the response hash for a given task from the on-chain contract.
```ts
const hash = await publicClient.getTaskResponseHash({ taskId: '0x...' });
// hash: '0x...' | null
```
**Signature:**
```ts
getTaskResponseHash(args: {
taskId: TaskId;
}): Promise
```
***
### Policy Methods
#### getPolicyId
Returns the policy ID associated with a given client address.
```ts
const policyId = await publicClient.getPolicyId({ client: '0x...' });
```
**Signature:**
```ts
getPolicyId(args: { client: Address }): Promise<`0x${string}`>
```
***
#### getPolicyConfig
Returns the full policy configuration for a given policy ID, including parameters and expiration settings.
```ts
const config = await publicClient.getPolicyConfig({ policyId: '0x...' });
console.log(config.policyParams);
console.log(config.policyParamsHex);
console.log(config.expireAfter);
```
**Signature:**
```ts
getPolicyConfig(args: {
policyId: `0x${string}`;
}): Promise<{
policyParams: string | object;
policyParamsHex: `0x${string}`;
expireAfter: number;
}>
```
***
#### getPolicyCid
Returns the content identifier (CID) of the policy WASM stored in the contract.
```ts
const cid = await publicClient.getPolicyCid();
```
**Signature:**
```ts
getPolicyCid(): Promise
```
***
#### getSchemaCid
Returns the content identifier (CID) of the policy schema.
```ts
const cid = await publicClient.getSchemaCid();
```
**Signature:**
```ts
getSchemaCid(): Promise
```
***
#### getMetadataCid
Returns the content identifier (CID) of the policy metadata.
```ts
const cid = await publicClient.getMetadataCid();
```
**Signature:**
```ts
getMetadataCid(): Promise
```
***
#### getEntrypoint
Returns the policy entrypoint string (e.g., `"newton/policy/allow"`).
```ts
const entrypoint = await publicClient.getEntrypoint();
```
**Signature:**
```ts
getEntrypoint(): Promise
```
***
#### getPolicyData
Returns the array of PolicyData contract addresses associated with the policy.
```ts
const addresses = await publicClient.getPolicyData();
```
**Signature:**
```ts
getPolicyData(): Promise
```
***
#### isPolicyVerified
Returns whether the policy contract has been verified.
```ts
const verified = await publicClient.isPolicyVerified();
```
**Signature:**
```ts
isPolicyVerified(): Promise
```
***
#### precomputePolicyId
Synchronously computes a policy ID from its constituent parts without making an on-chain call.
```ts
const policyId = publicClient.precomputePolicyId({
policyContract: '0x...',
policyData: ['0x...'],
params: {
admin: '0x...',
allowed_actions: {},
token_whitelist: {},
},
client: '0x...',
policyUri: 'ipfs://...',
schemaUri: 'ipfs://...',
entrypoint: 'newton/policy/allow',
expireAfter: 3600,
});
```
**Signature:**
```ts
precomputePolicyId(args: {
policyContract: Address;
policyData: Address[];
params: Record;
client: Address;
policyUri: string;
schemaUri: string;
entrypoint: string;
expireAfter?: number;
blockTimestamp?: bigint;
}): `0x${string}`
```
:::note
This is a **synchronous** method. It does not make any network or contract calls.
:::
***
#### Additional Policy Contract Readers
The following methods read individual fields from the policy contract. They all require a `policyContractAddress` to have been set during initialization (or passed via overrides).
| Method | Return Type | Description |
| ------------------------------------ | --------------------------- | ------------------------------------------------------ |
| `owner()` | `Promise` | Returns the owner address of the policy contract |
| `factory()` | `Promise` | Returns the factory address that deployed the contract |
| `entrypoint()` | `Promise` | Returns the policy entrypoint path |
| `clientToPolicyId({ client })` | `Promise<\`0x$string\`>\` | Maps a client address to its policy ID |
| `policyData({ index })` | `Promise` | Returns the PolicyData contract address at the given index |
| `policyCid()` | `Promise` | Returns the policy CID |
| `schemaCid()` | `Promise` | Returns the schema CID |
| `metadataCid()` | `Promise` | Returns the metadata CID |
| `supportsInterface({ interfaceId })` | `Promise` | EIP-165 interface support check |
***
## Privacy Methods
Client-side HPKE encryption for privacy-preserving policy evaluation. These methods are available as wallet client actions and as standalone exports.
:::info
The privacy module uses X25519 KEM + HKDF-SHA256 + ChaCha20-Poly1305 (RFC 9180, Base mode) and is compatible with the Rust gateway implementation.
:::
### `createSecureEnvelope`
Encrypt plaintext into a SecureEnvelope using HPKE. This is a pure offline function — zero network calls. The ephemeral HPKE key is generated internally and zeroed after use.
**Standalone signature:**
```ts
createSecureEnvelope(
params: CreateSecureEnvelopeParams,
signingKey: Uint8Array
): Promise
```
**Wallet client signature:**
```ts
client.createSecureEnvelope(params: CreateSecureEnvelopeParams, signingKey: Uint8Array): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `params.plaintext` | `Uint8Array \| string \| Record` | Data to encrypt |
| `params.policyClient` | `Address` | Policy client address this data is scoped to |
| `params.chainId` | `number` | Chain ID for AAD context binding |
| `params.recipientPublicKey` | `string` | Gateway's X25519 public key (hex, no 0x prefix) |
| `signingKey` | `Uint8Array` | Ed25519 private key seed (32 bytes). Caller owns the buffer lifecycle. |
**Returns:** `SecureEnvelopeResult` containing the encrypted envelope, Ed25519 signature, and sender public key.
:::warning
The caller is responsible for zeroing the `signingKey` buffer when done. The function copies the key internally and zeroes the copy after signing, but the original buffer remains in the caller's control.
:::
***
### `getPrivacyPublicKey`
Fetch the gateway's X25519 HPKE public key. Call this once and cache the result — the key only changes on gateway restart or key rotation.
**Standalone signature:**
```ts
getPrivacyPublicKey(
chainId: number,
apiKey: string,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.getPrivacyPublicKey(): Promise
```
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `public_key` | `string` | X25519 public key (hex, no 0x prefix) |
| `key_type` | `string` | Always `"x25519"` |
| `encryption_suite` | `string` | Encryption suite identifier |
***
### `getSecretsPublicKey`
Fetch the gateway's X25519 HPKE public key for WASM secrets encryption. In threshold DKG mode, this returns a different key than `getPrivacyPublicKey`. Use this key when encrypting secrets via `storeEncryptedSecrets`.
**Standalone signature:**
```ts
getSecretsPublicKey(
chainId: number,
apiKey: string,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.getSecretsPublicKey(): Promise
```
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `public_key` | `string` | X25519 public key (hex, no 0x prefix) |
| `key_type` | `string` | Always `"x25519"` |
| `encryption_suite` | `string` | Encryption suite identifier |
***
### `uploadIdentityEncrypted`
Upload HPKE-encrypted identity data to the gateway. The caller must provide a pre-built SecureEnvelope (via `createSecureEnvelope`) and an EIP-712 signature of the envelope JSON from the identity owner. The gateway stores the envelope and returns a `data_ref_id` + gateway signature for the on-chain `registerIdentityData` call.
**Standalone signature:**
```ts
uploadIdentityEncrypted(
chainId: number,
apiKey: string,
params: UploadIdentityEncryptedParams,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.uploadIdentityEncrypted(params: UploadIdentityEncryptedParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `params.identityOwner` | `Address` | EVM address of the identity owner |
| `params.identityOwnerSig` | `string` | EIP-712 signature of the SecureEnvelope JSON by the identity owner |
| `params.envelope` | `string` | JSON-serialized SecureEnvelope (from `createSecureEnvelope`) |
| `params.identityDomain` | `` `0x${string}` `` | Identity domain as 0x-prefixed bytes32 hex (e.g., `keccak256("kyc")`) |
| `params.chainId` | `number` | Chain ID the identity registry lives on |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `data_ref_id` | `string` | Content-hash data reference ID: `keccak256(envelope_json_bytes)` |
| `gateway_signature` | `string` | Gateway EIP-712 signature for `registerIdentityData` on-chain call |
| `deadline` | `number` | Signature expiration (unix timestamp) |
***
### `getIdentityEncrypted`
Fetch encrypted identity data by its content-hash reference ID. Used to resolve a `data_ref_id` (stored on-chain in IdentityRegistry) back to the encrypted data blob (stored off-chain in the gateway).
**Standalone signature:**
```ts
getIdentityEncrypted(
chainId: number,
apiKey: string,
dataRefId: string,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.getIdentityEncrypted(args: { dataRefId: string }): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `dataRefId` | `string` | Content-hash data reference ID (keccak256 of the encrypted data) |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `envelope` | `string` | HPKE-encrypted identity data as a SecureEnvelope JSON string |
| `identity_domain` | `string` | Identity domain this data was registered under |
| `identity_owner` | `string` | Identity owner address |
***
### `uploadConfidentialData`
Encrypt confidential data (blacklists, allowlists, sanctions lists, etc.) client-side with HPKE and upload the envelope to the gateway. The gateway stores it and returns a content-hash `data_ref_id`. The provider then calls `ConfidentialDataRegistry.publishData(domain, dataRefId)` on-chain.
The gateway validates provider registration via on-chain lookup — no Ed25519 signing key required.
**Standalone signature:**
```ts
uploadConfidentialData(
chainId: number,
apiKey: string,
params: UploadConfidentialDataParams,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.uploadConfidentialData(params: UploadConfidentialDataParams): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `params.provider` | `string` | Provider address (must be registered on-chain with ConfidentialDataRegistry) |
| `params.domain` | `string` | Confidential domain as 0x-prefixed bytes32 hex (e.g., `keccak256("newton.privacy.blacklist")`) |
| `params.plaintext` | `Uint8Array \| string \| Record` | Data to encrypt and upload |
| `params.chainId` | `number` | Chain ID for AAD context binding |
| `params.recipientPublicKey` | `string?` | Gateway's X25519 key (hex, no 0x prefix). If omitted, fetched via RPC. |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `data_ref_id` | `string` | Content-hash data reference ID |
***
### `getConfidentialData`
Retrieve an HPKE-encrypted confidential data envelope by its data reference ID. The caller is responsible for decryption using their HPKE private key.
**Standalone signature:**
```ts
getConfidentialData(
chainId: number,
apiKey: string,
dataRefId: string,
gatewayApiUrlOverride?: string
): Promise
```
**Wallet client signature:**
```ts
client.getConfidentialData(args: { dataRefId: string }): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `dataRefId` | `string` | Content-hash data reference ID returned from `uploadConfidentialData` |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `envelope` | `string` | JSON-serialized HPKE SecureEnvelope |
| `domain` | `string` | Confidential domain as 0x-prefixed bytes32 hex |
| `provider` | `string` | Provider address |
***
## Identity Methods
EIP-712 signed identity data submission to the on-chain IdentityRegistry. These methods are available as wallet client actions and as standalone exports.
:::info
The identity module uses a single `EncryptedIdentityData { string data }` EIP-712 struct across all identity domains. The `identity_domain` field (bytes32 hash of the domain name) tells the gateway how to interpret the encrypted blob.
:::
### `identityDomainHash`
Compute the bytes32 identity domain hash from a human-readable domain name. Matches the Rust convention: `keccak256(toBytes(domainName))`.
**Signature:**
```ts
identityDomainHash(domainName: string): Hex
```
**Example:**
```ts twoslash
import { identityDomainHash } from '@newton-xyz/sdk';
const kycDomain = identityDomainHash('kyc');
// Returns keccak256 of "kyc" as 0x-prefixed bytes32
```
### `linkIdentityAsSignerAndUser`
Link identity data when the caller is both the identity owner and the client user. Calls the IdentityRegistry contract directly via `writeContract`.
**Standalone signature:**
```ts
linkIdentityAsSignerAndUser(
walletClient: WalletClient,
params: LinkIdentityAsSignerAndUserParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes (use `identityDomainHash()`) |
**Returns:** Transaction hash (`Hex`).
### `linkIdentityAsSigner`
Link identity data as the identity owner (signer). Requires a counterparty EIP-712 signature from the client user.
**Standalone signature:**
```ts
linkIdentityAsSigner(
walletClient: WalletClient,
params: LinkIdentityAsSignerParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `clientUser` | `Address` | The client user's address |
| `clientUserSignature` | `Hex` | EIP-712 signature from the client user |
| `clientUserNonce` | `bigint` | Nonce for the client user's signature |
| `clientUserDeadline` | `bigint` | Deadline timestamp for the signature |
**Returns:** Transaction hash (`Hex`).
### `linkIdentityAsUser`
Link identity data as the client user. Requires a counterparty EIP-712 signature from the identity owner.
**Standalone signature:**
```ts
linkIdentityAsUser(
walletClient: WalletClient,
params: LinkIdentityAsUserParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `identityOwner` | `Address` | The identity owner's address |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `identityOwnerSignature` | `Hex` | EIP-712 signature from the identity owner |
| `identityOwnerNonce` | `bigint` | Nonce for the identity owner's signature |
| `identityOwnerDeadline` | `bigint` | Deadline timestamp for the signature |
**Returns:** Transaction hash (`Hex`).
### `linkIdentity`
Link identity data as a 3rd party with EIP-712 signatures from both the identity owner and the client user.
**Standalone signature:**
```ts
linkIdentity(
walletClient: WalletClient,
params: LinkIdentityParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `identityOwner` | `Address` | The identity owner's address |
| `clientUser` | `Address` | The client user's address |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes |
| `identityOwnerSignature` | `Hex` | EIP-712 signature from the identity owner |
| `identityOwnerNonce` | `bigint` | Nonce for the identity owner's signature |
| `identityOwnerDeadline` | `bigint` | Deadline timestamp for the owner signature |
| `clientUserSignature` | `Hex` | EIP-712 signature from the client user |
| `clientUserNonce` | `bigint` | Nonce for the client user's signature |
| `clientUserDeadline` | `bigint` | Deadline timestamp for the user signature |
**Returns:** Transaction hash (`Hex`).
### `unlinkIdentityAsSigner`
Unlink identity data as the identity owner (signer). Only the identity owner who created the link can call this.
**Standalone signature:**
```ts
unlinkIdentityAsSigner(
walletClient: WalletClient,
params: UnlinkIdentityAsSignerParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `clientUser` | `Address` | The client user whose link to remove |
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes to unlink |
**Returns:** Transaction hash (`Hex`).
### `unlinkIdentityAsUser`
Unlink identity data as the client user. Allows users to revoke links to their own account.
**Standalone signature:**
```ts
unlinkIdentityAsUser(
walletClient: WalletClient,
params: UnlinkIdentityAsUserParams
): Promise
```
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `policyClient` | `Address` | PolicyClient contract address |
| `identityDomains` | `Hex[]` | Array of domain hashes to unlink |
**Returns:** Transaction hash (`Hex`).
***
## Types & Interfaces
### Intent Types
:::details[IntentFromParams]
The intent shape accepted by SDK methods before normalization.
```ts
interface IntentFromParams {
from: string;
to: string;
value: string;
data: string;
chainId: string | number;
functionSignature: string;
}
```
:::
:::details[NormalizedIntent]
The intent after internal normalization, with all fields as consistent types.
```ts
interface NormalizedIntent {
from: Address;
to: Address;
value: Hex;
data: Hex;
chainId: number;
functionSignature: Hex;
}
```
:::
:::details[HexlifiedIntent]
The intent with all fields encoded as hex strings for on-chain use.
```ts
interface HexlifiedIntent {
from: Hex;
to: Hex;
value: Hex;
data: Hex;
chainId: Hex;
functionSignature: Hex;
}
```
:::
***
### Task Types
:::details[TaskId]
```ts
type TaskId = Hex;
```
A hex-encoded unique identifier for a task.
:::
:::details[TaskStatus]
```ts
type TaskStatus =
| 'Created'
| 'Responded'
| 'AttestationSpent'
| 'AttestationExpired'
| 'SuccessfullyChallenged';
```
Represents the lifecycle state of a task on-chain.
:::
:::details[Task]
```ts
interface Task {
initializationTimestamp: Hex
intent: {
chainId: Hex
data: Hex
from: Address
functionSignature: Hex
to: Address
value: Hex
}
intentSignature: Hex
policyClient: Address
policyId: Hex
policyRevision: number
/** Frozen at admission. Order and repeats are significant. */
policies: PolicySpec[]
wasmArgs: Hex[]
quorumNumbers: Hex
quorumThresholdPercentage: number
taskCreatedBlock: number
taskId: Hex
}
```
:::
:::details[TaskResponse]
```ts
interface PolicyDataEntry {
wasmArgs: Hex
data: Hex
expireBlock: number
}
interface PolicyTaskData {
policyId: Hex
policyAddress: Address
policy: Hex
policyData: PolicyDataEntry[]
}
interface TaskResponse {
taskId: Hex
policyClient: Address
policyId: Hex
intent: NormalizedIntent
intentSignature: Hex
/** True only when every policy allows. */
allowed: boolean
policyTaskData: PolicyTaskData[]
}
```
:::
:::details[TaskResponseResult]
```ts
interface TaskResponseResult {
taskResponse: TaskResponse
responseCertificate: ResponseCertificate
attestation: {
taskId: Hex
policyId: Hex
policyClient: Address
intent: NormalizedIntent
intentSignature: Hex
expiration: number
}
}
```
:::
:::details[SubmitEvaluationRequestParams]
```ts
interface SubmitEvaluationRequestParams {
policyClient: Address
intent: IntentFromParams
intentSignature?: Hex
quorumNumber?: Hex
quorumThresholdPercentage?: number
/** One per policy, positionally aligned. Pure-Rego policies still need a slot — pass `'0x'`. */
wasmArgs: Hex[]
timeout: number // in seconds
identityDomain?: Hex
/** Encrypted data reference UUIDs for privacy-preserving evaluation */
encryptedDataRefs?: string[]
/** User Ed25519 signature for privacy authorization (hex-encoded) */
userSignature?: string
/** Application Ed25519 signature for privacy authorization (hex-encoded) */
appSignature?: string
/** User Ed25519 public key for privacy signature verification (hex-encoded, 32 bytes) */
userPublicKey?: string
/** Application Ed25519 public key for privacy signature verification (hex-encoded, 32 bytes) */
appPublicKey?: string
/** IPFS CID of a TLSNotary presentation proof for zkTLS-backed policy evaluation */
proofCid?: string
/** Include ABI-encoded validateAttestationDirect calldata in the response */
includeValidateCalldata?: boolean
}
```
:::
***
### Simulation Types
:::details[SimulateTaskParams & SimulateTaskResult]
```ts
interface SimulateTaskParams {
intent: IntentFromParams
policyTaskData: SimulateTaskPolicyTaskData
}
interface SimulateTaskPolicyTaskData {
policyId: Hex
policyAddress: Address
policy: Hex
policyData: SimulateTaskPolicyData[]
}
interface SimulateTaskPolicyData {
wasmArgs: Hex
data: Hex
expireBlock: number
}
interface SimulateTaskResult {
success: boolean
result: { allow?: boolean; reason?: string } | null
error: string | null
details: unknown
}
```
:::
:::details[SimulatePolicyParams & SimulatePolicyResult]
```ts
interface SimulatePolicyParams {
policyClient: Address
chainId: number
intent: IntentFromParams
/** One per policy, positionally aligned. `'0x'` for a pure-Rego policy. */
wasmArgs: Hex[]
intentSignature?: Hex
}
interface PolicySimulationResult {
policy_index: number
policy: Address
result: unknown
allowed: boolean
expire_after: number
}
interface SimulatePolicyResult {
success: boolean
allowed: boolean | null
policy_id: string | null
policies: PolicySimulationResult[] | null
error: string | null
parsed_intent: unknown | null
}
```
:::
:::details[SimulatePolicyDataParams & SimulatePolicyDataResult]
```ts
interface SimulatePolicyDataParams {
policyAddress: Address;
secrets?: string;
wasmArgs?: Hex;
chainId: number; // required
}
interface SimulatePolicyDataResult {
success: boolean;
policy_data: {
specifier: string;
data: unknown;
timestamp: number;
} | null;
error: string | null;
}
```
:::
:::details[SimulatePolicyDataWithClientParams & SimulatePolicyDataWithClientResult]
```ts
interface SimulatePolicyDataWithClientParams {
policyAddress: Address;
policyClient: Address;
wasmArgs?: Hex;
}
interface SimulatePolicyDataWithClientResult {
success: boolean;
policy_data: {
specifier: string;
data: unknown;
timestamp: number;
} | null;
error: string | null;
}
```
:::
***
### Policy Types
:::details[PolicyId, PolicyInfo, PolicyCodeInfo, PolicyDataInfo]
```ts
type PolicyId = `0x${string}`;
interface PolicyInfo {
policyId: PolicyId;
policyParams: string | object;
policyParamsHex: `0x${string}`;
expireAfter: number;
}
interface PolicyCodeInfo {
policyCid: string;
schemaCid: string;
metadataCid: string;
entrypoint: string;
}
interface PolicyDataInfo {
addresses: Address[];
}
```
:::
:::details[PolicyParamsJson, SetPolicyInput, SetPolicyResult, PolicyDataInput]
```ts
type PolicyParamsJson = Record;
interface SetPolicyInput {
client: Address;
policyUri: string;
schemaUri: string;
entrypoint: string;
params: PolicyParamsJson;
policyData: Address[];
expireAfter: number;
}
interface SetPolicyResult {
txHash: `0x${string}`;
policyId: PolicyId;
}
interface PolicyDataInput {
policyAddress: Address;
}
```
:::
***
### Gateway Types
:::details[GatewayCreateTaskResult & AggregationResponse]
```ts
interface GatewayCreateTaskResult {
aggregation_response: AggregationResponse
error: string | null
expiration: number
reference_block: number
status: 'success' | 'failed'
task: Task
task_id: Hex
task_response: {
allowed: boolean
initialization_timestamp: Hex
intent: {
chainId: Hex
data: Hex
from: Address
functionSignature: Hex
to: Address
value: Hex
}
intent_signature: Hex
policy_client: Address
policy_id: Hex
policy_task_data: Array<{
policy: Hex
policyAddress: Address
policyData: Array<{
data: Hex
expireBlock: number
wasmArgs: Hex
}>
policyId: Hex
}>
task_id: Hex
}
signature_data: Hex
/** ABI-encoded calldata for validateAttestationDirect (present when include_validate_calldata was true) */
validate_calldata?: string
/** Per-operator error details when quorum fails */
operator_errors?: OperatorError[]
timestamp: number
}
interface AggregationResponse {
non_signer_quorum_bitmap_indices: number[]
non_signer_stake_indices: number[][]
non_signers_pub_keys_g1: number[][]
quorum_apk_indices: number[]
quorum_apks_g1: number[][]
signers_agg_sig_g1: { g1_point: number[] }
signers_apk_g2: number[]
total_stake_indices: number[]
task_created_block: number
}
interface OperatorError {
operator_address: Address
operator_id: Hex
error_code: number
message: string
timestamp: string
retryable: boolean
}
```
:::
***
### JSON-RPC Types
:::details[JsonRpcRequestPayload, JsonRpcResponsePayload, JsonRpcError, NewtonWalletPayloadMethod]
```ts
interface JsonRpcRequestPayload {
jsonrpc: '2.0';
id: number | string;
method: NewtonWalletPayloadMethod;
params: any[];
}
interface JsonRpcResponsePayload {
jsonrpc: '2.0';
id: number | string;
result?: T;
error?: JsonRpcError;
}
interface JsonRpcError {
code: number;
message: string;
data?: any;
}
enum NewtonWalletPayloadMethod {
ShowUI = 'newton_wallet_wallet',
Receive = 'newton_wallet_wallet_receive',
PersonalSign = 'personal_sign',
SendUserOperation = 'eth_sendUserOperation',
Connect = 'newton_wallet_user_connect',
Disconnect = 'newton_wallet_user_disconnect',
IsConnected = 'newton_wallet_user_is_connected',
GetConnectedProfile = 'newton_wallet_user_get_connected_profile',
}
```
:::
***
### Builder Types
:::details[PendingTaskBuilder]
```ts
interface PendingTaskBuilder {
readonly taskId?: TaskId;
waitForTaskResponded(args: {
timeoutMs?: number;
}): Promise;
}
```
Returned by `submitEvaluationRequest`. Call `waitForTaskResponded()` to poll for the on-chain attestation.
:::
***
### Privacy Types
:::details[SecureEnvelope & CreateSecureEnvelopeParams]
```ts
interface SecureEnvelope {
enc: string;
ciphertext: string;
policy_client: string;
chain_id: number;
recipient_pubkey: string;
}
interface CreateSecureEnvelopeParams {
plaintext: Uint8Array | string | Record;
policyClient: Address;
chainId: number;
recipientPublicKey: string;
}
interface SecureEnvelopeResult {
envelope: SecureEnvelope;
signature: string;
senderPublicKey: string;
}
```
:::
:::details[Ed25519KeyPair]
```ts
interface Ed25519KeyPair {
privateKey: string; // 32-byte hex, no 0x prefix
publicKey: string; // 32-byte hex, no 0x prefix
}
```
:::
:::details[UploadIdentityEncryptedParams & Response]
```ts
interface UploadIdentityEncryptedParams {
identityOwner: Address;
identityOwnerSig: string;
envelope: string;
identityDomain: `0x${string}`;
chainId: number;
}
interface UploadIdentityEncryptedResponse {
data_ref_id: string;
gateway_signature: string;
deadline: number;
}
```
:::
:::details[GetIdentityEncryptedResult]
```ts
interface GetIdentityEncryptedResult {
envelope: string;
identity_domain: string;
identity_owner: string;
}
```
:::
:::details[SecretsPublicKeyResponse]
```ts
interface SecretsPublicKeyResponse {
public_key: string;
key_type: string;
encryption_suite: string;
}
```
:::
:::details[StoreEncryptedSecretsParams & Response]
```ts
interface StoreEncryptedSecretsParams {
policyClient: Address;
policyAddress: Address;
/** Plaintext secrets as a JSON object (e.g., { "API_KEY": "...", "ENDPOINT": "..." }) */
plaintext: Record;
chainId: number;
/** Gateway's X25519 public key (hex, no 0x prefix). Fetched automatically if omitted. */
recipientPublicKey?: string;
}
interface StoreEncryptedSecretsResponse {
success: boolean;
schema: Record | null;
error: string | null;
}
```
:::
:::details[SignPrivacyAuthorizationParams & PrivacyAuthorizationResult]
```ts
interface SignPrivacyAuthorizationParams {
policyClient: Address;
intentHash: Hex;
encryptedDataRefs: string[];
userSigningKey: string;
appSigningKey: string;
}
interface PrivacyAuthorizationResult {
userSignature: string;
appSignature: string;
userPublicKey: string;
appPublicKey: string;
}
```
:::
:::details[PrivacyPublicKeyResponse]
```ts
interface PrivacyPublicKeyResponse {
public_key: string;
key_type: string;
encryption_suite: string;
}
```
:::
***
### Webhook Types
:::details[RegisterWebhookParams & RegisterWebhookResult]
```ts
interface RegisterWebhookParams {
url: string;
secret?: string;
timeoutSeconds?: number;
maxRetries?: number;
failureTypes?: TaskFailureType[];
}
interface RegisterWebhookResult {
success: boolean;
message: string;
}
```
:::
:::details[UnregisterWebhookResult]
```ts
interface UnregisterWebhookResult {
success: boolean;
message: string;
}
```
:::
:::details[TaskFailureType]
```ts
type TaskFailureType =
| 'channel_error'
| 'timeout'
| 'quorum_not_reached'
| 'onchain_submission_failed'
| 'policy_evaluation_failed'
| 'signature_verification_failed'
| 'internal_error';
```
:::
### Identity Types
:::details[LinkIdentityAsSignerAndUserParams]
```ts
interface LinkIdentityAsSignerAndUserParams {
policyClient: Address;
identityDomains: Hex[];
}
```
:::
:::details[LinkIdentityAsSignerParams]
```ts
interface LinkIdentityAsSignerParams {
policyClient: Address;
identityDomains: Hex[];
clientUser: Address;
clientUserSignature: Hex;
clientUserNonce: bigint;
clientUserDeadline: bigint;
}
```
:::
:::details[LinkIdentityAsUserParams]
```ts
interface LinkIdentityAsUserParams {
identityOwner: Address;
policyClient: Address;
identityDomains: Hex[];
identityOwnerSignature: Hex;
identityOwnerNonce: bigint;
identityOwnerDeadline: bigint;
}
```
:::
:::details[LinkIdentityParams]
```ts
interface LinkIdentityParams {
identityOwner: Address;
clientUser: Address;
policyClient: Address;
identityDomains: Hex[];
identityOwnerSignature: Hex;
identityOwnerNonce: bigint;
identityOwnerDeadline: bigint;
clientUserSignature: Hex;
clientUserNonce: bigint;
clientUserDeadline: bigint;
}
```
:::
:::details[UnlinkIdentityAsSignerParams]
```ts
interface UnlinkIdentityAsSignerParams {
clientUser: Address;
policyClient: Address;
identityDomains: Hex[];
}
```
:::
:::details[UnlinkIdentityAsUserParams]
```ts
interface UnlinkIdentityAsUserParams {
policyClient: Address;
identityDomains: Hex[];
}
```
:::
:::details[RegisterIdentityDataParams]
```ts
interface RegisterIdentityDataParams {
identityDomain: Hex;
dataRefId: string;
gatewaySignature: Hex;
deadline: bigint;
}
```
:::
***
## Error Handling
The SDK provides structured error classes with typed error codes for precise error handling.
### SDKError
Thrown when an SDK-level error occurs (e.g., missing API key, invalid arguments).
```ts
class SDKError extends Error {
code: SDKErrorCode;
rawMessage: string;
constructor(code: SDKErrorCode, rawMessage: string);
}
```
### MagicRPCError
Thrown when the Newton Gateway returns a JSON-RPC error.
```ts
class MagicRPCError extends Error {
code: RPCErrorCode | number;
rawMessage: string;
data: unknown;
constructor(sourceError?: JsonRpcError | null);
}
```
### SDKErrorCode
:::details[SDKErrorCode enum values]
```ts
enum SDKErrorCode {
MissingApiKey = 'MISSING_API_KEY',
PopupAlreadyExists = 'POPUP_ALREADY_EXISTS',
MalformedResponse = 'MALFORMED_RESPONSE',
InvalidArgument = 'INVALID_ARGUMENT',
ExtensionNotInitialized = 'EXTENSION_NOT_INITIALIZED',
IncompatibleExtensions = 'INCOMPATIBLE_EXTENSIONS',
FailedToOpenPopup = 'FAILED_TO_OPEN_POPUP',
FailedToRetrieveNativeTokenBalance = 'FAILED_TO_RETRIEVE_NATIVE_TOKEN_BALANCE',
MissingChain = 'MISSING_CHAIN',
MissingAccount = 'MISSING_ACCOUNT',
InvalidAddress = 'INVALID_ADDRESS',
}
```
:::
### RPCErrorCode
:::details[RPCErrorCode enum values]
```ts
enum RPCErrorCode {
// Standard JSON-RPC 2.0 error codes
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// Custom Newton error codes
MagicLinkRateLimited = -10002,
UserAlreadyLoggedIn = -10003,
AccessDeniedToUser = -10011,
UserRejectedAction = -10012,
RequestCancelled = -10014,
RedirectLoginComplete = -10015,
NewtonWalletSessionTerminated = -10016,
PopupRequestOverriden = -10017,
}
```
:::
### SDKWarningCode
:::details[SDKWarningCode enum values]
```ts
enum SDKWarningCode {
SyncWeb3Method = 'SYNC_WEB3_METHOD',
ReactNativeEndpointConfiguration = 'REACT_NATIVE_ENDPOINT_CONFIGURATION',
DeprecationNotice = 'DEPRECATION_NOTICE',
ProductAnnouncement = 'ANNOUNCEMENT',
}
```
:::
:::note
`SDKError`, `MagicRPCError`, `SDKErrorCode`, and `RPCErrorCode` are exported from the package's public API, so you can narrow with `instanceof SDKError` / `instanceof MagicRPCError` and switch on the typed `code`. `SDKWarningCode` is not exported.
:::
***
## Subpath Exports
The SDK provides multiple entry points for tree-shaking and targeted imports.
| Import Path | Description |
| ------------------------------------------- | --------------------------------------------------------------------------------- |
| `@newton-xyz/sdk` | Full SDK -- all methods, types, utilities, and constants |
| `@newton-xyz/sdk/types` | Type definitions and interfaces only |
:::info
Use subpath exports to minimize bundle size when you only need a subset of SDK functionality.
:::
***
## Related Resources
* [Integration Guide](/developers/guides/integration-guide) -- Step-by-step guide to building with the Newton Protocol SDK
* [RPC API Reference](/developers/reference/rpc-api) -- Underlying JSON-RPC methods called by the SDK
# RPC API \[Newton Gateway JSON-RPC API reference for policy-based transaction authorization]
The Newton Gateway provides a JSON-RPC 2.0 interface for submitting policy evaluation tasks to the Newton Protocol AVS (Actively Validated Service). The protocol evaluates user intents against Rego policies and returns cryptographic proofs of compliance through BLS signatures.
For client-side helpers that wrap these RPC calls, see the [SDK Reference](/developers/reference/sdk-reference). For a step-by-step walkthrough of building on Newton, see the [Integration Guide](/developers/guides/integration-guide).
## API version scope
Policy-set request schemas below apply to the current gateway API. Use an SDK release that supports task submission and `simulatePolicy`.
## Base URLs
| Network | Environment | URL |
|---------|-------------|-----|
| Testnet (Sepolia) | Production | `https://gateway.testnet.newton.xyz/rpc` |
| Testnet (Sepolia) | Staging | `https://gateway.stagef.testnet.newton.xyz/rpc` |
| Mainnet | Production | `https://gateway.newton.xyz/rpc` |
| Mainnet | Staging | `https://gateway.stagef.newton.xyz/rpc` |
**Protocol:** JSON-RPC 2.0 over HTTPS
:::note
Gateway JSON-RPC requests must be sent to the `/rpc` endpoint. Requests to the host root, such as `https://gateway.testnet.newton.xyz`, return a 404 response instead of a JSON-RPC response.
:::
Use a UUID string for the JSON-RPC `id` field. Numeric IDs are valid in the JSON-RPC 2.0 specification, but the Newton Gateway expects request IDs as strings.
:::code-group
```json [Request Format]
{
"jsonrpc": "2.0",
"method": "",
"params": { ... },
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Success Response]
{
"jsonrpc": "2.0",
"result": { ... },
"id": ""
}
```
```json [Error Response]
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid request"
},
"id": ""
}
```
:::
***
## Authentication
:::info
All API requests require authentication via API key. Include the key in the `Authorization` header:
```
Authorization: Bearer
```
API keys are issued with specific permissions. See [Getting an API Key](/developers/overview/dashboard-api-keys) to get yours.
:::
### Permission Levels
| Permission | Description |
|------------|---------------------------------------------------|
| `RpcRead` | Read-only operations: task submission, simulation |
| `RpcWrite` | Write operations: secrets management |
### Example
```bash
curl -X POST https://gateway.testnet.newton.xyz/rpc \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_api_key_here" \
-d '{"jsonrpc":"2.0","method":"newt_createTask","params":{...},"id":"7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"}'
```
### Policy Client Ownership
Some endpoints require **ownership verification** of a `policy_client` address. This mechanism protects access to stored secrets and ensures only authorized users can manage secrets for a given PolicyClient.
:::note
As of v0.2.0, ownership is determined **on-chain** via the `INewtonPolicyClient.getOwner()` method on the PolicyClient contract. PolicyClient ownership transfers through `setPolicyClientOwner()` on the contract. The Gateway reads on-chain state for every ownership check.
:::
**How Ownership Works:**
1. **Ownership is read from the chain** — the Gateway calls `getOwner()` on the `policy_client` contract to determine the current owner.
2. **Ownership is verified** when calling endpoints that require it. The Gateway checks that the caller's API key is associated with the on-chain owner of the `policy_client`.
3. **Ownership is transferable** — call `setPolicyClientOwner()` as the current owner; the Gateway reads the new on-chain owner on its next check.
**Endpoints Requiring Ownership Verification:**
| Endpoint | Ownership Required |
|-----|-----|
| `newt_simulatePolicyData` | No (caller provides secrets directly) |
| `newt_simulatePolicyDataWithClient` | Yes (fetches stored secrets) |
| `newt_simulatePolicy` | Yes; explicitly authorized read-only admin keys can bypass |
| `newt_storeEncryptedSecrets` | Yes (verifies on-chain owner) |
:::note
The pinned `newt_simulatePolicy` handler checks client ownership for every set, including pure-Rego sets. An explicitly authorized admin API key can bypass this check on read endpoints only, after its permission is revalidated.
:::
**Error Responses:**
If ownership verification fails, you will receive one of these errors:
* `"policy_client is owned by another user"` -- The `policy_client` is owned by a different address on-chain.
* `"policy client has no owner"` -- The contract returned a zero address for `getOwner()`.
***
## Conventions
### Hex Value Encoding
Following Ethereum JSON-RPC conventions:
**Quantities (integers, numbers):**
* Encode as hex with `0x` prefix
* Use most compact representation (no leading zeros except for zero itself)
* Zero is represented as `"0x0"`
```
0x41 (65 in decimal)
0x400 (1024 in decimal)
0x0 (zero)
```
**Unformatted Data (byte arrays, addresses, hashes):**
* Encode as hex with `0x` prefix
* Two hex digits per byte
* Even number of digits required
```
0x1234567890123456789012345678901234567890 (20-byte address)
0x28dca9f7 (4-byte function selector)
0x (empty data)
```
### WASM Arguments
Task submission and policy-set simulation use an ordered array with one hex-encoded entry per policy. Preserve repeated policies and their positions. For two pure-Rego policies use `["0x", "0x"]`, not a scalar or an empty array.
| Method | Argument shape and meaning |
|---|---|
| SDK task methods and `simulatePolicy` | `wasmArgs: Hex[]`; SDK removes the `0x` prefix from each entry on the wire |
| `newt_createTask` / `newt_sendTask` | `wasm_args: string[]`; input to each policy's optional WASM |
| `newt_simulatePolicy` | `wasm_args: string[]`; input to each policy's optional WASM |
| `newt_simulateTask` | `wasm_args: string[]`; **precomputed oracle outputs**, not WASM execution inputs |
| `newt_simulatePolicyData` / `newt_simulatePolicyDataWithClient` | Scalar `wasm_args`; input to one policy's WASM |
Encode JSON inputs with `stringToHex(JSON.stringify(input))` from viem. Use the canonical `0x` prefix in raw requests. An empty `0x` entry represents no data for a pure-Rego policy.
### Address Format
Ethereum addresses are 20-byte hex strings with `0x` prefix:
```
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
```
Addresses are case-insensitive but checksummed addresses are recommended.
***
## Common Types
### TaskIntent
Represents a transaction intent to be evaluated against a policy.
| Field | Type | Required | Description |
|---|---|---|---|
| `from` | `Address` | Yes | Transaction sender address |
| `to` | `Address` | Yes | Transaction recipient address |
| `value` | `Quantity` | Yes | Wei value to transfer (hex encoded) |
| `data` | `Data` | Yes | Transaction calldata (hex encoded) |
| `chain_id` | `Quantity` | Yes | Target chain ID (hex encoded) |
| `function_signature` | `Data` | Yes | Hex-encoded human-readable ABI signature used for calldata decoding, or `"0x"` when the intent has no calldata |
:::details[TaskIntent Example]
```json
{
"from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"to": "0xb1aD5f82407bC0f19f42b2614fb9083035a36b69",
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000e42e3458283032c669c98e0d8f883a92fc64fe220000000000000000000000000000000000000000000000000000000000000001",
"chain_id": "0xaa36a7",
"function_signature": "0x62757928616464726573732c75696e743235362c75696e74333229"
}
```
:::
#### Function signature examples
`function_signature` is not the 4-byte selector. For contract calls, hex-encode the human-readable ABI signature without the `function ` prefix, for example `transfer(address,uint256)`. For native ETH transfers or any intent with no calldata, set both `data` and `function_signature` to `"0x"`.
| Intent kind | `data` | `function_signature` |
|---|---|---|
| Native ETH transfer | `"0x"` | `"0x"` |
| Contract call | ABI-encoded calldata | Hex-encoded signature, for example `transfer(address,uint256)` |
### Policy-set snapshot
A Task contains `policyId`, `policyRevision`, and an ordered `policies` array frozen at admission. Each entry is `{ policy, config: { policyParams, expireAfter } }`. `wasmArgs[i]` belongs to `policies[i]`.
The response's `policy_task_data[i]` retains the same position and carries `policyId`, `policyAddress`, resolved `policy` bytes, and `policyData` evidence entries (`wasmArgs`, `data`, `expireBlock`). The response has one aggregate `allowed` boolean. Do not expect an individual verdict on each evidence entry; use simulation's `policies[i].allowed` for per-policy diagnostics. Never reorder, flatten, or deduplicate signed evidence.
### TaskStatus
Enumeration of task processing states.
| Value | Description |
|---|---|
| `pending` | Task queued for processing |
| `processing` | Task being evaluated by operators |
| `success` | Task completed successfully |
| `failed` | Task evaluation failed |
| `timeout` | Task exceeded timeout threshold |
***
## RPC Methods
### newt\_createTask
Creates a task and waits synchronously for the BLS aggregation result. Use this for simple integrations where blocking is acceptable.
**Permission:** `RpcRead`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_client` | `Address` | Yes | Policy client contract address |
| `intent` | `TaskIntent` | Yes | Transaction intent to evaluate |
| `intent_signature` | `Data` | No | Hex-encoded EIP-712 signature of the exact intent. Optional in the base schema, but required by policies and identity flows that consume `input.intent_signature` |
| `quorum_number` | `Data` | No | Quorum numbers (hex bytes, e.g., `"00"` for quorum 0) |
| `quorum_threshold_percentage` | `Number` | No | BLS aggregation threshold (0-100) |
| `wasm_args` | `Array` | Yes | One entry per policy, in set order; `"0x"` for pure-Rego |
| `timeout` | `Number` | No | Timeout in seconds (default: 30) |
| `proof_cid` | `String` | No | IPFS CID of a TLSNotary presentation proof for zkTLS-backed policy evaluation |
| `include_validate_calldata` | `Boolean` | No | Include ABI-encoded `validateAttestationDirect` calldata in the response (default: false) |
:::warning
Some policy clients and identity-backed flows require `intent_signature` even though the RPC schema marks it optional. Passing an empty signature such as `"0x"` can fail before policy evaluation with an error like `Failed to encode intent signature: 0x reason: expected exactly 65 bytes`.
:::
Sign the exact intent with EIP-712 before calling `newt_createTask`. For PolicyClients that expose EIP-5267, read the signing domain from the contract's `eip712Domain()` method so the signature is bound to the deployed client:
```ts
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;
async function readEip712Domain(policyClient, 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;
}
}
const domain = await readEip712Domain(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: {
...intent,
value: BigInt(intent.value),
chainId: BigInt(intent.chainId),
},
});
```
For manual fallback, the typed-data domain must match the policy client's Solidity `EIP712(name, version)` constructor values and deployed address:
| Solidity / deployment value | EIP-712 domain field |
|---|---|
| `EIP712("NewtonPolicyWallet", "1")` first argument | `name: "NewtonPolicyWallet"` |
| `EIP712("NewtonPolicyWallet", "1")` second argument | `version: "1"` |
| Target chain | `chainId` |
| Deployed PolicyClient address | `verifyingContract` |
#### Returns
| Field | Type | Description |
|---|---|---|
| `task_id` | `Data` | 32-byte task identifier |
| `status` | `TaskStatus` | Final task status |
| `aggregation_response` | `Object` | BLS aggregation result (if successful) |
| `task` | `Object` | Full Task struct for `validateAttestationDirect` (if success) |
| `task_response` | `Object` | Full TaskResponse struct from aggregation (if success) |
| `reference_block` | `Number` | Block number when response was prepared (if success) |
| `expiration` | `Number` | Expiration block: `reference_block + policyConfig.expireAfter` |
| `validate_calldata` | `String` | ABI-encoded calldata for `validateAttestationDirect` (present when `include_validate_calldata` was true) |
| `operator_errors` | `Array` | Per-operator error details with `retryable` classification (present on quorum failure) |
| `error` | `String` | Error message (if failed) |
| `timestamp` | `Number` | Unix timestamp |
:::note
The `task`, `task_response`, `reference_block`, and `expiration` fields enable clients to call `validateAttestationDirect` optimistically without waiting for on-chain task submission. The `signature_data` field contains pre-built, chain-aware encoded bytes ready to pass directly to the contract.
:::
To look the task up later — its evaluation result, operator errors, and the policy data it was evaluated against — pass `task_id` to the [Explorer API](/developers/reference/explorer-api#get-a-task).
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_createTask",
"params": {
"policy_client": "0x1234567890123456789012345678901234567890",
"intent": {
"from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"to": "0xb1aD5f82407bC0f19f42b2614fb9083035a36b69",
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000e42e3458283032c669c98e0d8f883a92fc64fe220000000000000000000000000000000000000000000000000000000000000001",
"chain_id": "0xaa36a7",
"function_signature": "0x7472616e7366657228616464726573732c75696e7432353629"
},
"intent_signature": "0x...",
"wasm_args": ["0x", "0x"],
"timeout": 30
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Success)]
{
"jsonrpc": "2.0",
"result": {
"task_id": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"status": "success",
"aggregation_response": {
"task_response_digest": "0xabcd...",
"non_signer_stakes_and_signature": { }
},
"task": {
"taskId": "0x1234...",
"policyClient": "0x1234567890123456789012345678901234567890",
"policyId": "0x...",
"policyRevision": 1,
"initializationTimestamp": "0x...",
"policies": [
{ "policy": "0x...", "config": { "policyParams": "0x7b7d", "expireAfter": 25 } },
{ "policy": "0x...", "config": { "policyParams": "0x7b7d", "expireAfter": 25 } }
],
"taskCreatedBlock": 12345678,
"quorumThresholdPercentage": 67,
"intent": { },
"intentSignature": "0x...",
"wasmArgs": ["0x", "0x"],
"quorumNumbers": "0x00"
},
"task_response": {
"task_id": "0x1234...",
"policy_client": "0x...",
"policy_id": "0x...",
"intent": { },
"intent_signature": "0x...",
"allowed": true,
"policy_task_data": [
{ "policyId": "0x...", "policyAddress": "0x...", "policy": "0x...", "policyData": [] },
{ "policyId": "0x...", "policyAddress": "0x...", "policy": "0x...", "policyData": [] }
]
},
"reference_block": 12345700,
"expiration": 12345800,
"error": null,
"timestamp": 1705123456
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Failure)]
{
"jsonrpc": "2.0",
"result": {
"task_id": "0x1234567890abcdef...",
"status": "failed",
"aggregation_response": null,
"error": "Policy evaluation failed: insufficient permissions",
"timestamp": 1705123456
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_sendTask
Submits a task asynchronously and returns immediately with a WebSocket subscription topic for receiving updates.
**Permission:** `RpcRead`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_client` | `Address` | Yes | Policy client contract address |
| `intent` | `TaskIntent` | Yes | Transaction intent to evaluate |
| `intent_signature` | `Data` | No | Hex-encoded EIP-712 signature of the exact intent. Required by policies and identity flows that consume `input.intent_signature` |
| `quorum_number` | `Data` | No | Quorum numbers (hex bytes) |
| `quorum_threshold_percentage` | `Number` | No | BLS aggregation threshold (0-100) |
| `wasm_args` | `Array` | Yes | One entry per policy, in set order; `"0x"` for pure-Rego |
| `proof_cid` | `String` | No | IPFS CID of a TLSNotary presentation proof for zkTLS-backed policy evaluation |
#### Returns
| Field | Type | Description |
|---|---|---|
| `task_id` | `Data` | 32-byte task identifier |
| `subscription_topic` | `String` | WebSocket topic for task updates |
| `message` | `String` | Confirmation message |
| `timestamp` | `Number` | Unix timestamp |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_sendTask",
"params": {
"policy_client": "0x1234567890123456789012345678901234567890",
"intent": {
"from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"to": "0xb1aD5f82407bC0f19f42b2614fb9083035a36b69",
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000e42e3458283032c669c98e0d8f883a92fc64fe220000000000000000000000000000000000000000000000000000000000000001",
"chain_id": "0xaa36a7",
"function_signature": "0x7472616e7366657228616464726573732c75696e7432353629"
},
"intent_signature": "0x..."
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"task_id": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"subscription_topic": "newton.task.0x1234567890abcdef...",
"message": "Task submitted for async processing",
"timestamp": 1705123456
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
#### WebSocket Updates
Connect to the WebSocket endpoint and subscribe to the returned topic:
```json
{
"type": "subscribe",
"id": "sub-1",
"method": "newton.task.0x1234567890abcdef...",
"params": null
}
```
Updates are received in the following format:
```json
{
"type": "update",
"subscription": "sub-1",
"result": {
"event": "success",
"task_id": "0x1234567890abcdef...",
"timestamp": 1705123460,
"data": {
"status": "success",
"operator_responses": [],
"result": { },
"progress": 100
}
}
}
```
***
### newt\_simulateTask
Evaluates Rego locally on the gateway using provided oracle **outputs**. It resolves the client's current policy set; it does not execute WASM, contact an operator quorum, produce BLS signatures, or create an on-chain task. Reconfiguration changes what this endpoint evaluates: it is not historical replay of a task's frozen set.
**Permission:** `RpcRead`.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
| `policy_client` | `Address` | Yes | Configured PolicyClient |
| `chain_id` | `number` | Yes | Deployment chain ID |
| `intent` | `TaskIntent` | Yes | Intent to evaluate |
| `wasm_args` | `Array` | Yes | Preassembled oracle outputs, positionally aligned; `"0x"` for pure-Rego |
```json
{
"jsonrpc": "2.0",
"method": "newt_simulateTask",
"params": {
"policy_client": "0x3333333333333333333333333333333333333333",
"chain_id": 11155111,
"intent": {
"from": "0x1111111111111111111111111111111111111111",
"to": "0x2222222222222222222222222222222222222222",
"value": "0x0",
"data": "0x",
"chain_id": "0xaa36a7",
"function_signature": "0x"
},
"wasm_args": [
"0x",
"0x"
]
},
"id": "policy-set-example"
}
```
| Field | Type | Meaning |
|---|---|---|
| `success` | `boolean` | Evaluation completed without an error |
| `allowed` | `boolean \| null` | AND of every policy verdict; null on error |
| `policy_id` | `string \| null` | ID of the set evaluated |
| `policies` | `array \| null` | Ordered results: `policy_index`, `policy` address, `result`, `allowed`, `expire_after` |
| `error` | `string \| null` | Evaluation failure details |
| `parsed_intent` | `object \| null` | Decoded intent |
A completed denial is `success: true, allowed: false`. A simulation failure is `success: false, allowed: null`. Neither response carries a BLS attestation.
:::warning
SDK v1.2.2's `simulateTask` wrapper still sends the old `policy_task_data` request. Use raw RPC for this endpoint, or SDK `simulatePolicy` for WASM + Rego simulation.
:::
***
### newt\_storeEncryptedSecrets
Uploads encrypted secrets for a Policy contract. Secrets are validated against the on-chain schema and stored for use during task evaluation.
**Permission:** `RpcWrite`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_client` | `Address` | Yes | Policy client contract address |
| `policy_address` | `Address` | Yes | Policy contract address |
| `chain_id` | `Number` | Yes | Deployment chain ID |
| `envelope` | `String` | Yes | JSON-serialized HPKE SecureEnvelope (produced by `storeEncryptedSecrets` in the SDK) |
#### Returns
| Field | Type | Description |
|---|---|---|
| `success` | `Boolean` | Whether upload succeeded |
| `schema` | `Object` | JSON schema used for validation (on success) |
| `error` | `String` | Error message (on failure) |
#### Encryption
Secrets are encrypted client-side using HPKE before upload. Use `storeEncryptedSecrets` from the SDK, which handles encryption automatically. See [Encrypting Secrets](/developers/advanced/encrypting-secrets) for the full workflow.
**Plaintext format (before encryption):**
```json
{
"API_KEY": "sk-xxxxxxxxxxxxx",
"ENDPOINT": "https://api.example.com"
}
```
#### Ownership Model
:::note
Only the on-chain owner of the PolicyClient contract (as returned by `getOwner()`) can upload or update secrets. Ownership is transferable on-chain via `setOwner()` or `transferOwnership()`.
:::
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_storeEncryptedSecrets",
"params": {
"policy_client": "0x1234567890123456789012345678901234567890",
"policy_address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"chain_id": 11155111,
"envelope": ""
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Success)]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"schema": {
"type": "object",
"properties": {
"API_KEY": { "type": "string" },
"ENDPOINT": { "type": "string", "format": "uri" }
},
"required": ["API_KEY", "ENDPOINT"]
},
"error": null
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Error - Ownership Violation)]
{
"jsonrpc": "2.0",
"result": {
"success": false,
"schema": null,
"error": "policy_client is owned by a different user"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Error - Schema Validation)]
{
"jsonrpc": "2.0",
"result": {
"success": false,
"schema": null,
"error": "Secrets upload validation failed: missing required field 'API_KEY'"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_simulatePolicyData
Simulates PolicyData WASM execution with caller-provided secrets. Use this endpoint to iterate on WASM plugins before uploading production secrets.
**Permission:** `RpcRead`
**Authorization:** Rate limiting only. Any valid API key can call this endpoint. No ownership verification is performed because secrets are provided directly by the caller.
#### When to Use
* **Iterating on PolicyData WASM** before uploading production secrets
* **Testing WASM execution** with different secret configurations
* **Policies whose WASM does not require secrets** (secrets field is optional)
#### Workflow
1. Deploy a Policy contract with `wasmCid` and optional `secretsSchemaCid`
2. Call `newt_simulatePolicyData` with optional encrypted secrets to test
3. Once satisfied, upload production secrets via `newt_storeEncryptedSecrets`
4. Test full policy via `newt_simulatePolicy` or `newt_simulatePolicyDataWithClient`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_address` | `Address` | Yes | Policy contract address |
| `secrets` | `Object` | No | HPKE SecureEnvelope JSON object; not base64 |
| `wasm_args` | `Data` | No | Hex-encoded WASM arguments |
| `chain_id` | `Number` | Yes | Chain ID for RPC provider resolution |
#### Returns
| Field | Type | Description |
|---|---|---|
| `success` | `Boolean` | Whether simulation succeeded |
| `policy_data` | `Object` | WASM execution result (on success) |
| `error` | `String` | Error message (on failure) |
The `policy_data` object contains:
| Field | Type | Description |
|---|---|---|
| `specifier` | `String` | WASM source identifier (IPFS CID) |
| `data` | `Object` | WASM output data |
| `timestamp` | `Number` | Execution timestamp |
#### Example
:::code-group
```json [Request (with secrets)]
{
"jsonrpc": "2.0",
"method": "newt_simulatePolicyData",
"params": {
"policy_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"chain_id": 11155111,
"secrets": {
"enc": "",
"ciphertext": "",
"policy_client": "0x3333333333333333333333333333333333333333",
"chain_id": 11155111,
"recipient_pubkey": ""
},
"wasm_args": "0x7b22626173655f73796d626f6c223a22425443227d"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Request (without secrets)]
{
"jsonrpc": "2.0",
"method": "newt_simulatePolicyData",
"params": {
"policy_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"chain_id": 11155111,
"wasm_args": "0x7b22626173655f73796d626f6c223a22425443227d"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Success)]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"policy_data": {
"specifier": "bafkreigh2akiscaill...",
"data": {
"base_symbol": "BTC",
"quote_symbol": "USD",
"price": 42000.5
},
"timestamp": 1705123456
},
"error": null
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Error)]
{
"jsonrpc": "2.0",
"result": {
"success": false,
"policy_data": null,
"error": "WASM execution failed: missing required secret 'API_KEY'"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_simulatePolicyDataWithClient
Simulates PolicyData WASM execution with previously uploaded secrets. Use this endpoint to test that stored secrets work correctly before testing the full policy.
**Permission:** `RpcRead`
**Authorization:** Ownership verification required. The caller's API key must belong to the user who owns the `policy_client` address. Ownership is checked against the client; uploading secrets does not transfer ownership.
#### When to Use
* **Testing PolicyData WASM** with previously uploaded secrets
* **Verifying stored secrets** work correctly before using `newt_simulatePolicy`
* **Debugging policy failures** by testing individual PolicyData sources
#### Prerequisites
1. Deploy a Policy contract with `wasmCid` and optional `secretsSchemaCid`
2. Upload secrets via `newt_storeEncryptedSecrets` as the authorized owner
3. Call this endpoint to test with stored secrets
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_address` | `Address` | Yes | Policy contract address |
| `chain_id` | `Number` | Yes | Deployment chain ID |
| `policy_client` | `Address` | Yes | Policy client address (for ownership check + secrets lookup) |
| `wasm_args` | `Data` | No | Hex-encoded WASM arguments |
#### Returns
| Field | Type | Description |
|---|---|---|
| `success` | `Boolean` | Whether simulation succeeded |
| `policy_data` | `Object` | WASM execution result (on success) |
| `error` | `String` | Error message (on failure) |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_simulatePolicyDataWithClient",
"params": {
"policy_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"chain_id": 11155111,
"policy_client": "0x1234567890123456789012345678901234567890",
"wasm_args": "0x7b22626173655f73796d626f6c223a22425443227d"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Success)]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"policy_data": {
"specifier": "bafkreigh2akiscaill...",
"data": {
"base_symbol": "BTC",
"quote_symbol": "USD",
"price": 42000.5
},
"timestamp": 1705123456
},
"error": null
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Error - Unauthorized)]
{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Authorization failed: policy client 0x1234... is owned by another user"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response (Error - Missing Secrets)]
{
"jsonrpc": "2.0",
"result": {
"success": false,
"policy_data": null,
"error": "No secrets found for policy_client 0x1234... and policy_data 0xaaaa.... Upload secrets via newt_storeEncryptedSecrets first, or use newt_simulatePolicyData with directly provided secrets."
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_simulatePolicy
Runs the configured policy set's optional WASM oracles and then Rego in the gateway's in-process evaluator. Unlike `newt_simulateTask`, `wasm_args` contains oracle **inputs**. Policies and the client must already be deployed and configured. Parameters come from the client's set; this endpoint no longer accepts inline Rego, `policy_data`, `policy_params`, or an `entrypoint` override.
**Permission:** `RpcRead`; on-chain client ownership is checked, with the controlled read-only admin-key exception described above. The in-process evaluator must be configured. This endpoint does not produce quorum signatures or execute an application transaction.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
| `policy_client` | `Address` | Yes | Configured PolicyClient |
| `chain_id` | `number` | Yes | Deployment chain ID |
| `intent` | `TaskIntent` | Yes | Intent to evaluate |
| `wasm_args` | `Array` | Yes | One oracle input per policy; `"0x"` for pure-Rego |
| `intent_signature` | `Data` | No | Accepted request field; simulation does not prove live signature authorization |
```json
{
"jsonrpc": "2.0",
"method": "newt_simulatePolicy",
"params": {
"policy_client": "0x3333333333333333333333333333333333333333",
"chain_id": 11155111,
"intent": {
"from": "0x1111111111111111111111111111111111111111",
"to": "0x2222222222222222222222222222222222222222",
"value": "0x0",
"data": "0x",
"chain_id": "0xaa36a7",
"function_signature": "0x"
},
"wasm_args": [
"0x",
"0x"
]
},
"id": "policy-set-example"
}
```
| Field | Type | Meaning |
|---|---|---|
| `success` | `boolean` | Evaluation completed without an error |
| `allowed` | `boolean \| null` | AND of every policy verdict; null on error |
| `policy_id` | `string \| null` | ID of the set evaluated |
| `policies` | `array \| null` | Ordered results: `policy_index`, `policy` address, `result`, `allowed`, `expire_after` |
| `error` | `string \| null` | Evaluation failure details |
| `parsed_intent` | `object \| null` | Decoded intent |
A completed denial is `success: true, allowed: false`. A simulation failure is `success: false, allowed: null`. Neither response carries a BLS attestation.
Use `policies[i].policy_index` and `policies[i].policy` to map each verdict back to the configured order. For production evaluation and signatures, use `newt_createTask` or `newt_sendTask` and subsequently validate the attestation on-chain.
***
### newt\_registerWebhook
Registers a webhook URL to receive failure notifications for tasks created with this API key.
#### Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | Yes | HTTPS URL to receive notifications |
| `secret` | `string` | No | HMAC secret for payload signing (recommended) |
| `timeout_seconds` | `number` | No | Request timeout in seconds (default: 10) |
| `max_retries` | `number` | No | Maximum retry attempts (default: 3) |
| `failure_types` | `string[]` | No | Failure types to notify about (default: all) |
**Failure Types:**
* `channel_error` -- Internal channel communication failures
* `timeout` -- Task processing timed out
* `quorum_not_reached` -- Insufficient operator signatures
* `onchain_submission_failed` -- Blockchain submission failed
* `policy_evaluation_failed` -- Policy evaluation error
* `signature_verification_failed` -- BLS signature verification error
* `internal_error` -- Unexpected internal error
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_registerWebhook",
"params": {
"url": "https://api.example.com/newton/webhook",
"secret": "your-hmac-secret-here",
"failure_types": ["timeout", "quorum_not_reached", "onchain_submission_failed"]
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"message": "Webhook registered for https://api.example.com/newton/webhook"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_unregisterWebhook
Removes the webhook configuration for this API key.
**Parameters:** None
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_unregisterWebhook",
"params": {},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"message": "Webhook unregistered"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
## Failure Notifications
The Newton Gateway provides real-time failure notifications through two channels: WebSocket subscriptions and webhook callbacks. Use WebSocket for real-time UI updates and webhooks for reliable server-to-server notifications.
### WebSocket Notifications
WebSocket notifications are best for:
* Real-time UI updates in browser applications
* Low-latency notification requirements
* Clients that maintain persistent connections
When subscribed to a task via WebSocket, failure notifications are delivered as JSON messages containing the full `TaskFailureNotification` payload.
### Webhook Callbacks
Webhook callbacks are best for:
* Server-to-server integrations
* Reliable delivery with automatic retries
* Auditable notification history
* Clients that cannot maintain persistent connections
**Webhook Request Format:**
The gateway sends POST requests to your registered URL with the following structure:
:::details[Webhook Payload]
```json
{
"notification_id": "uuid-v4-unique-id",
"task_id": "0x1234...",
"timestamp": "2024-01-15T10:30:00Z",
"failure_type": "quorum_not_reached",
"error_message": "Quorum not reached: only 45% stake signed (required: 67%)",
"context": {
"signed_stake": "45000000000000000000",
"required_stake": "67000000000000000000",
"operator_count": 3
},
"is_recoverable": false,
"retry_after_seconds": null
}
```
:::
**Security Headers:**
| Header | Description |
|---|---|
| `X-Newton-Signature` | HMAC-SHA256 signature (if secret configured) |
| `X-Idempotency-Key` | Unique notification ID for deduplication |
| `Content-Type` | `application/json` |
**Signature Verification:**
```python
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
```
**Retry Behavior:**
* Initial retry after 100ms with exponential backoff
* Maximum 3 retry attempts (configurable)
* Backoff capped at 10 seconds
* 4xx client errors (except 429) are not retried
***
## Developer Workflow
### Policy Development Lifecycle
1. Test Rego and optional WASM locally.
2. Deploy independent policies and configure the ordered set on the client.
3. Upload policy-scoped secrets when needed; test individual WASM modules with scalar-argument simulation endpoints.
4. Run `newt_simulatePolicy` for WASM + Rego, or `newt_simulateTask` with precomputed outputs. Neither produces signatures.
5. Sign the exact intent and submit `newt_createTask` or `newt_sendTask` for live operator evaluation.
6. Check aggregate `allowed` and validate the signed response on-chain before application execution.
7. Preserve task snapshots for historical investigation. Current-set simulation is not historical replay.
For required cases and evidence, see [Testing Policies](/developers/guides/testing-policies#policy-set-validation-checklist).
## Error Codes
### JSON-RPC Standard Errors
| Code | Message | Description |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid request | Invalid JSON-RPC structure |
| -32601 | Method not found | Unknown RPC method |
| -32602 | Invalid params | Invalid method parameters |
| -32603 | Internal error | Server error |
### Newton-Specific Error Codes
| Code | Name | Description |
|---|---|---|
| -32615 | PolicyEvaluationError | Policy evaluation failed |
| -32616 | ContractError | Smart contract interaction error |
| -32617 | OperatorNotRegistered | Operator is not registered with the AVS |
| -32618 | IncompatiblePolicyVersion | Policy version mismatch between client/server |
| -32619 | IncompatiblePolicyDataVersion | Policy data version mismatch |
| -32620 | DataProviderError | Error fetching policy data from data provider |
### Newton-Specific Error Messages
| Error Message | Cause | Resolution |
|---|---|---|
| `"Unknown Newton RPC method: {method}"` | Invalid method name | Check method spelling |
| `"Invalid request format: {details}"` | Malformed parameters | Review parameter types |
| `"RpcRead permission required"` | Missing read permission | Use key with RpcRead |
| `"RpcWrite permission required"` | Missing write permission | Use key with RpcWrite |
| `"Invalid API key"` | Invalid or expired key | Verify API key |
| `"policy client {addr} is owned by another user"` | Ownership violation | Use API key that owns the policy\_client |
| `"policy client {addr} has no owner..."` | No ownership established | Upload secrets first via `newt_storeEncryptedSecrets` |
| `"Missing secrets for N PolicyData contract(s)"` | Secrets not uploaded | Upload secrets via `newt_storeEncryptedSecrets` |
| `"No secrets found for policy_client ... and policy_data..."` | Secrets not uploaded for pair | Upload secrets or use `newt_simulatePolicyData` instead |
| `"Secrets upload validation failed: {details}"` | Schema validation failure | Fix secrets format |
| `"WASM execution failed: {details}"` | WASM runtime error | Check WASM and inputs |
| `"Rego evaluation failed: {details}"` | Policy syntax/runtime error | Fix Rego code |
| `"Contract read failed: {details}"` | On-chain read error | Verify contract addresses |
| `"Task timeout"` | Aggregation timeout | Increase timeout or retry |
### HTTP Status Codes
| Status | Description |
|---|---|
| 200 | Success (check JSON-RPC result for errors) |
| 401 | Unauthorized - Invalid API key |
| 403 | Forbidden - Insufficient permissions |
| 429 | Too Many Requests - Rate limited |
| 500 | Internal Server Error |
| 502 | Bad Gateway - Service unavailable |
***
### Two HPKE Public Key Domains
Newton exposes two distinct HPKE public key endpoints. Using the wrong one causes silent decryption failures in threshold mode.
| Endpoint | Use For | Key Behavior |
|----------|---------|--------------|
| `newt_getPrivacyPublicKey` | Identity data, confidential data, ephemeral data | Threshold master public key (MPK) when DKG is active; otherwise individual operator key |
| `newt_getSecretsPublicKey` | WASM secrets (API keys, config for data providers) | Always the individual operator's key |
**Why two keys?** WASM secrets are decrypted during the Prepare phase when operators fetch policy data independently. In threshold mode, the threshold MPK cannot be decrypted until the Commit phase (after partial DH outputs are collected). Secrets encrypted to the threshold MPK would be undecryptable during Prepare, deadlocking the protocol. `newt_getSecretsPublicKey` always returns the individual operator's key to avoid this.
In single-node deployments, both keys may be identical. In production threshold DKG deployments, they differ.
### newt\_getPrivacyPublicKey
:::note
This method is under active development and not yet available in production. The API surface may change.
:::
Retrieves the system's threshold HPKE public key for encrypting data. Use this key to create a `SecureEnvelope` for sensitive data that operators threshold-decrypt during task evaluation.
**Permission:** `RpcRead`
#### Parameters
None.
#### Returns
| Field | Type | Description |
|---|---|---|
| `public_key` | `String` | Hex-encoded X25519 public key (32 bytes) |
| `key_type` | `String` | Key type identifier (`"x25519"`) |
| `encryption_suite` | `String` | Full cipher suite identifier |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_getPrivacyPublicKey",
"params": {},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"public_key": "",
"key_type": "x25519",
"encryption_suite": "HPKE-Base-X25519-HKDF-SHA256-ChaCha20Poly1305"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
See [Privacy Layer](/developers/concepts/privacy-layer) for details on HPKE encryption.
***
### newt\_getSecretsPublicKey
Retrieves the HPKE public key used for encrypting WASM secrets. In threshold DKG mode, this may differ from the privacy public key returned by `newt_getPrivacyPublicKey`.
**Permission:** `RpcRead`
#### Parameters
None.
#### Returns
| Field | Type | Description |
|---|---|---|
| `public_key` | `String` | Hex-encoded X25519 public key (32 bytes) |
| `key_type` | `String` | Key type identifier (`"x25519"`) |
| `encryption_suite` | `String` | Full cipher suite identifier |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_getSecretsPublicKey",
"params": {},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"public_key": "",
"key_type": "x25519",
"encryption_suite": "HPKE-Base-X25519-HKDF-SHA256-ChaCha20Poly1305"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
:::note
Use `newt_getSecretsPublicKey` when encrypting WASM secrets via `storeEncryptedSecrets`. Use `newt_getPrivacyPublicKey` for identity and confidential data encryption.
:::
***
### newt\_uploadIdentityEncrypted
Uploads HPKE-encrypted identity data to the gateway. The gateway stores the encrypted envelope and returns a `data_ref_id`, gateway EIP-712 signature, and deadline for the on-chain `registerIdentityData` call.
**Permission:** `RpcWrite`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `identity_owner` | `Address` | Yes | The identity owner's address |
| `identity_owner_sig` | `String` | Yes | EIP-712 signature of the SecureEnvelope JSON by the identity owner |
| `envelope` | `String` | Yes | JSON-serialized HPKE SecureEnvelope |
| `identity_domain` | `Hex (bytes32)` | Yes | Identity domain hash (e.g., `keccak256("kyc")`) |
| `chain_id` | `Number` | Yes | Chain ID the identity registry lives on |
#### Returns
| Field | Type | Description |
|---|---|---|
| `data_ref_id` | `String` | Content-hash data reference ID: `keccak256(envelope_json_bytes)` |
| `gateway_signature` | `String` | Gateway EIP-712 signature for `registerIdentityData` on-chain call |
| `deadline` | `Number` | Signature expiration (unix timestamp) for `registerIdentityData` |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_uploadIdentityEncrypted",
"params": {
"identity_owner": "0x1234567890abcdef1234567890abcdef12345678",
"identity_owner_sig": "0x...",
"envelope": "{\"enc\":\"...\",\"ciphertext\":\"...\",\"policy_client\":\"0x...\",\"chain_id\":11155111,\"recipient_pubkey\":\"...\"}",
"identity_domain": "0x...",
"chain_id": 11155111
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"data_ref_id": "0xabcdef1234567890...",
"gateway_signature": "0x...",
"deadline": 1705127056
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
After receiving the response, call `registerIdentityData` on the IdentityRegistry contract with the `data_ref_id`, `gateway_signature`, and `deadline`.
***
### newt\_getIdentityEncrypted
Fetches encrypted identity data by its content-hash reference ID. Used to resolve a `data_ref_id` (stored on-chain in IdentityRegistry) back to the actual encrypted data blob (stored off-chain in the gateway).
**Permission:** `RpcRead`
#### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `data_ref_id` | `String` | Yes | Content-hash data reference ID |
#### Returns
| Field | Type | Description |
|---|---|---|
| `envelope` | `String` | The HPKE-encrypted identity data as a SecureEnvelope JSON string |
| `identity_domain` | `String` | The identity domain this data was registered under |
| `identity_owner` | `String` | The identity owner address |
#### Example
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_getIdentityEncrypted",
"params": {
"data_ref_id": "0xabcdef1234567890..."
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"envelope": "{\"enc\":\"...\",\"ciphertext\":\"...\"}",
"identity_domain": "0x...",
"identity_owner": "0x1234567890abcdef1234567890abcdef12345678"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
### newt\_uploadConfidentialData
Upload HPKE-encrypted confidential data (blacklists, allowlists, sanctions lists) for a registered provider. The gateway stores the encrypted envelope and returns a content-hash `data_ref_id`. The provider then publishes the `data_ref_id` on-chain via `ConfidentialDataRegistry.publishData()` and grants access to specific policy clients.
**Permission level:** `RpcWrite`
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `provider` | `Address` | Yes | Provider address (must be registered on ConfidentialDataRegistry) |
| `domain` | `Hex (bytes32)` | Yes | Domain identifier (e.g., `keccak256("newton.privacy.blacklist")`) |
| `envelope` | `string` | Yes | Serialized HPKE `SecureEnvelope` JSON |
| `chain_id` | `number` | Yes | Chain ID the data is scoped to |
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_uploadConfidentialData",
"params": {
"provider": "0x1234567890abcdef1234567890abcdef12345678",
"domain": "0x6f389bcc4114a6f517365d26d1327865d8634fb1e896e1323e904058afbea0e0",
"envelope": "{\"enc\":\"...\",\"ciphertext\":\"...\",\"policy_client\":\"0x...\",\"chain_id\":11155111,\"recipient_pubkey\":\"...\"}",
"chain_id": 11155111
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"success": true,
"data_ref_id": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"error": null
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
The SDK's `uploadConfidentialData` function handles HPKE encryption and envelope construction automatically.
***
### newt\_getConfidentialData
Retrieve encrypted confidential data by its content-hash reference ID. Returns the stored HPKE envelope for operator-side decryption.
**Permission level:** `RpcRead`
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data_ref_id` | `string` | Yes | Content-hash data reference ID from `uploadConfidentialData` |
:::code-group
```json [Request]
{
"jsonrpc": "2.0",
"method": "newt_getConfidentialData",
"params": {
"data_ref_id": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
```json [Response]
{
"jsonrpc": "2.0",
"result": {
"envelope": "{\"enc\":\"...\",\"ciphertext\":\"...\",\"policy_client\":\"0x...\",\"chain_id\":11155111,\"recipient_pubkey\":\"...\"}",
"data_type": "confidential"
},
"id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
}
```
:::
***
## Related Resources
* [SDK Reference](/developers/reference/sdk-reference) -- TypeScript SDK for interacting with Newton Protocol
* [Integration Guide](/developers/guides/integration-guide) -- Step-by-step guide for integrating Newton into your application
* [Open Policy Agent (Rego) Documentation](https://www.openpolicyagent.org/docs/latest/) -- Reference for writing Rego policies
* [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification) -- Protocol specification
# Explorer API \[Read-only REST API for Newton tasks and policies. Query task results, policy metadata, and protocol stats with your Newton API key.]
The Explorer API is the read-only REST API behind [Newton Explorer](/developers/resources/newton-explorer). It serves the indexed history of every task and policy on the protocol, so scripts, CLIs, and backends can look up the same data the Explorer UI shows.
Use it alongside the other Newton interfaces:
| Need | Use |
|------|-----|
| Submit or simulate a task, manage secrets | [RPC API](/developers/reference/rpc-api) (Newton Gateway) |
| Check a task's on-chain status from your app | SDK [`getTaskStatus`](/developers/reference/sdk-reference#gettaskstatus) |
| Look up a task's evaluation result, errors, and policy data after the fact | Explorer API |
| List tasks for a policy, browse policies, or pull protocol stats | Explorer API |
## Base URL
```
https://explorer.api.newton.xyz/v1
```
One host serves every network. Select the network with the `X-Protocol-Network` header:
| Network | `X-Protocol-Network` |
|---------|----------------------|
| Testnet (Sepolia) | `testnet` (default) |
| Base Sepolia | `base-sepolia` |
| Mainnet | `mainnet` |
| Base | `base` |
Requests without the header read testnet.
***
## Authentication
:::info
Every `/v1` request requires your Newton API key, the same `gw_…` key you use with the Newton Gateway. Send it in the `Authorization` header:
```
Authorization: Bearer
```
`X-API-Key: ` is also accepted. Get a key from [Dashboard & API Keys](/developers/overview/dashboard-api-keys).
:::
The key needs the `rpc_read` permission. Keys created in the dashboard carry `rpc`, which includes it. Rotating or deleting a key in the dashboard takes effect on the next request.
:::warning
Keep the API key server-side. Read it from `NEWTON_API_KEY` in scripts and Route Handlers; never embed it in browser bundles or commit it to source.
:::
### Example
```bash
export NEWTON_API_KEY="gw_..."
curl -sS https://explorer.api.newton.xyz/v1/task?size=5 \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet"
```
***
## Conventions
### Pagination
List endpoints accept `page` (from `1`, default `1`) and `size` (`1`–`100`, default `10`) query parameters and return:
```json
{
"total": 1284,
"page": 1,
"size": 10,
"results": [ ... ]
}
```
### Error format
Errors return a non-2xx status with a JSON body of the form `{ "detail": "" }`. See [Errors](#errors) for the full table.
### Timestamps and fields
Timestamps are ISO 8601 in UTC. Fields that have not happened yet — for example `task_responded_at` before operators respond — are `null`.
***
## Tasks
### Task object
List endpoints return task summaries. [Get a task](#get-a-task) returns the full detail, which adds the intent, policy data, and transaction hashes.
| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | 32-byte task identifier (`task_id` from [`newt_createTask`](/developers/reference/rpc-api#newt_createtask)) |
| `network` | `string` | `testnet`, `base-sepolia`, `mainnet`, or `base` |
| `sender_address` | `string` | Address that submitted the task |
| `policy_client_address` | `string` | PolicyClient the task was evaluated for |
| `policy_id` | `string \| null` | Policy ID bound to the PolicyClient |
| `policy_address` | `string \| null` | Policy contract address |
| `evaluation_result` | `string \| null` | Hex-encoded evaluation result from the task response. `null` until operators respond |
| `error` | `string \| null` | Task-level error, if the task failed |
| `operator_errors` | `object \| array \| null` | Errors reported by individual operators |
| `created_block` | `number \| null` | Block the task was created in |
| `task_created_at` | `string \| null` | When the task was created on-chain |
| `task_responded_at` | `string \| null` | When operators responded |
| `attestation_spent_at` | `string \| null` | When the attestation was consumed by the PolicyClient |
| `task_challenged_at` | `string \| null` | When the task was challenged |
| `challenge_result` | `string \| null` | `unchallenged`, `success`, or `unsuccessful` |
| `task_responded_metadata` | `object \| null` | Response certificate metadata |
| `updated_at` | `string` | When the indexer last updated this task |
Task detail adds:
| Field | Type | Description |
|-------|------|-------------|
| `intent` | `object` | The evaluated [Intent](/developers/overview/core-concepts#intent) |
| `policy_config` | `object \| null` | Policy configuration (params) used for the evaluation |
| `policy_task_data` | `object \| null` | Data oracle output the policy was evaluated against |
| `created_txn_hash` | `string \| null` | Task creation transaction |
| `responded_txn_hash` | `string \| null` | Task response transaction |
| `challenged_txn_hash` | `string \| null` | Challenge transaction |
| `spent_txn_hash` | `string \| null` | Attestation spend transaction |
### Get a task
`GET /v1/task/{task_id}`
| Parameter | In | Description |
|-----------|----|-------------|
| `task_id` | path | 32-byte task identifier |
:::code-group
```bash [Request]
curl -sS https://explorer.api.newton.xyz/v1/task/0x1234...abcd \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet"
```
```json [Response]
{
"id": "0x1234...abcd",
"network": "testnet",
"sender_address": "0xSender...",
"policy_client_address": "0xPolicyClient...",
"policy_id": "0xPolicyId...",
"policy_address": "0xPolicy...",
"evaluation_result": "0x...",
"error": null,
"operator_errors": null,
"created_block": 8912345,
"task_created_at": "2026-09-24T17:02:11Z",
"task_responded_at": "2026-09-24T17:02:14Z",
"attestation_spent_at": null,
"task_challenged_at": null,
"challenge_result": null,
"intent": { "from": "0x...", "to": "0x...", "value": "0x0", "data": "0x...", "chainId": 11155111, "functionSignature": "0x..." },
"policy_config": { ... },
"policy_task_data": { ... },
"created_txn_hash": "0x...",
"responded_txn_hash": "0x...",
"challenged_txn_hash": null,
"spent_txn_hash": null,
"updated_at": "2026-09-24T17:02:15Z"
}
```
:::
Returns `404` with `{"detail": "Task not found."}` if the indexer has not seen the task on the selected network. Tasks appear shortly after the creation transaction is indexed; check that `X-Protocol-Network` matches the chain you submitted to.
### List tasks
`GET /v1/task`
Lists tasks on the selected network. Supports [pagination](#pagination).
```bash
curl -sS "https://explorer.api.newton.xyz/v1/task?page=1&size=20" \
-H "Authorization: Bearer $NEWTON_API_KEY"
```
Returns a paginated list of [task summaries](#task-object).
***
## Policies
### Policy object
| Field | Type | Description |
|-------|------|-------------|
| `address` | `string` | Policy contract address |
| `network` | `string` | Network the policy is deployed on |
| `name` | `string \| null` | Name from the policy metadata |
| `version` | `string \| null` | Version from the policy metadata |
| `author` | `string \| null` | Author from the policy metadata |
| `description` | `string \| null` | Description from the policy metadata |
| `verified` | `boolean \| null` | Whether the policy is verified |
| `policy_info` | `object \| null` | On-chain policy info (including the metadata reference) |
| `deployed_txn_hash` | `string \| null` | Deployment transaction |
| `policy_deployed_at` | `string \| null` | Deployment time |
### Get a policy
`GET /v1/policy/{address}`
:::code-group
```bash [Request]
curl -sS https://explorer.api.newton.xyz/v1/policy/0xPolicy... \
-H "Authorization: Bearer $NEWTON_API_KEY"
```
```json [Response]
{
"address": "0xPolicy...",
"network": "testnet",
"name": "sanctions-screening",
"version": "1.0.0",
"author": "0xAuthor...",
"description": "Blocks transfers to sanctioned addresses",
"verified": true,
"policy_info": { ... },
"deployed_txn_hash": "0x...",
"policy_deployed_at": "2026-09-01T12:00:00Z"
}
```
:::
Returns `404` if no policy exists at that address on the selected network.
### List policies
`GET /v1/policy`
Lists policies on the selected network. Supports [pagination](#pagination).
```bash
curl -sS "https://explorer.api.newton.xyz/v1/policy?size=50" \
-H "Authorization: Bearer $NEWTON_API_KEY"
```
### List tasks for a policy
`GET /v1/policy/id/{policy_id}/task`
`GET /v1/policy/address/{policy_address}/task`
Lists tasks evaluated against a policy, by policy ID or by policy contract address. Supports [pagination](#pagination) and returns [task summaries](#task-object).
```bash
curl -sS "https://explorer.api.newton.xyz/v1/policy/address/0xPolicy.../task?size=20" \
-H "Authorization: Bearer $NEWTON_API_KEY"
```
***
## Stats
### Get stats
`GET /v1/stats`
Counts tasks and policies within a time window.
| Parameter | In | Required | Description |
|-----------|----|----------|-------------|
| `task_window_start` | query | Yes | ISO 8601 start of the task window |
| `task_window_end` | query | No | ISO 8601 end of the task window |
| `policy_window_start` | query | No | ISO 8601 start of the policy window |
| `policy_window_end` | query | No | ISO 8601 end of the policy window |
:::code-group
```bash [Request]
curl -sS "https://explorer.api.newton.xyz/v1/stats?task_window_start=2026-09-01T00:00:00Z" \
-H "Authorization: Bearer $NEWTON_API_KEY"
```
```json [Response]
{
"total_tasks": 18234,
"total_policies": 97
}
```
:::
***
## Recipes
### Check a task after submitting it
Take the `task_id` returned by [`newt_createTask`](/developers/reference/rpc-api#newt_createtask) and pull the fields you need to debug the evaluation:
```bash
TASK_ID="0x..."
curl -sS "https://explorer.api.newton.xyz/v1/task/$TASK_ID" \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet" \
| jq '{evaluation_result, error, operator_errors, policy_task_data}'
```
* `error` or `operator_errors` set: the evaluation failed. Check the oracle with [`newt_simulatePolicyData`](/developers/reference/rpc-api#newt_simulatepolicydata) and the policy with [`newt_simulatePolicy`](/developers/reference/rpc-api#newt_simulatepolicy).
* `policy_task_data` contains the original evaluation evidence. Preserve the task’s frozen policy set and revision for reproduction. [`newt_simulateTask`](/developers/reference/rpc-api#newt_simulatetask) resolves the current set and is not historical replay.
### Recent tasks for your PolicyClient's policy
```bash
POLICY_ADDRESS="0x..."
curl -sS "https://explorer.api.newton.xyz/v1/policy/address/$POLICY_ADDRESS/task?size=20" \
-H "Authorization: Bearer $NEWTON_API_KEY" \
| jq -r '.results[] | [.id, .task_created_at, .evaluation_result, (.error // "")] | @tsv'
```
### Page through every result
```bash
page=1
while :; do
resp=$(curl -sS "https://explorer.api.newton.xyz/v1/task?page=$page&size=100" \
-H "Authorization: Bearer $NEWTON_API_KEY")
echo "$resp" | jq -c '.results[]'
[ "$(echo "$resp" | jq '.page * .size < .total')" = "true" ] || break
page=$((page + 1))
done
```
### Server-side TypeScript helper
Call the Explorer API from a server Route Handler or backend job so the key never reaches the browser:
```typescript
const EXPLORER_API_URL = 'https://explorer.api.newton.xyz/v1'
type Network = 'testnet' | 'base-sepolia' | 'mainnet' | 'base'
export async function getTask(taskId: `0x${string}`, network: Network = 'testnet') {
const res = await fetch(`${EXPLORER_API_URL}/task/${taskId}`, {
headers: {
Authorization: `Bearer ${process.env.NEWTON_API_KEY}`,
'X-Protocol-Network': network,
},
})
if (res.status === 404) return null
if (!res.ok) {
const { detail } = await res.json().catch(() => ({ detail: res.statusText }))
throw new Error(`Explorer API ${res.status}: ${detail}`)
}
return res.json()
}
```
***
## Errors
| Status | `detail` | Fix |
|--------|----------|-----|
| `400` | `Invalid protocol network header` | Use `testnet`, `base-sepolia`, `mainnet`, or `base` for `X-Protocol-Network` |
| `401` | `API key required (Authorization: Bearer or X-API-Key)` | Send your API key in one of the two headers |
| `401` | `invalid or expired API key` | Check for typos and whitespace; the key may have been rotated or deleted in the [dashboard](https://dashboard.newton.xyz/) |
| `403` | `API key missing required permission: rpc_read` | Use a key with `rpc_read` or `rpc` permission |
| `404` | `Task not found.` / `Policy Template not found.` | Confirm the ID or address and that `X-Protocol-Network` matches its network |
| `422` | Validation error | Check query parameters, e.g. `size` must be between 1 and 100 |
| `503` | `API key validation unavailable` | Temporary; retry with backoff |
## Next Steps
Submit and simulate tasks through the Newton Gateway
Debug policies, oracles, and attestations
Field definitions and the task lifecycle
# Command Line Tool \[Newton CLI reference for deploying policies, managing policy data, uploading secrets, and submitting evaluation requests]
## Overview
The Newton CLI (`newton-cli`) is a command-line interface for interacting with the Newton Policy Protocol AVS. It provides tools for deploying policies, managing policy data, configuring policy clients, uploading encrypted secrets, managing privacy data, and submitting evaluation requests.
With the Newton CLI you can:
* **Generate CIDs** for policy files and upload them to IPFS via Pinata
* **Deploy and simulate** policy data contracts and policy contracts
* **Configure policy clients** with parameters, expiration, and lifecycle management
* **Upload encrypted secrets** for WASM data providers via HPKE
* **Encrypt and upload privacy data** (identity, confidential, ephemeral)
* **Submit evaluation requests** to the prover AVS
* **Evaluate Rego policies locally** with Newton crypto extensions
For a walkthrough of how these commands fit into a full deployment workflow, see the [Integration Guide](/developers/guides/integration-guide).
## Installation
Install `newton-cli` using the `newtup` version manager:
```bash
curl -L cli.newton.xyz | sh
newtup
```
This installs `newtup` (the version manager) and the latest `newton-cli` binary to `~/.newton/bin/`.
Pin a specific version:
```bash
newtup -v v0.2.1
```
Verify the installation:
```bash
newton-cli --version
```
:::note
Windows does not have prebuilt binaries. Use WSL and follow the Linux install above.
:::
## Configuration
`newton-cli` loads configuration in the following priority order (highest wins):
1. Command-line flags (`--chain-id`, `--rpc-url`, etc.)
2. Environment variables with `NEWTON_CLI__` prefix
3. Config file at `~/.newton/newton-cli.toml` (if it exists)
### Config File
Create `~/.newton/newton-cli.toml` for non-secret persistent defaults:
```toml
gateway_url = "https://gateway.testnet.newton.xyz/rpc"
```
Contract addresses are loaded automatically from bundled deployment files — you do not need to configure them manually.
### Environment Variables
Most commands accept configuration through environment variables or command-line arguments. Inject credentials into the current process environment or the user-level `~/.newton/.env`; do not commit or source a project-local `.env`:
```bash
export CHAIN_ID=84532
export PRIVATE_KEY="your_private_key"
export RPC_URL="https://your-rpc.example"
export PINATA_JWT="your_pinata_jwt"
export PINATA_GATEWAY="your_pinata_gateway"
```
Never paste secret values into chat with a coding agent or place them directly in command arguments, where they can leak through shell history and process inspection.
### Global Flags
The following flags can be used with any command:
| Flag | Environment Variable | Description |
| --- | --- | --- |
| `--chain-id` | `CHAIN_ID` | The chain ID to use |
| `--log-format` | — | Log format: `full`, `compact`, `pretty`, `json`, or `minimal` |
| `--quiet` | — | Suppress verbose output |
## Commands
### Policy Files
#### `generate-cids`
Generates CIDs for policy files and uploads them to IPFS via Pinata.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--directory` | Yes | Directory containing policy files |
| `--output` | Yes | Output path for the generated CID JSON file |
| `--entrypoint` | Yes | Policy entrypoint (e.g. `max_gas_price.allow`) |
| `--pinata-jwt` | No | Pinata JWT (falls back to `PINATA_JWT` env var) |
| `--pinata-gateway` | No | Pinata gateway (falls back to `PINATA_GATEWAY` env var) |
**Usage**
```bash
newton-cli policy-files generate-cids \
--directory policy-files \
--output policy-files/policy_cids.json \
--entrypoint "max_gas_price.allow"
```
***
### Policy
#### `deploy`
Builds and deploys a policy project, including an optional WASM module.
:::note
Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
:::
**Usage**
```bash
newton-cli policy deploy -p policies/my-policy
```
#### `simulate`
Simulates a full policy evaluation (Rego + WASM + secrets) locally.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--policy-client` | Yes | PolicyClient contract address |
| `--intent-from` | Yes | Intent sender address |
| `--intent-to` | Yes | Intent target address |
| `--intent-value` | No | ETH value (hex, defaults to `0x0`) |
| `--intent-data` | No | Encoded calldata (hex) |
| `--gateway-url` | Yes | Gateway RPC URL |
| `--api-key` | Yes | Newton API key |
```bash
newton-cli --chain-id 84532 policy simulate \
--policy-client 0x... \
--intent-from 0xf39f...2266 \
--intent-to 0xb1ad...36b69 \
--intent-value 0x0 \
--intent-data 0x... \
--gateway-url https://gateway.testnet.newton.xyz/rpc \
--api-key $NEWTON_API_KEY
```
:::note
`policy simulate` evaluates the policy's Rego and optional WASM module. Use it to debug policy logic before going live.
:::
***
### Policy Client
Most `policy-client` subcommands require `--registry` (the `PolicyClientRegistry` contract address) and `--client` (the `PolicyClient` contract address).
#### `register`
Registers a PolicyClient contract with the PolicyClientRegistry. Required for identity linking.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--client` | Yes | Address of the policy client contract |
| `--private-key` | No | Deployer private key (falls back to `PRIVATE_KEY` env var) |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
**Usage**
```bash
newton-cli --chain-id 84532 policy-client register \
--registry 0x... \
--client 0x...
```
#### `status`
Checks the registration status of a PolicyClient.
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--client` | Yes | Address of the policy client contract |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
```bash
newton-cli --chain-id 84532 policy-client status \
--registry 0x... \
--client 0x...
```
#### `list`
Lists all PolicyClients owned by an address.
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--owner` | Yes | Owner address to query |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
```bash
newton-cli --chain-id 84532 policy-client list \
--registry 0x... \
--owner 0x...
```
#### `deactivate`
Deactivates a registered PolicyClient. Prevents new identity links.
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--client` | Yes | Address of the policy client contract |
| `--private-key` | No | Deployer private key (falls back to `PRIVATE_KEY` env var) |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
```bash
newton-cli --chain-id 84532 policy-client deactivate \
--registry 0x... --client 0x... --private-key $KEY --rpc-url $RPC
```
#### `activate`
Reactivates a previously deactivated PolicyClient.
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--client` | Yes | Address of the policy client contract |
| `--private-key` | No | Deployer private key (falls back to `PRIVATE_KEY` env var) |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
```bash
newton-cli --chain-id 84532 policy-client activate \
--registry 0x... --client 0x... --private-key $KEY --rpc-url $RPC
```
#### `transfer-ownership`
Transfers registry ownership of a PolicyClient contract.
| Flag | Required | Description |
| --- | --- | --- |
| `--registry` | Yes | Address of the PolicyClientRegistry contract |
| `--client` | Yes | Address of the policy client contract |
| `--new-owner` | Yes | Address of the new owner |
| `--private-key` | No | Deployer private key (falls back to `PRIVATE_KEY` env var) |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
```bash
newton-cli --chain-id 84532 policy-client transfer-ownership \
--registry 0x... --client 0x... --new-owner 0x... --private-key $KEY --rpc-url $RPC
```
***
### Secrets
Upload HPKE-encrypted secrets for WASM data providers that call `secrets::get()` at runtime. Secrets are scoped per `policy_data_address` — redeploying PolicyData creates a new contract, so you must re-upload secrets for the new address.
#### `upload`
Encrypts secrets client-side via HPKE and uploads the sealed envelope to the gateway.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--secrets-file` | Yes | Path to JSON file containing key-value secrets |
| `--policy-client` | Yes | PolicyClient contract address |
| `--policy-data-address` | Yes | PolicyData contract address |
| `--api-key` | Yes | Newton API key |
| `--chain-id` | Yes | Chain ID |
| `--gateway-url` | No | Gateway RPC URL (auto-resolved from chain ID if omitted) |
**Usage**
```bash
# Create a secrets file matching your PolicyData's schema
cat > secrets.json << 'EOF'
{
"INDEXER_API_KEY": "sk-prod-xxxxxxxxxxxx",
"INDEXER_URL": "https://api.example.com/v1"
}
EOF
# Upload (encrypts client-side via HPKE, operator validates schema)
newton-cli --chain-id 84532 secrets upload \
--secrets-file secrets.json \
--policy-client 0x... \
--policy-data-address 0x... \
--api-key $NEWTON_API_KEY
```
:::note
The CLI handles the full workflow: fetches the HPKE public key via `newt_getSecretsPublicKey`, seals the envelope with X25519/ChaCha20-Poly1305, and uploads via `newt_storeEncryptedSecrets`. See [Encrypting Secrets](/developers/advanced/encrypting-secrets) for details on the wire protocol.
:::
***
### Privacy
Manage HPKE-encrypted privacy data across all three privacy paths: identity, confidential, and ephemeral. All privacy operations encrypt client-side using the key from `newt_getPrivacyPublicKey`.
#### `get-public-key`
Fetch the gateway's HPKE public key for client-side encryption.
```bash
newton-cli --chain-id 84532 privacy get-public-key \
--gateway-url https://gateway.testnet.newton.xyz/rpc \
--api-key $NEWTON_API_KEY
```
#### `identity upload`
Upload persistent identity data (KYC, credentials). The data is linked to a policy client on-chain after upload.
| Flag | Required | Description |
| --- | --- | --- |
| `--data-file` | Yes | Path to identity data JSON file |
| `--domain` | Yes | Identity domain (e.g. `kyc`) |
| `--policy-client` | Yes | PolicyClient contract address |
| `--gateway-url` | No | Gateway RPC URL |
| `--api-key` | Yes | Newton API key |
```bash
newton-cli --chain-id 84532 privacy identity upload \
--data-file identity.json \
--domain kyc \
--policy-client 0x... \
--gateway-url https://gateway.testnet.newton.xyz/rpc \
--api-key $NEWTON_API_KEY
```
#### `confidential upload`
Upload provider-managed confidential data (blacklists, allowlists).
| Flag | Required | Description |
| --- | --- | --- |
| `--data-file` | Yes | Path to confidential data JSON file |
| `--domain` | Yes | Data domain (e.g. `blacklist`) |
| `--gateway-url` | No | Gateway RPC URL |
| `--api-key` | Yes | Newton API key |
```bash
newton-cli --chain-id 84532 privacy confidential upload \
--data-file blacklist.json \
--domain blacklist \
--gateway-url https://gateway.testnet.newton.xyz/rpc \
--api-key $NEWTON_API_KEY
```
#### `ephemeral encrypt`
Encrypt ephemeral data for inline use in task requests. Ephemeral data exists only during evaluation and is never stored.
| Flag | Required | Description |
| --- | --- | --- |
| `--data` | Yes | JSON data to encrypt (inline string) |
| `--policy-client` | Yes | PolicyClient contract address |
| `--gateway-url` | No | Gateway RPC URL |
| `--api-key` | Yes | Newton API key |
```bash
newton-cli --chain-id 84532 privacy ephemeral encrypt \
--data '{"credit_score": 750}' \
--policy-client 0x... \
--gateway-url https://gateway.testnet.newton.xyz/rpc \
--api-key $NEWTON_API_KEY
```
See [Privacy Flows](/developers/guides/privacy-flows) for the full architecture and [Writing Policies: Identity Built-ins](/developers/guides/writing-policies) for Rego integration.
***
### Task
#### `submit-evaluation-request`
Submits a policy evaluation task to the Newton network via the gateway.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--policy-client` | Yes | Address of the policy client contract |
| `--intent-from` | Yes | Intent sender address |
| `--intent-to` | Yes | Intent target address |
| `--intent-value` | No | ETH value (hex string, defaults to `0x0`) |
| `--intent-data` | No | Encoded calldata (hex string) |
| `--intent-chain-id` | No | Intent chain ID (defaults to `--chain-id`) |
| `--api-key` | Yes | API key for gateway authentication |
| `--gateway-url` | No | Gateway RPC URL (auto-resolved from chain ID if omitted) |
| `--timeout` | No | Timeout in seconds (default: 30) |
**Usage**
```bash
newton-cli --chain-id 84532 task submit-evaluation-request \
--policy-client 0x... \
--intent-from 0xf39f...2266 \
--intent-to 0xb1ad...36b69 \
--intent-value 0x0 \
--intent-data 0x... \
--intent-chain-id 84532 \
--api-key $NEWTON_API_KEY \
--gateway-url https://gateway.testnet.newton.xyz/rpc
```
The response includes `signature_data` for on-chain attestation validation.
:::note
The command normalizes the intent (converts `value`/`chainId` to hex), signs the task, and submits it to the Newton Gateway.
:::
***
### Regorus
The `regorus` command wraps Newton's fork of **Regorus 0.9.0**, configured for **Rego v1**, with Newton-specific crypto extensions (`newton.crypto.ecdsa_recover_signer`, `newton.crypto.ecdsa_recover_signer_personal`) and identity built-ins (`newton.identity.*`, `newton.confidential.*`).
Its compatibility target is an **OPA v1.2.0-compatible subset plus Newton-specific extensions**. It does not implement every OPA built-in. See the [Rego Syntax Guide](/developers/advanced/rego-syntax-guide#unsupported-opa-built-ins) before relying on standard crypto/JWT, GraphQL, JSON Patch, network/CIDR/DNS, AWS provider, metadata, or module-parsing functions.
#### `eval`
Evaluates a Rego query locally.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `query` (positional) | Yes | Rego query to evaluate (e.g., `data.policy.allow`) |
| `-d`, `--data` | Yes | Policy or data files (Rego, JSON, or YAML). Can be specified multiple times. |
| `-i`, `--input` | No | Input file (JSON or YAML) |
| `-b`, `--bundles` | No | Directories containing Rego bundles |
| `-t`, `--trace` | No | Enable tracing |
| `-n`, `--non-strict` | No | Perform non-strict evaluation (OPA default behavior) |
| `-c`, `--coverage` | No | Display coverage information |
**Usage**
```bash
newton-cli regorus eval \
-d policy.rego \
-d data.json \
-i intent.json \
--non-strict \
"data.policy.allow"
```
:::note
The `--non-strict` flag enables OPA-style non-strict evaluation. It does not enable missing OPA built-ins or imply complete OPA v1.2.0 compatibility.
:::
#### `lex`
Tokenizes a Rego policy file. Useful for debugging policy syntax.
```bash
newton-cli regorus lex policy.rego
```
#### `parse`
Parses a Rego policy file and validates its syntax.
```bash
newton-cli regorus parse policy.rego
```
#### `ast`
Parses a Rego policy file and dumps the abstract syntax tree (AST).
```bash
newton-cli regorus ast policy.rego
```
***
### Completions
Generate shell completions for tab-completion of commands, flags, and arguments.
```bash
# Bash (add to ~/.bashrc)
newton-cli completions bash >> ~/.bashrc
# Zsh (add to fpath)
newton-cli completions zsh > ~/.zfunc/_newton-cli
# Fish
newton-cli completions fish > ~/.config/fish/completions/newton-cli.fish
```
Supported shells: `bash`, `zsh`, `fish`, `elvish`, `powershell`.
Pre-generated completions are also included in release tarballs under a `completions/` directory.
***
### Version
#### `info`
Shows protocol version information.
```bash
newton-cli version info
```
#### `check-compatibility`
Checks if a PolicyClient contract is compatible with the current protocol version or if migration is needed.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--policy-client` | Yes | Address of the PolicyClient contract to check |
| `--chain-id` | Yes | Chain ID where the contract is deployed |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
**Usage**
```bash
newton-cli version check-compatibility \
--policy-client "0x..." \
--chain-id 84532
```
#### `migrate`
Runs automated migration for PolicyClient contracts that are incompatible with the current protocol version.
**Parameters**
| Flag | Required | Description |
| --- | --- | --- |
| `--policy-client` | Yes | Address of the PolicyClient contract to migrate |
| `--chain-id` | Yes | Chain ID where the contract is deployed |
| `--private-key` | No | Deployer private key (falls back to `PRIVATE_KEY` env var) |
| `--rpc-url` | No | RPC endpoint URL (falls back to `RPC_URL` env var) |
| `--skip-check` | No | Skip compatibility check before migrating |
| `--dry-run` | No | Simulate the migration without executing it |
**Usage**
```bash
newton-cli version migrate \
--policy-client "0x..." \
--chain-id 84532
```
***
## Integration Workflow
A typical local-first workflow for integrating a new smart contract with Newton:
| Step | Command | Purpose |
| --- | --- | --- |
| 1 | `newton-cli policy scaffold` | Create the policy source, schemas, fixtures, and deployment config |
| 2 | `newton-cli policy build -p ` | Validate and compile the policy artifacts |
| 3 | `newton-cli policy simulate -p ` | Prove at least one allow and one deny case locally |
| 4 | `newton-cli policy deploy -p ` | Generate CIDs and deploy PolicyData + Policy after approval |
| 5 | `forge build && forge test` | Build and adversarially test the application PolicyClient |
| 6 | `forge script ... --broadcast` | Deploy the tested PolicyClient after approval |
| 7 | `cast send ... setPolicies` | Configure ordered policies and params while the local deployer is owner |
| 8 | `cast send ... registerClient` | Register only when the application requires it |
| 9 | `cast send ... setPolicyClientOwner` | Transfer ownership to the dashboard login wallet when required |
| 10 | `newton-cli secrets upload` | Upload client-scoped secrets after owner and schema checks |
| 11 | SDK `evaluateIntentDirect` | Sign the EIP-712 intent and evaluate through a server-side gateway client |
For the full contract-side integration, see the [Smart Contract Integration](/developers/guides/smart-contract-integration) guide.
## Supported Chains
| Chain | Chain ID | Network |
| --- | --- | --- |
| Ethereum | 1 | mainnet |
| Sepolia | 11155111 | testnet |
| Base | 8453 | mainnet |
| Base Sepolia | 84532 | testnet |
| Local (anvil) | 31337 | local |
## Supported Platforms
| Platform | CLI | Node (operators) | Notes |
| --- | --- | --- | --- |
| Linux x86\_64 | Yes | Yes | |
| Linux aarch64 | Yes | Yes | Native build on ARM64 runner |
| macOS aarch64 (Apple Silicon) | Yes | Yes | |
| macOS x86\_64 (Intel) | Yes | No | `sp1-prover` fails under Rosetta |
| Windows | No | No | Use WSL |
## Troubleshooting
:::details[newton-cli: command not found]
Restart your shell or run `source ~/.zshrc`. Verify `~/.newton/bin` is on your `PATH`.
:::
:::details[chain id is required]
Pass `--chain-id` to the command or set the `CHAIN_ID` environment variable.
:::
:::details[Failed to load configuration]
For commands that interact with contracts, ensure deployment files exist for your chain. Run with `--quiet` to suppress verbose config loading logs.
:::
:::details[HPKE private key not configured]
The `secrets upload` command encrypts client-side. The gateway's HPKE public key is fetched automatically via `newt_getSecretsPublicKey` — no local key configuration is needed.
:::
## Next Steps
Step-by-step deployment walkthrough
TypeScript SDK for programmatic integration
Interact with the Gateway directly via JSON-RPC
# Contract Addresses \[Deployed Newton Protocol contract addresses across all supported networks]
This page lists all deployed Newton Protocol contract addresses. Use these when configuring the SDK, interacting with contracts directly, or verifying deployments.
When code or examples refer to `NEWTON_TASK_MANAGER`, use the `NewtonProverTaskManager` address for your target network.
## Ethereum Sepolia (Chain ID: 11155111)
### Production
| Contract | Address |
|----------|---------|
| NewtonProverTaskManager | `0xecb741f4875770f9a5f060cb30f6c9eb5966ed13` |
| AttestationValidator | `0x26f452e4b9c9c28508cb836ba486cceaa95b429c` |
| PolicyClientRegistry | `0x0dbd6e44a1814f5efe4f67a00b7f28642e3064dd` |
| IdentityRegistry | `0xbb688f4ad1bd896197db9a20e04abe8c6344625a` |
| ConfidentialDataRegistry | `0xdaee51cda3c2728627b7432eacc09425be6a308b` |
| PolicyFactory | `0xe37952d9003d399579670e97b4c67b175112d0cd` |
| PolicyDataFactory | `0x3558adccf326f459d2bec38db47f5534d789e758` |
## Base Sepolia (Chain ID: 84532)
Base Sepolia supports cross-chain policy verification.
| Contract | Address |
|----------|---------|
| NewtonProverTaskManager | `0xa5e104ad7f09df5d9036d1e9ad60fada11140071` |
| AttestationValidator | `0xf345c77e111a5731f88571c7c69919e8460154e5` |
| PolicyClientRegistry | `0x2a7b31e48e8b8962b71c36c9377e5a1023b89b0d` |
| IdentityRegistry | `0x1b6ad56a68544e3f5d4afa29aa2aee8e62a70130` |
| ConfidentialDataRegistry | `0x64866c22ee0fb16b6b7502f5a4ca73d82f3de3d7` |
| PolicyFactory | `0x4302e64b36706411e96d120ae539dd84febac59c` |
| PolicyDataFactory | `0xa1242b625fbf03c94c6502a217e9ffcf30c9ed73` |
## Base Mainnet (Chain ID: 8453)
Base mainnet serves as a destination chain for cross-chain policy verification.
| Contract | Address |
|----------|---------|
| NewtonProverTaskManager | `0x56afac1633d6260e7aa7df7e57550e8238355f1b` |
| AttestationValidator | `0x28ff3f99b4efa428a5ab1908edba76f4c525bffc` |
| PolicyClientRegistry | `0x852c458207fdf9380c2e80a01baf3666dc838d4e` |
| IdentityRegistry | `0x55c02f400e606f1c9357c9181ea4ff3c4e878afc` |
| ConfidentialDataRegistry | `0xf906551ae33bf3eb03188f035dd0147ce76b150f` |
| PolicyFactory | `0x05f6f25a95170734a81809f7eea04d9c80dc6241` |
| PolicyDataFactory | `0x12c2d6dda8df0ca99478e275edd17e12e175bc6c` |
## Ethereum Mainnet (Chain ID: 1)
:::warning
Ethereum mainnet contracts are deployed but require coordination before production use. Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
:::
| Contract | Address |
|----------|---------|
| NewtonProverTaskManager | `0xf26b8b9a8de7ec35635e8175613701cf155a7528` |
| AttestationValidator | `0xb679e939410a4f490d328a41b49453160eaf5e2e` |
| PolicyClientRegistry | `0x9445404e83ef6349cd11fa7645b2dcb7629a940f` |
| IdentityRegistry | `0x59ac3c6834beb6dd8d87ab6e785a99607d421396` |
| ConfidentialDataRegistry | `0x301facba2c85beabb1d38bdf82cca28e9b839215` |
| PolicyFactory | `0x4c033e5e27fce5e3606ba43640c52d2e71af616b` |
| PolicyDataFactory | `0xd9771c997a7bf87f2519b322a69f0160c7b37055` |
## Gateway URLs
| Network | Environment | URL |
|---------|-------------|-----|
| Testnet (Sepolia) | Production | `https://gateway.testnet.newton.xyz/rpc` |
| Testnet (Sepolia) | Staging | `https://gateway.stagef.testnet.newton.xyz/rpc` |
| Mainnet | Production | `https://gateway.newton.xyz/rpc` |
| Mainnet | Staging | `https://gateway.stagef.newton.xyz/rpc` |
## Next Steps
Use these contracts through the TypeScript SDK
Interact with the Gateway directly
# Error Reference \[Newton Protocol error codes, messages, and resolution steps]
This page documents all error codes and messages you may encounter when interacting with the Newton Protocol Gateway, SDK, and smart contracts.
## JSON-RPC Standard Errors
| Code | Message | Description |
|------|---------|-------------|
| -32700 | Parse error | Invalid JSON in request body |
| -32600 | Invalid request | Request does not conform to JSON-RPC 2.0 |
| -32601 | Method not found | Unknown RPC method name |
| -32602 | Invalid params | Missing or invalid method parameters |
| -32603 | Internal error | Unexpected server-side error |
## Newton-Specific Error Codes
| Code | Name | Description | Resolution |
|------|------|-------------|------------|
| -32615 | PolicyEvaluationError | Rego policy evaluation failed | Check Rego syntax, verify `data.wasm` and `data.params` are populated correctly |
| -32616 | ContractError | Smart contract interaction failed | Verify contract addresses are correct and contracts are deployed |
| -32617 | OperatorNotRegistered | Calling operator is not registered with the AVS | Ensure operator registration is complete |
| -32620 | DataProviderError | WASM oracle execution failed | Check WASM binary, verify secrets and `wasm_args` |
## Gateway Error Messages
| Error Message | Cause | Resolution |
|---------------|-------|------------|
| `"Unknown Newton RPC method: {method}"` | Typo in method name | Verify method spelling against [RPC API](/developers/reference/rpc-api) |
| `"Invalid request format: {details}"` | Malformed request parameters | Review parameter types and required fields |
| `"RpcRead permission required"` | API key lacks read permission | Use key with `rpc_read` or `rpc` permission |
| `"RpcWrite permission required"` | API key lacks write permission | Use key with `rpc_write` or `rpc` permission |
| `"Invalid API key"` | API key is invalid or expired | Verify key or create a new one via [Dashboard](/developers/overview/dashboard-api-keys) |
| `"policy_client is owned by another user"` | On-chain owner does not match API key holder | Use API key associated with the on-chain `getOwner()` address |
| `"policy client has no owner"` | Contract returned zero address for `getOwner()` | Verify contract is initialized with an owner |
| `"Missing secrets for N PolicyData contract(s)"` | Secrets not uploaded for PolicyData contracts | Upload secrets via `newt_storeEncryptedSecrets` |
| `"No secrets found for policy_client ... and policy_data..."` | No secrets stored for this client/data pair | Upload secrets or use `newt_simulatePolicyData` with inline secrets |
| `"Secrets upload validation failed: {details}"` | Secrets JSON does not match on-chain schema | Fix secrets format to match `secretsSchemaCid` schema |
| `"WASM execution failed: {details}"` | WASM oracle runtime error | Check WASM binary, inputs, and `wasm_args` |
| `"Rego evaluation failed: {details}"` | Policy syntax or runtime error | Fix Rego code, test with `newton-cli regorus eval` |
| `"Contract read failed: {details}"` | On-chain contract read failed | Verify contract addresses and chain connectivity |
| `"Task timeout"` | BLS aggregation exceeded timeout | Increase timeout parameter or retry |
| `"No operators available"` | No registered operators online | Retry later; check network status |
| `"TaskAlreadyExists"` | Duplicate task submission | Use a different nonce or wait for existing task |
| `"BlockNumberTooRecent"` | Reference block too close to current | Wait a few blocks before submitting |
## On-Chain Errors
| Error | Contract | Cause | Resolution |
|-------|----------|-------|------------|
| `InvalidAttestation()` | PolicyClient | BLS signature verification failed | Verify Task Manager address, check attestation expiration |
| `ExecutionFailed()` | PolicyClient | Inner transaction call reverted | Debug the target transaction independently |
| `PolicyClientNotRegistered` | PolicyClientRegistry | Client not registered or has been deactivated | Register via `registerClient()` or CLI: `newton-cli policy-client register`. See [PolicyClientRegistry](/developers/guides/smart-contract-integration#policyclientregistry) |
| `EmptyPolicySet` | PolicyClient | Empty replacement | Supply at least one spec |
| `TooManyPolicies` | PolicyClient | More than 8 policies in pinned release | Reduce the set size |
| `PolicyNotRegistered` | PolicyClient | Address not registered by configured factory | Verify TaskManager `policyFactory()` and factory `isPolicy` |
| `PolicyFactoryNotSet` | PolicyClient | Missing TaskManager or factory | Configure the correct deployment |
| `ZeroExpireAfter` | PolicyClient | A spec has zero expiry | Set a nonzero block window |
| `PolicyParamsTooLarge` | PolicyClient | Params exceed 65,536 bytes | Reduce encoded params |
| `OnlyPolicyClientOwner` | PolicyClient | Unauthorized replacement | Submit through the current owner |
| `PolicyDenied` | TaskManager | Aggregate decision is false | Inspect per-policy simulation results; do not execute |
| `Not authorized intent sender` | PolicyClient | `msg.sender` does not match `attestation.intent.from` | Ensure the user who signed the intent is the transaction sender |
| `Chain ID does not match` | PolicyClient | Attestation was created for a different chain | Verify the intent specifies the correct `chainId` |
| `Attestation expired` | TaskManager | `block.number >= attestation.expiration` | Request a new attestation; consider increasing `expireAfter` in policy config |
| `Attestation already spent` | TaskManager | Attestation has already been used | Each attestation is single-use; request a new one |
| `InterfaceNotSupported` | PolicyClient | Contract does not implement `IERC165` correctly | Override `supportsInterface` and call `super.supportsInterface(interfaceId)` |
### On-Chain Error Selectors
When `_validateAttestationDirect()` reverts, the error can originate from the PolicyClient, TaskManager, or AttestationValidator. Standard ABI decoders (including viem) may show "Unable to decode signature" because these selectors are not in your contract's ABI.
| Selector | Error | Meaning |
|----------|-------|---------|
| `0xec9758ff` | `InsufficientQuorumStake()` | Operators lack registered on-chain stake |
| `0x716dcc39` | `AttestationExpired()` | Attestation expired before tx was mined |
| `0x297fb567` | `AttestationHashMismatch()` | Task/response hash does not match attestation |
| `0x2a5d4fed` | `AttestationAlreadySpent()` | This attestation was already used |
| `0xf984c150` | `CertificateMessageHashMismatch()` | BLS certificate hash mismatch |
| `0x729d0f6b` | `SignatureVerificationFailed()` | BLS signature invalid |
| `0x90fe746d` | `ClientNotRegistered(address)` | Vault not in PolicyClientRegistry |
Use `cast sig "ErrorName()"` to compute selectors for new errors.
## SDK Error Codes
| Code | Description |
|------|-------------|
| `MISSING_API_KEY` | API key was not provided when initializing wallet client actions |
| `POPUP_ALREADY_EXISTS` | A Newton identity popup is already open |
| `MALFORMED_RESPONSE` | Gateway returned an unparseable response |
| `INVALID_ARGUMENT` | Invalid argument passed to an SDK method |
| `EXTENSION_NOT_INITIALIZED` | Client extension was not properly initialized |
| `INCOMPATIBLE_EXTENSIONS` | Conflicting client extensions detected |
| `FAILED_TO_OPEN_POPUP` | Browser blocked or failed to open the Newton popup |
| `FAILED_TO_RETRIEVE_NATIVE_TOKEN_BALANCE` | Could not read native token balance from the chain |
```ts twoslash
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { sepolia } from 'viem/chains';
import {
newtonWalletClientActions,
SDKError,
SDKErrorCode,
} from '@newton-xyz/sdk';
const walletClient = createWalletClient({
account: privateKeyToAccount('0x' + '1'.repeat(64) as `0x${string}`),
chain: sepolia,
transport: http('https://eth-sepolia.g.alchemy.com/v2/demo'),
}).extend(newtonWalletClientActions({ apiKey: 'test-api-key' }));
const request = {
policyClient: '0x1234567890123456789012345678901234567890',
intent: {
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
to: '0xb1aD5f82407bC0f19f42b2614fb9083035a36b69',
value: '0x0',
data: '0x',
chainId: 11155111,
functionSignature: '0x',
},
wasmArgs: ['0x'] as `0x${string}`[],
timeout: 30,
} as const;
// ---cut---
try {
await walletClient.evaluateIntentDirect(request);
} catch (error) {
if (error instanceof SDKError) {
switch (error.code) {
case SDKErrorCode.MissingApiKey:
// Provide an API key when initializing newtonWalletClientActions
break;
case SDKErrorCode.MalformedResponse:
// Check gateway connectivity and API key validity
break;
}
}
}
```
## HTTP Status Codes
| Status | Description |
|--------|-------------|
| 200 | Success (check JSON-RPC result for application-level errors) |
| 401 | Unauthorized — invalid API key |
| 403 | Forbidden — insufficient permissions |
| 429 | Too Many Requests — rate limited |
| 500 | Internal Server Error |
| 502 | Bad Gateway — service unavailable |
## Diagnostic Tools
* **`newton-cli regorus eval`** — test Rego policies locally with Newton crypto extensions
* **`newton-cli policy simulate`** — test full policy evaluation locally
* **`newton-cli policy-data simulate`** — test WASM oracle execution locally
* **`newt_simulatePolicy`** — test policy evaluation via the Gateway
* **`newt_simulatePolicyData`** — test WASM execution via the Gateway
* **[Newton Explorer](https://explorer.newton.xyz)** — view task status and evaluation results
* **[Explorer API](/developers/reference/explorer-api)** — script task and policy lookups with your API key
# Glossary \[Definitions of key Newton Protocol terms and concepts]
## A
### Attestation
A cryptographic proof (BLS aggregate signature) that Newton operators evaluated a policy and approved or rejected an Intent. Contains the `taskId`, `policyId`, evaluation result, and expiration block. See also: [Core Concepts](/developers/overview/core-concepts).
### AVS (Actively Validated Service)
An EigenLayer service that leverages restaked ETH for economic security. Newton Protocol operates as an AVS, meaning its operators have staked assets that can be slashed for incorrect behavior.
## B
### BLS (Boneh-Lynn-Shacham)
A signature scheme that allows multiple signatures to be aggregated into a single compact proof. Newton operators produce individual BLS signatures that are aggregated by the Aggregator into a single consensus proof.
## C
### CID (Content Identifier)
An IPFS content-addressable hash used to reference policy files, schemas, and metadata stored on IPFS. Generated by `newton-cli policy-files generate-cids`.
## G
### Gateway
The JSON-RPC 2.0 endpoint that receives tasks and coordinates operator evaluation. All SDK and CLI interactions with the Newton network go through the Gateway. See also: [RPC API](/developers/reference/rpc-api).
## H
### HPKE (Hybrid Public Key Encryption)
The encryption scheme (X25519/ChaCha20-Poly1305) used by Newton's privacy layer to encrypt sensitive data before submission to operators.
## I
### Intent
A proposed transaction submitted for policy evaluation. Contains EVM fields: `from`, `to`, `value`, `data`, `chain_id`, and `function_signature`. See also: [Core Concepts](/developers/overview/core-concepts).
## O
### Operator
An EigenLayer node registered with the Newton AVS that independently evaluates tasks. Operators fetch PolicyData, run Rego policies, and produce BLS signatures that are aggregated into consensus proofs.
## P
### Policy
A Rego program that defines the conditions an Intent must meet to be approved. Policies are stored on IPFS and referenced by CID. They access `data.params` (configuration) and `data.wasm` (runtime oracle data). See also: [Writing Policies](/developers/guides/writing-policies).
### Policy set
An ordered list of deployed policies and per-policy configurations owned by a PolicyClient. Every policy must allow. Replacement advances the revision and changes the set ID, invalidating old attestations. Tasks freeze the set at admission.
### PolicyClient
A smart contract that validates Newton attestations before executing transactions. Created by inheriting the `NewtonPolicyClient` Solidity mixin. See also: [Smart Contract Integration](/developers/guides/smart-contract-integration).
### PolicyData
A WASM data oracle that fetches or computes external data at evaluation time. The output is fed to the Rego policy as `data.wasm`. See also: [Writing Data Oracles](/developers/guides/writing-data-oracles).
## R
### Rego
The policy language used by Newton Protocol (originally from Open Policy Agent). Newton supports Rego v1 through its fork of Regorus 0.9.0: an OPA v1.2.0-compatible subset with Newton-specific extensions, not every OPA built-in. Rego policies define declarative rules that evaluate to `true` or `false`. See also: [Rego Syntax Guide](/developers/advanced/rego-syntax-guide).
## T
### Task
The atomic evaluation unit in Newton — pairs an Intent with a frozen ordered policy set, ID, and revision. Each Task is either compliant or non-compliant. See also: [Core Concepts](/developers/overview/core-concepts).
## W
### WASM (WebAssembly)
The compilation target for PolicyData oracles. JavaScript, Rust, or Python code is compiled to WASM components that implement the `newton-provider` WIT interface.
### WIT (WebAssembly Interface Types)
The interface definition language used to define the contract between WASM components and the Newton runtime. The `newton-provider.wit` file specifies the `run` function and `http.fetch` import.
# Rego Syntax Guide \[Supported Rego syntax, builtins, and Newton extensions (crypto, identity) for policy development]
## Language and Engine Compatibility
**Policy language:** Rego v1, evaluated by the Newton Regorus engine. Newton uses its own fork of **Regorus 0.9.0**, configured for Rego v1.
The engine implements an **OPA v1.2.0-compatible subset plus Newton-specific extensions**. It is mostly compatible with OPA v1.2.0 syntax and semantics, but it does not implement every OPA built-in function. Do not describe Newton policies as simply “Rego 1.2” or assume that a policy accepted by OPA v1.2.0 will run unchanged.
:::warning
Check the [unsupported built-ins](#unsupported-opa-built-ins) before porting an OPA policy. In particular, portions of the standard crypto/JWT, GraphQL, JSON Patch, network/CIDR/DNS, AWS provider, Rego metadata, and module-parsing APIs are unavailable. Newton's `newton.*` built-ins extend the runtime but are not one-for-one replacements for every missing OPA built-in.
:::
## Modules & Files
**A module is one policy file.**
* Must start with a package
* Optional imports
* Then one or more **rules**
```rego
package app.auth
import data.roles
import data.helpers as H
default allow = false
```
***
## Rules
## Value Rules (with if/else bodies)
```rego
allow if {
input.user == "admin"
} else if {
input.action == "read"
}
```
* Branching with else
* Each block is a **query** of literals
## Default Rules
```rego
default allow = false
```
* Sets a fallback value if no other rule branch applies
## Function Rules
```rego
allow(u, a) := true if {
u == "admin"
} else if {
a == "read"
}
```
* Rules can take arguments, like functions
## Set & Object Rules
```rego
# Set membership
my_ids[id] if {
id := input.items[_].id
id > 1000
}
# Object construction
user_roles[uid] := role if {
role := data.roles[uid]
}
```
## Comprehension Rules
```rego
allowed_names := [n | u := input.users[_]; u.allowed; n := u.name]
project_ids := {p | p := input.projects[_].id}
role_by_user := { u.id: u.role | u := input.users[_] }
```
***
## Expressions & Queries
## Literals
* Expressions, not exprs
* `some` existential, `every` universal
```rego
some i
not blocked[i]
every u in input.users { u.active }
```
## Assignments & Comparisons
```rego
x := input.value
x in [1,2,3]
count(input.items) >= 5
```
## Arithmetic & Boolean
```rego
total := price * quantity + tax
ok if input.age >= 18 and not input.banned
```
## References & Calls
```rego
v1 := input.user.name
v2 := data.groups["admins"].members
r := helper.add(2,3)
```
## With Modifiers
```rego
allow with data.now as "2025-06-01T00:00:00Z"
```
***
## Collections & Scalars
## Arrays, Objects, Sets
```rego
a := [1,2,3]
o := {"x":1, "y":2}
s := {1,2,3}
```
## Scalars & Variables
```rego
n := 42
s := "hello"
t := true
z := null
```
***
## Quantifiers
* `some` / `every`
```rego
some i in input.items
input.items[i].active
every u in input.users {
u.age >= 18
}
```
***
## Negation
* `not`
```rego
deny if {
not input.authenticated
}
```
***
## Membership
* `in`
```rego
"admin" in input.user.roles
```
***
## Builtins (Supported Categories)
## Aggregates
```rego
n := count(input.items)
sum_ok := sum([1,2,3]) == 6
min_val := min([4,9,1])
```
## Arrays
```rego
array.slice([1,2,3,4], 1, 3) # [2,3]
```
## Sets
```rego
u := union({{1,2},{2,3}})
```
## Objects
```rego
ks := object.keys({"a":1,"b":2})
```
## Strings
```rego
ok := contains("hello world", "world")
```
## Numbers
```rego
cl := ceil(3.2) # 4
```
## Time
```rego
ts := time.parse_rfc3339_ns("2024-01-01T00:00:00Z")
```
## Conversions & Encoding
```rego
j := json.marshal({"x":1})
```
## Regex
```rego
regex.match(`^\\d+$`, "12345")
```
## Semver
```rego
semver.is_valid("1.2.3")
```
***
## Newton Crypto Extensions
Newton extends the standard Rego runtime with custom cryptographic builtins for signature recovery. These are available in the Newton Regorus engine used by operators and the `newton-cli regorus eval` command.
### `newton.crypto.ecdsa_recover_signer`
Recovers the signer address from a raw message hash and ECDSA signature.
```rego
signer := newton.crypto.ecdsa_recover_signer(signature, message_hash)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `signature` | `string` | Hex-encoded ECDSA signature (65 bytes with recovery id) |
| `message_hash` | `string` | Hex-encoded 32-byte message hash |
**Returns:** Hex-encoded Ethereum address of the signer.
### `newton.crypto.ecdsa_recover_signer_personal`
Recovers the signer address from a personal message and ECDSA signature. Applies the EIP-191 `\x19Ethereum Signed Message:\n` prefix before recovery.
```rego
signer := newton.crypto.ecdsa_recover_signer_personal(signature, message)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `signature` | `string` | Hex-encoded ECDSA signature (65 bytes with recovery id) |
| `message` | `string` | The original message string (prefix is applied automatically) |
**Returns:** Hex-encoded Ethereum address of the signer.
### Example
```rego
package auth
default allow = false
allow if {
signer := newton.crypto.ecdsa_recover_signer(input.intent_signature, input.intent_hash)
signer == input.from
}
```
***
## Newton Identity Extensions
Newton extends the Rego runtime with identity verification built-ins for checking user identity data within policies. Identity data is domain-namespaced — the `identity_domain` (bytes32) stored on-chain determines which schema and built-ins apply. Domain is always required.
Two APIs are available:
* **Domain-namespaced built-ins** (primary): `newton.identity.kyc.age_gte(21)`. Type-safe with input validation and specific error messages.
* **Generic field accessor** (escape hatch): `newton.identity.get("field_name")`. Returns the raw field value from the current domain's data. Useful for rapid prototyping with new domains before dedicated built-ins exist.
:::info
Identity data is injected by Newton operators at evaluation time from the on-chain IdentityRegistry. Policy authors do not have direct access to personally identifying information — only the built-in check results (booleans) are exposed to the policy.
:::
### KYC Domain (`newton.identity.kyc.*`)
The KYC domain provides 8 built-ins for verifying Know Your Customer identity data.
#### KYC Data Fields
| Field | Type | Description |
|-------|------|-------------|
| `reference_date` | `YYYY-MM-DD` | Timestamp of "now" for time-based checks |
| `status` | `string` | KYC status: `approved`, `pending`, `completed`, `failed`, `expired`, `declined` |
| `selected_country_code` | ISO 3166-1 alpha-2 | Country selected during KYC process |
| `address_country_code` | ISO 3166-1 alpha-2 | Country from document address |
| `address_subdivision` | `string` | State/province from document address |
| `birthdate` | `YYYY-MM-DD` | Date of birth |
| `expiration_date` | `YYYY-MM-DD` | Document expiration date |
| `issue_date` | `YYYY-MM-DD` | Document issuance date |
| `issuing_authority` | `string` | Issuing country or state |
#### `newton.identity.kyc.check_approved`
Returns `true` if the identity status is `"approved"`.
```rego
result := newton.identity.kyc.check_approved()
```
No parameters. Returns `bool`.
#### `newton.identity.kyc.address_in_countries`
Checks if the document's address country code is in the provided list.
```rego
result := newton.identity.kyc.address_in_countries(["US", "CA", "DE"])
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `countries` | `string[]` | Array of ISO 3166-1 alpha-2 country codes |
Returns `bool`. Errors if the array is empty or the stored country code is empty.
#### `newton.identity.kyc.address_in_subdivision`
Checks if the document's address subdivision is in the provided list.
```rego
result := newton.identity.kyc.address_in_subdivision(["US-CA", "US-OR", "US-WA"])
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `subdivisions` | `string[]` | Array of ISO subdivision codes (`CC-SS` format, e.g., `US-CA`) |
Returns `bool`. The check concatenates `address_country_code` + `-` + `address_subdivision` and matches against the list. Errors if the array is empty or country/subdivision fields are empty.
#### `newton.identity.kyc.address_not_in_subdivision`
Inverse of `address_in_subdivision`. Returns `true` if the address is NOT in the list.
```rego
result := newton.identity.kyc.address_not_in_subdivision(["US-NY", "US-NC"])
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `subdivisions` | `string[]` | Array of ISO subdivision codes to exclude |
Returns `bool`. Useful when combined with `address_in_countries` to include a country but exclude specific states.
#### `newton.identity.kyc.age_gte`
Checks if the person's age (calculated from `birthdate` to `reference_date`) is at least the specified years.
```rego
result := newton.identity.kyc.age_gte(21)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `min_age` | `number` | Minimum age in years (must be positive) |
Returns `bool`. Errors if `min_age` is not positive or dates cannot be parsed.
#### `newton.identity.kyc.not_expired`
Returns `true` if the document expiration date has not passed relative to `reference_date`.
```rego
result := newton.identity.kyc.not_expired()
```
No parameters. Returns `bool`.
#### `newton.identity.kyc.valid_for`
Checks if the document will remain valid for at least the specified number of days.
```rego
result := newton.identity.kyc.valid_for(365)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `min_days` | `number` | Minimum days of remaining validity (must be positive) |
Returns `bool`. Compares `(expiration_date - reference_date)` against the provided day count.
#### `newton.identity.kyc.issued_since`
Checks if the document was issued at least the specified number of days ago.
```rego
result := newton.identity.kyc.issued_since(90)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `min_days` | `number` | Minimum days since issuance (must be positive) |
Returns `bool`. Compares `(reference_date - issue_date)` against the provided day count.
### Generic Field Accessor (`newton.identity.get`)
Works across any identity domain. Returns the raw field value by name from the current domain's data. Returns `undefined` if the field does not exist, allowing Rego `default` patterns.
```rego
result := newton.identity.get("field_name")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `field_name` | `string` | Field name to look up |
Returns the field value (any type), or `undefined` if not found.
When multiple identity domains are registered, all domains' fields are accessible through this single accessor.
### KYC Policy Example
```rego
package kyc_compliance
default allow = false
# Allow transactions from approved US/CA users over 21 with valid documents
allow if {
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(21)
newton.identity.kyc.address_in_countries(["US", "CA"])
newton.identity.kyc.address_not_in_subdivision(["US-NY", "US-NC"])
newton.identity.kyc.not_expired()
newton.identity.kyc.valid_for(90)
newton.identity.kyc.issued_since(30)
}
```
### Mixed Domain and Generic Accessor Example
```rego
package mixed_check
default allow = false
# Combine domain-specific built-ins with generic field access
allow if {
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(18)
newton.identity.get("address_country_code") == "US"
}
```
### Adding New Identity Domains
The identity extension system is designed for new domains beyond KYC. Each domain (social, credit, professional, etc.) defines its own data struct, Rego built-ins, and field accessors. New domains register under `newton.identity..*` and merge their fields into the shared `newton.identity.get` accessor.
Until domain-specific built-ins are available for a new domain, policy authors can use `newton.identity.get("field_name")` to access any field from the domain's data.
***
## Privacy Extensions (`newton.privacy.*`)
Provider-managed confidential data (blacklists, allowlists, sanctions lists) uploaded via the `ConfidentialDataRegistry`. Operators fetch and decrypt this data at task time based on the `confidential_domain` in `policyParams`.
Two access patterns:
* **Domain-namespaced builtins** (primary): `newton.privacy.blacklist.contains(addr)`. Type-safe with address normalization.
* **Generic field accessor** (escape hatch): `newton.privacy.get("field_name")`. Returns raw field values across all registered privacy domains.
### Blacklist Domain (`newton.privacy.blacklist.*`)
#### `newton.privacy.blacklist.contains`
```rego
result := newton.privacy.blacklist.contains(address)
```
Returns `true` if `address` is in the provider's blacklist. Addresses are normalized to lowercase hex before comparison.
#### `newton.privacy.blacklist.count`
```rego
result := newton.privacy.blacklist.count()
```
Returns the number of addresses in the blacklist.
### Allowlist Domain (`newton.privacy.allowlist.*`)
#### `newton.privacy.allowlist.contains`
```rego
result := newton.privacy.allowlist.contains(address)
```
Returns `true` if `address` is in the provider's allowlist.
#### `newton.privacy.allowlist.count`
```rego
result := newton.privacy.allowlist.count()
```
Returns the number of addresses in the allowlist.
### Generic Field Accessor (`newton.privacy.get`)
```rego
value := newton.privacy.get("field_name")
```
Returns `undefined` if the field does not exist, which allows `default` rules to apply.
### Privacy Policy Examples
#### Blacklist check (deny blacklisted senders)
```rego
package sanctions_check
default allow := false
allow if {
not newton.privacy.blacklist.contains(input.from)
}
```
#### Allowlist check (only allow approved addresses)
```rego
package allowlist_only
default allow := false
allow if {
newton.privacy.allowlist.contains(input.from)
}
```
#### Combined identity + privacy check
```rego
package compliant_transfer
default allow := false
allow if {
# User must pass KYC
newton.identity.kyc.check_approved()
newton.identity.kyc.age_gte(18)
# Sender must not be blacklisted
not newton.privacy.blacklist.contains(input.from)
# Recipient must be on allowlist
newton.privacy.allowlist.contains(input.to)
}
```
***
## Time Extensions (`newton.time.*`)
Date arithmetic builtins for time-based policy checks. All dates use `YYYY-MM-DD` format strings.
### `newton.time.days_between`
```rego
result := newton.time.days_between(date_a, date_b)
```
Returns the absolute number of days between two dates.
### `newton.time.days_since`
```rego
result := newton.time.days_since(past_date, reference_date)
```
Returns positive days if `past_date` is before `reference_date`.
### `newton.time.is_within_days`
```rego
result := newton.time.is_within_days(date, reference_date, max_days)
```
Returns `true` if the absolute difference between `date` and `reference_date` is at most `max_days`.
### `newton.time.is_before` / `newton.time.is_after`
```rego
result := newton.time.is_before(date_a, date_b)
result := newton.time.is_after(date_a, date_b)
```
### `newton.time.age_years`
```rego
result := newton.time.age_years(birthdate, reference_date)
```
Returns the number of complete years between `birthdate` and `reference_date`. Useful for age verification without the KYC identity domain.
### Time Policy Example
```rego
package time_check
default allow := false
# Allow if document was issued within the last 90 days
allow if {
newton.time.is_within_days(data.issued_date, data.reference_date, 90)
}
```
***
## Unsupported OPA Built-ins
* **Standard Crypto / Tokens / JWT**: portions of `crypto.*`, `jwtverify*`, and `jwtencode*` are unavailable. Newton supplies specific `newton.crypto.*` functions, but they are not complete replacements for the OPA APIs.
* **HTTP**: `http.send` — not implemented (use PolicyData WASM oracles for external data)
* **GraphQL**: `graphql.*` — not implemented
* **Glob matching**: `regex.globs_match` — not implemented
* **JSON Patch**: `json.patch` — not implemented
* **Networking / CIDR / DNS**: portions of `net.*` and related network functions are not implemented
* **AWS Providers**: `providers.aws.*` — not implemented
* **Rego Meta**: `rego.metadata.*`, `rego.parse_module` — not implemented
* **Template rendering**: `strings.render_template` — not implemented
This list highlights known gaps rather than promising that every unlisted OPA built-in is available. Validate the exact functions your policy uses with `newton-cli regorus parse` and `newton-cli regorus eval`.
# Encrypting Secrets \[Securely store API keys and credentials for Newton Protocol PolicyData oracles]
PolicyData oracles often need access to API keys and credentials to fetch external data. Newton Protocol's [Privacy Layer](/developers/concepts/privacy-layer) encrypts these secrets client-side using HPKE (X25519 + HKDF-SHA256 + ChaCha20-Poly1305) so they are never exposed on-chain or to unauthorized parties.
## Overview
The secret storage flow:
1. **Prepare your secrets** as a JSON object matching your PolicyData's schema
2. **Call `storeEncryptedSecrets`** from the SDK — it encrypts the plaintext locally and uploads the HPKE envelope
3. **Operators decrypt** the envelope during task evaluation to run your WASM oracle
Secrets are validated against the on-chain schema (`secretsSchemaCid`) defined on your PolicyData contract.
## Secrets Format
Prepare a JSON object matching your PolicyData's secrets schema:
```json
{
"API_KEY": "sk-xxxxxxxxxxxxx",
"ENDPOINT": "https://api.example.com"
}
```
## Store via SDK
Use the `storeEncryptedSecrets` function from the SDK. It fetches the gateway's HPKE public key automatically (or accepts one you've cached), encrypts the plaintext locally, and uploads the envelope in a single call.
```ts twoslash
import { storeEncryptedSecrets } from '@newton-xyz/sdk'
const result = await storeEncryptedSecrets(
11155111, // chainId
'your-api-key',
{
policyClient: '0xYourPolicyClientAddress',
policyDataAddress: '0xYourPolicyDataAddress',
plaintext: {
API_KEY: 'sk-xxxxxxxxxxxxx',
ENDPOINT: 'https://api.example.com',
},
chainId: 11155111,
// recipientPublicKey: '...' — optional, fetched automatically if omitted
},
)
if (!result.success) {
throw new Error(`Upload failed: ${result.error}`)
}
```
Secrets are validated against the on-chain schema before storing.
## Reference in PolicyData
Your WASM oracle reads secrets at runtime through the `newton:provider/secrets` host interface. Operators decrypt the stored envelope before execution; `get()` returns the decrypted secrets JSON as raw bytes, which you decode and parse.
```js
// In your WASM oracle (policy.js)
import { fetch as httpFetch } from "newton:provider/http@0.2.0";
import { get as getHostSecrets } from "newton:provider/secrets@0.2.0";
let _secrets = {};
function loadHostSecrets() {
const r = getHostSecrets(); // result
const resp = r?.val ?? r;
const bytes = resp?.value; // value: list — decrypted secrets JSON
if (!bytes || bytes.length === 0) return;
const text = new TextDecoder().decode(new Uint8Array(bytes));
_secrets = { ..._secrets, ...JSON.parse(text) };
}
export function run(wasm_args) {
loadHostSecrets();
const response = httpFetch({
url: "https://api.example.com/data",
method: "GET",
headers: [["Authorization", `Bearer ${_secrets.API_KEY}`]],
body: null,
});
if (response.tag === "err") {
return JSON.stringify({ error: response.val });
}
const body = new TextDecoder().decode(new Uint8Array(response.val.body));
return JSON.stringify(JSON.parse(body));
}
```
:::note
For the current deployment and secret-scoping workflow, see [Deploying with CLI](/developers/guides/deploying-with-cli).
:::
## Secrets Schema
The `secretsSchemaCid` on your PolicyData contract points to a JSON Schema that defines the required shape of your secrets:
```json
{
"type": "object",
"properties": {
"API_KEY": { "type": "string" },
"ENDPOINT": { "type": "string", "format": "uri" }
},
"required": ["API_KEY", "ENDPOINT"]
}
```
If your uploaded secrets do not match this schema, `newt_storeEncryptedSecrets` returns a validation error.
## Security
* Secrets are encrypted client-side before leaving your application — the HPKE envelope is what travels over the wire
* The gateway decrypts the envelope in memory to validate against the schema, then stores the envelope (not the plaintext)
* Only the on-chain owner of the PolicyClient can upload or update secrets
* Operators decrypt the stored envelope locally during task evaluation — plaintext secrets are never persisted by operators and never sent over the network
* Secrets are scoped **per `policy_data_address`** — redeploying a PolicyData contract produces a new address, so re-upload its secrets. In a multi-oracle policy, upload each oracle's secrets to its own PolicyData address
* Key rotation is supported — upload new secrets at any time via `newt_storeEncryptedSecrets`
For a deeper look at Newton's encryption architecture, see the [Privacy Layer](/developers/concepts/privacy-layer).
## Testing
Test your secrets work correctly before deploying:
1. **Test with inline secrets** (no ownership required):
```bash
# newt_simulatePolicyData — provide secrets directly
```
2. **Test with stored secrets** (requires ownership):
```bash
# newt_simulatePolicyDataWithClient — uses stored secrets
```
See the [RPC API](/developers/reference/rpc-api) for full parameter details.
## Next Steps
Full privacy architecture overview
newt\_storeEncryptedSecrets and related methods
# Policy Data Oracles \[With Newton Protocol, you can integrate data from other sources into your policy]
These providers offer external signals that can be evaluated by a Newton Policy, from KYC and sanctions screening to historical APY. A deployed Policy contains its Rego logic, parameters, and optionally a WASM oracle that fetches or computes the signal at evaluation time.
## Identity
### Veriff
Veriff enables developers to build policies that use KYC data — specifically a user's country of residence.
* [Example use cases](https://blog.newton.xyz/newton-protocol-launches-veriff-data-oracle-to-bring-kyc-and-identity-verification-onchain/)
### Persona
Persona is another service that can assist with applications that require KYC. In addition to country of residence, data such as a bot score and state of residence can be used as part of a policy.
* [Example use cases](https://blog.newton.xyz/newton-protocol-integrates-persona-to-enable-real-time-identity-and-jurisdictional-compliance/)
## Sanctions & Wallet Screening
### Magic Labs
The security signals data policy provides a risk score for transacting with an address. This leverages onchain signals to determine how likely an address is to be malicious.
* [Example use cases](https://blog.newton.xyz/newton-protocol-integrates-magic-labs-wallet-risk-data-to-enforce-pre-transaction-compliance/)
### Chainalysis
Chainalysis provides useful insights into whether an address is sanctioned or not. This is valuable when complying with US OFAC sanctions regulations.
### [Human.tech](https://human.tech)
Human Passport (part of the broader [human.tech](https://human.tech) suite of products) provides the infrastructure layer for verifying unique personhood across web3. Use it to implement sophisticated anti-Sybil and compliance controls without sacrificing user privacy or protocol decentralization.
* [Reference implementation](https://github.com/newt-foundation/quickstart-policies/tree/main/human.tech)
* [Example use cases](https://blog.newton.xyz/newton-protocol-integrates-human-passport-to-power-onchain-humanity-verification/)
## Transaction Controls
### [Vaults.fyi](https://vaults.fyi)
Vaults is a powerful service that returns data around yield and assets for particular vaults. This is useful for building AI agents that are seeking to optimize yield.
* [Example use cases](https://blog.newton.xyz/newton-protocol-integrates-vaults-fyi-data-to-power-ai-trading-guardrails/)
### [Massive](https://massive.com/)
Massive (fka Polygon.io) is a sevice that provides a wealth of macroeconomic data. This policy data gives insights into US Treasury yields, useful if building an application that is protected against interest rate movement.
### Etherscan
Gas price data can be retrieved from Etherscan and used as part of a policy. This is useful as a final check against expensive gas prices for transaction automation senarios.
* [Example use cases](https://blog.newton.xyz/newton-protocol-integrates-etherscan-data-to-power-real-time-transaction-guardrails/)
## Social
### Neynar
Neynar is a data provider for Farcaster, a decentralized social media protocol. Their data is useful when building an agent that uses a user's social score to make purchase decisions. For example, data like a bot score or follower count can be used as part of a policy.
## Use a Deployed Oracle
To use an oracle-backed Policy in an application:
1. Choose or deploy the Policy whose WASM oracle supplies the signal you need.
2. Configure that Policy as an entry in the PolicyClient's ordered set with `setPolicies`. Each entry has its own parameter bytes and expiration window. See [Smart Contract Integration](/developers/guides/smart-contract-integration#configure-the-policy-set).
3. Upload any required API credentials as PolicyClient-scoped secrets. See [Deploying with CLI](/developers/guides/deploying-with-cli).
4. Pass the oracle input in the Policy's positional `wasmArgs` entry when simulating or evaluating an intent.
5. Read the oracle output in that Policy's Rego through `data.wasm`:
```rego
risk_score := data.wasm.risk_score
allow if risk_score < 50
```
Use `newt_simulatePolicy` to inspect the Policy's result before live evaluation. For multiple independent checks, configure each Policy in the set and preserve their input order; every Policy must allow for the aggregate result to allow. See [Composing Policy Sets](/developers/guides/composing-policy-sets).
For a full end-to-end walkthrough, see the [Integration Guide](/developers/guides/integration-guide).
# Writing Data Oracles in Python \[This guide walks you through building a Python newton-provider WebAssembly data oracle using componentize-py.]
## 0) Prerequisites
* **Python 3.10+** and **pip** installed
* A terminal on macOS/Linux/WSL (Windows PowerShell also works)
> **Tip:** Use a virtual environment to keep dependencies isolated:
```bash
python -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
```
***
## 1) Create a project folder
```bash
mkdir my_project
cd my_project
```
***
## 2) Install the CLI
```bash
pip install componentize-py
```
***
## 3) Add the WIT world
Create **`newton-provider.wit`** in the project root:
```wit
package newton:provider@0.2.0;
// HTTP host interface
interface http {
record http-request {
url: string,
method: string,
headers: list>,
body: option>,
}
record http-response {
status: u16,
headers: list>,
body: list,
}
fetch: func(request: http-request) -> result;
}
// Secrets host interface
interface secrets {
record secret-response {
// Raw bytes of the decrypted secrets JSON object.
// The scope (policy_client, policy_data) is bound by the host execution context.
value: list,
}
get: func() -> result;
}
// [NEW] TLSNotary verification interface
interface tlsn {
record verified-data {
/// Authenticated server name (e.g. "api.x.com")
server-name: string,
/// Unix timestamp (seconds) of the TLS connection
connection-time: u64,
/// Sent transcript bytes (unauthenticated bytes masked as 0x58 'X')
sent-transcript: list,
/// Received transcript bytes (unauthenticated bytes masked as 0x58 'X')
received-transcript: list,
/// SHA-256 fingerprint of the presentation notary verifying key, hex-encoded.
notary-key-fingerprint: string,
}
/// Download a TLSNotary Presentation from IPFS by CID, verify it, and return authenticated data.
///
/// The host implementation:
/// 1. Downloads from IPFS (5 MiB cap — returns error if exceeded)
/// 2. Re-verifies the CID multihash against the downloaded bytes (defends against malicious IPFS gateways)
/// 3. BCS-deserializes the bytes into a Presentation
/// 4. Calls verify_presentation() from newton-tls-notary
///
/// This bypasses the WASM HTTP 1 MiB limit since the host fetches directly.
verify-from-cid: func(proof-cid: string) -> result;
/// Verify a TLSNotary Presentation from raw BCS-serialized bytes.
///
/// Useful for small proofs or testing where bytes are already available.
verify: func(presentation-bytes: list) -> result;
}
world newton-provider {
import http;
import secrets;
import tlsn;
export run: func(input: string) -> result;
}
```
***
## 4) Generate Python bindings
This produces a `wit_world` package with types and imports for your WIT:
```bash
componentize-py -d newton-provider.wit -w newton-provider bindings py_bindings
```
Resulting project structure (key parts):
```
my_project/
├─ newton-provider.wit
└─ py_bindings/
├─ wit_world/ # generated Python bindings
└─ app.py # (you will add this file next)
```
***
## 5) Implement your component logic
Create **`py_bindings/app.py`**:
```python
from json import loads, dumps
import wit_world
from wit_world.imports import http
from wit_world.imports.http import HttpRequest, HttpResponse
# WIT: export run: func(input: string) -> result
# We return a JSON string on success AND on "errors"
# (i.e., we don't surface WIT Err — we encode error info in JSON)
class WitWorld(wit_world.WitWorld):
def run(self, input: str) -> str:
req = loads(input)
# Fetch ETH price from CoinGecko
request = HttpRequest(
url="https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd",
method="GET",
headers=[],
body=None,
)
result = http.fetch(request)
if isinstance(result, http.Err):
return dumps({"error": str(result)})
body = bytes(result.value.body).decode("utf-8")
data = loads(body)
return dumps({
"eth_price_usd": data["ethereum"]["usd"],
})
```
> **Note:** Keep all imports at the top of the file; don’t import inside functions.
***
## 6) Build the component
From inside the **`py_bindings/`** directory, build the component:
```bash
cd py_bindings
componentize-py -d ../newton-provider.wit -w newton-provider componentize --stub-wasi app -o ../policy.wasm
```
You’ll get `policy.wasm` in the project root — a component that:
* **Imports** `newton:provider/http.fetch` from the host
* **Exports** `run(input: string) -> result`
***
## 7) Test your component
Use the Newton CLI to simulate your WASM data provider locally without deploying to the blockchain:
```bash
newton-cli --chain-id 11155111 policy-data simulate \
--wasm-file policy.wasm \
--input-json '{"inquiry_id": "inq_xRZrQFKg7rqZ5UZGLhnvb2ympshE"}'
```
The `--input-json` value is passed directly to your component's `run` function as the `input` string argument. Replace the example JSON with your own input schema.
For more options, see the [Newton CLI reference](/developers/reference/command-line-tool#simulate).
# Writing Data Oracles in JavaScript \[This guide walks you through building a JavaScript newton-provider WebAssembly component using ComponentizeJS (@bytecodealliance/componentize-js).]
## 0) Prerequisites
* **Node.js 18+** and **npm** installed
* A terminal on macOS/Linux/WSL (Windows PowerShell also works)
> **Tip:** Use nvm to manage Node versions:
```bash
# macOS/Linux example
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install --lts
nvm use --lts
```
***
## 1) Create a project folder
```bash
mkdir my_project
cd my_project
```
***
## 2) Install CLI
```bash
npm install -g @bytecodealliance/componentize-js
```
***
## 3) Add the WIT world
Create **`newton-provider.wit`** in the project root:
```wit
package newton:provider@0.2.0;
// HTTP host interface
interface http {
record http-request {
url: string,
method: string,
headers: list>,
body: option>,
}
record http-response {
status: u16,
headers: list>,
body: list,
}
fetch: func(request: http-request) -> result;
}
// Secrets host interface
interface secrets {
record secret-response {
// Raw bytes of the decrypted secrets JSON object.
// The scope (policy_client, policy_data) is bound by the host execution context.
value: list,
}
get: func() -> result;
}
// [NEW] TLSNotary verification interface
interface tlsn {
record verified-data {
/// Authenticated server name (e.g. "api.x.com")
server-name: string,
/// Unix timestamp (seconds) of the TLS connection
connection-time: u64,
/// Sent transcript bytes (unauthenticated bytes masked as 0x58 'X')
sent-transcript: list,
/// Received transcript bytes (unauthenticated bytes masked as 0x58 'X')
received-transcript: list,
/// SHA-256 fingerprint of the presentation notary verifying key, hex-encoded.
notary-key-fingerprint: string,
}
/// Download a TLSNotary Presentation from IPFS by CID, verify it, and return authenticated data.
///
/// The host implementation:
/// 1. Downloads from IPFS (5 MiB cap — returns error if exceeded)
/// 2. Re-verifies the CID multihash against the downloaded bytes (defends against malicious IPFS gateways)
/// 3. BCS-deserializes the bytes into a Presentation
/// 4. Calls verify_presentation() from newton-tls-notary
///
/// This bypasses the WASM HTTP 1 MiB limit since the host fetches directly.
verify-from-cid: func(proof-cid: string) -> result;
/// Verify a TLSNotary Presentation from raw BCS-serialized bytes.
///
/// Useful for small proofs or testing where bytes are already available.
verify: func(presentation-bytes: list) -> result;
}
world newton-provider {
import http;
import secrets;
import tlsn;
export run: func(input: string) -> result;
}
```
***
## 4) Implement your component logic
Create **`app.js`**:
```js
// Import the WIT import exactly like the ComponentizeJS usage pattern:
// e.g. import { log } from 'local:hello/logger' in their docs.
// Here our package is newton:provider and interface is http.
import { fetch as httpFetch } from "newton:provider/http@0.2.0";
// WIT: export run: func(input: string) -> result
// We return a JSON string on success AND on "errors"
// (i.e., we don't surface WIT Err — we encode error info in JSON)
export function run(input) {
const req = JSON.parse(input);
// Fetch ETH price from CoinGecko
const response = httpFetch({
url: "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd",
method: "GET",
headers: [],
body: null,
});
if (response.tag === "err") {
return JSON.stringify({ error: response.val });
}
const body = new TextDecoder().decode(new Uint8Array(response.val.body));
const data = JSON.parse(body);
return JSON.stringify({
eth_price_usd: data.ethereum.usd,
timestamp: Date.now(),
});
}
```
> **Note:** Keep this import at the top level of your module; ComponentizeJS resolves virtual specifiers like `'newton:provider/http@0.2.0'` when you build the component.
***
## 5) Build the component
```bash
componentize-js --wit newton-provider.wit -o policy.wasm app.js -d stdio random clocks http fetch-event
```
This produces `policy.wasm` in the project root — a component that:
* **Imports** `newton:provider/http.fetch` from the host
* **Exports** `run(input: string) -> result`
Resulting project tree:
```
my_project/
├─ policy.wasm
├─ newton-provider.wit
└─ app.js
```
***
## 6) (Alternative) Programmatic build
Create **`componentize.mjs`**:
```js
import { componentize } from "@bytecodealliance/componentize-js";
import { readFile, writeFile } from "node:fs/promises";
const { component, imports } = await componentize({
sourcePath: "./app.js", // your JS file
witPath: "./newton-provider.wit", // WIT file path
worldName: "newton-provider", // the world in your WIT
});
await writeFile("policy.wasm", component);
console.log("guest imports:", imports);
```
Run it with:
```bash
node componentize.mjs
```
***
## 7) Test your component
Use the Newton CLI to simulate your WASM data provider locally without deploying to the blockchain:
```bash
newton-cli --chain-id 11155111 policy-data simulate \
--wasm-file policy.wasm \
--input-json '{"inquiry_id": "inq_xRZrQFKg7rqZ5UZGLhnvb2ympshE"}'
```
The `--input-json` value is passed directly to your component's `run` function as the `input` string argument. Replace the example JSON with your own input schema.
For more options, see the [Newton CLI reference](/developers/reference/command-line-tool#simulate).
# Writing Data Oracles in Rust \[This guide provides step-by-step instructions for building a WASI-compatible WebAssembly component from Rust code using the WebAssembly Interface Types (WIT) format.]
## Prerequisites
1. **Rust Toolchain**
* Install Rust via [rustup](https://rustup.rs/)
* Version: 1.75.0 or later
2. **WASM Targets**
```bash
# Install the WASI Preview 1 target
rustup target add wasm32-wasip1
# Optionally, install WASI Preview 2 target for future compatibility
rustup target add wasm32-wasip2
```
3. **Required Tools**
```bash
# Install cargo-component for building WASM components
cargo install cargo-component
# Install wasm-tools for validation and inspection
cargo install wasm-tools
```
***
## Project Structure
```
component/
├── Cargo.toml # Rust project configuration
├── src/
│ └── lib.rs # Main Rust implementation
└── wit/
└── world.wit # WebAssembly Interface Types definition
```
***
## Step-by-Step Build Process
### Step 1: Define the WIT Interface
Create a WIT file (`wit/world.wit`) compatible with Newton Protocol:
```wit
package newton:provider@0.2.0;
// HTTP host interface
interface http {
record http-request {
url: string,
method: string,
headers: list>,
body: option>,
}
record http-response {
status: u16,
headers: list>,
body: list,
}
fetch: func(request: http-request) -> result;
}
// Secrets host interface
interface secrets {
record secret-response {
// Raw bytes of the decrypted secrets JSON object.
// The scope (policy_client, policy_data) is bound by the host execution context.
value: list,
}
get: func() -> result;
}
// [NEW] TLSNotary verification interface
interface tlsn {
record verified-data {
/// Authenticated server name (e.g. "api.x.com")
server-name: string,
/// Unix timestamp (seconds) of the TLS connection
connection-time: u64,
/// Sent transcript bytes (unauthenticated bytes masked as 0x58 'X')
sent-transcript: list,
/// Received transcript bytes (unauthenticated bytes masked as 0x58 'X')
received-transcript: list,
/// SHA-256 fingerprint of the presentation notary verifying key, hex-encoded.
notary-key-fingerprint: string,
}
/// Download a TLSNotary Presentation from IPFS by CID, verify it, and return authenticated data.
///
/// The host implementation:
/// 1. Downloads from IPFS (5 MiB cap — returns error if exceeded)
/// 2. Re-verifies the CID multihash against the downloaded bytes (defends against malicious IPFS gateways)
/// 3. BCS-deserializes the bytes into a Presentation
/// 4. Calls verify_presentation() from newton-tls-notary
///
/// This bypasses the WASM HTTP 1 MiB limit since the host fetches directly.
verify-from-cid: func(proof-cid: string) -> result;
/// Verify a TLSNotary Presentation from raw BCS-serialized bytes.
///
/// Useful for small proofs or testing where bytes are already available.
verify: func(presentation-bytes: list) -> result;
}
world newton-provider {
import http;
import secrets;
import tlsn;
export run: func(input: string) -> result;
}
```
***
### Step 2: Configure Cargo.toml
Set up your `Cargo.toml` for WASM component compilation:
```toml
[package]
name = "eligibility-checker"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # Required for WASM components
[dependencies]
wit-bindgen = "0.36" # WIT bindings generator
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[package.metadata.component]
package = "newton:provider"
[package.metadata.component.dependencies]
# Add any WIT dependencies here
```
***
### Step 3: Implement the Rust Code
In `src/lib.rs`, generate bindings and implement your functionality:
```rust
use serde::{Deserialize, Serialize};
use serde_json;
// Generate bindings from the WIT file
wit_bindgen::generate!({
world: "newton-provider", // Must match the world name in WIT
path: "wit", // Path to WIT files
});
// Export the implementation
export!(YourImplementation);
struct YourImplementation;
impl Guest for YourImplementation {
fn run(input: String) -> Result {
// Fetch ETH price from CoinGecko
let request = newton::provider::http::HttpRequest {
url: "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"
.to_string(),
method: "GET".to_string(),
headers: vec![],
body: None,
};
let response = newton::provider::http::fetch(&request)
.map_err(|e| format!("HTTP fetch failed: {e}"))?;
let body = String::from_utf8(response.body)
.map_err(|e| format!("Invalid UTF-8: {e}"))?;
let data: serde_json::Value = serde_json::from_str(&body)
.map_err(|e| format!("JSON parse error: {e}"))?;
let price = data["ethereum"]["usd"]
.as_f64()
.ok_or("Missing price field")?;
Ok(serde_json::json!({
"eth_price_usd": price
}).to_string())
}
}
```
***
### Step 4: Build the WASM Component
#### Option A: Using cargo-component (Recommended)
```bash
# Build the component
cargo component build --release
# Output location:
# target/wasm32-wasip1/release/.wasm
```
#### Option B: Using standard cargo
```bash
# Build for WASI Preview 1
cargo build --target wasm32-wasip1 --release
# Output location:
# target/wasm32-wasip1/release/.wasm
```
***
### Step 5: Validate the WASM Component
```bash
# Validate the WASM module
wasm-tools validate target/wasm32-wasip1/release/.wasm
# Inspect the component's WIT interface
wasm-tools component wit target/wasm32-wasip1/release/.wasm
# Check the component's imports and exports
wasm-tools component new target/wasm32-wasip1/release/.wasm \
-o component.wasm --adapt wasi_snapshot_preview1.wasm
```
***
### Step 6: Test your component
Use the Newton CLI to simulate your WASM data provider locally without deploying to the blockchain:
```bash
newton-cli --chain-id 11155111 policy-data simulate \
--wasm-file target/wasm32-wasip1/release/.wasm \
--input-json '{}'
```
Replace `` with your crate name and `--input-json` with your component's expected input schema.
For more options, see the [Newton CLI reference](/developers/reference/command-line-tool#simulate).
# FAQ & Troubleshooting \[Frequently asked questions and solutions to common issues with Newton Protocol — API keys, policy evaluation, chain support, error codes, and debugging.]
## General FAQ
:::details[What is Newton Protocol?]
Newton Protocol is a decentralized policy engine for onchain transaction authorization, built as an EigenLayer Actively Validated Service (AVS). It lets you encode, verify, and enforce rules — such as spend limits, sanctions screening, or KYC gates — directly within smart contracts. See [What is Newton?](/developers/overview/about) for a full overview.
:::
:::details[What chains does Newton support?]
Newton currently supports **Ethereum Sepolia** (`11155111`) as both a source and destination chain, plus policy execution on **Base** (`8453`) and **Base Sepolia** (`84532`). See [Supported Networks](/developers/concepts/multichain#supported-networks).
:::
:::details[How do I get an API key?]
Go to [dashboard.newton.xyz](https://dashboard.newton.xyz/), sign in, and click **API Keys** in the left navigation — your key is already generated. See [Getting an API Key](/developers/overview/dashboard-api-keys).
:::
:::details[What programming languages can I use for data oracles?]
Newton data oracles compile to WebAssembly (WASM) components. You can write them in:
* [JavaScript](/developers/advanced/javascript-wasm-guide) (using ComponentizeJS)
* [Python](/developers/advanced/python-wasm-guide) (using componentize-py)
* [Rust](/developers/advanced/rust-wasm-guide) (using cargo-component)
Any language that compiles to WASM and supports the WIT interface can be used.
:::
:::details[What is the difference between Standard and Direct validation?]
**Standard validation** (`_validateAttestation`) uses the PolicyClientRegistry to automatically resolve your contract's policy configuration. It costs more gas but is simpler to use.
**Direct validation** (`_validateAttestationDirect`) bypasses the registry. It uses less gas but requires your contract to manage its own policy reference.
See [Validation Methods](/developers/overview/core-concepts#validation-methods) for details.
:::
:::details[How long does policy evaluation take?]
Two-phase consensus (Prepare-Commit) takes approximately **4 seconds**. See [Consensus & Security](/developers/concepts/consensus-security).
:::
:::details[Can I test policies without deploying on-chain?]
Yes. Use SDK `simulatePolicy` or Gateway `newt_simulatePolicy` to run the configured set’s optional WASM and Rego without an application transaction or BLS attestation. You can also test individual components:
* `newt_simulatePolicy` — test Rego policy logic with provided inputs
* `newt_simulatePolicyData` — test a WASM data oracle
* `newton-cli regorus eval` — evaluate Rego locally with Newton crypto extensions
See [Testing & Debugging](/developers/resources/testing-debugging).
:::
:::details[What is the difference between a Policy and a PolicyClient?]
A **Policy** is a reusable Rego program that defines evaluation logic (e.g., "block transactions over $10,000"). A **PolicyClient** is a smart contract that references a specific policy and its configuration parameters (thresholds, allowlists). Multiple PolicyClients can use the same Policy with different parameters. See [Core Concepts](/developers/overview/core-concepts).
:::
## Common Errors
:::details[TaskAlreadyExists — duplicate task submission]
**Cause:** You submitted a task with the same intent hash that is already being evaluated.
**Fix:** Wait for the existing task to complete, or modify the intent (e.g., change the nonce or timestamp) to produce a different hash.
:::
:::details[InvalidAttestation — on-chain verification failed]
**Cause:** The attestation proof failed BLS signature verification on-chain.
**Common reasons:**
* The attestation has expired (check `expiration` field)
* The operator set has rotated since the attestation was produced
* The attestation was produced for a different chain
**Fix:** Request a fresh attestation and submit it before expiration. Ensure the `chainId` in your intent matches the chain where the PolicyClient is deployed.
:::
:::details[PolicyNotFound (-32617) — policy not found]
**Cause:** The Gateway could not find the policy associated with the provided PolicyClient address.
**Fix:** Verify:
1. The PolicyClient address is correct and deployed on the expected chain
2. The PolicyClient has a policy set (use `newton-cli policy-client status`)
3. The PolicyClient is registered with the PolicyClientRegistry
:::
:::details[Unauthorized (-32615) — authentication failed]
**Cause:** Your API key is missing, invalid, or lacks the required permission.
**Fix:**
* Ensure your `Authorization: Bearer ` header is present
* Check that your key has the required permission level (`rpc_read` for simulations, `rpc_write` for secret storage)
* Verify the key has not been revoked
:::
:::details[WASM oracle returns empty or unexpected data]
**Cause:** The WASM component's `run` function is not returning valid JSON, or the HTTP fetch is failing silently.
**Fix:**
1. Test locally: `newton-cli --chain-id 11155111 policy-data simulate --wasm-file policy.wasm --input-json '{}'`
2. Verify your `run` function returns a JSON string (not an object)
3. Check that HTTP requests use the correct URL and headers
4. Ensure secrets are stored if your oracle requires API keys
:::
For a complete list of error codes, see the [Error Reference](/developers/reference/error-reference).
## Getting Help
* **GitHub Issues:** [github.com/newt-foundation](https://github.com/newt-foundation)
* **Email:** `support@newton.xyz`
* **Blog:** [blog.newton.xyz](https://blog.newton.xyz)
# Testing & Debugging \[Tools and techniques for testing policies, oracles, and attestations during Newton Protocol development]
Newton provides simulation endpoints and local tools at every layer of the stack. Use these to validate your policies, oracles, and integration before deploying to production.
## Local Policy Simulation
Test Rego policy logic without the network using the Newton CLI. Pass policy and data files with `-d` (repeatable), the intent with `-i`, and the query as a positional argument:
```bash
newton-cli regorus eval \
-d policy.rego \
-d data.json \
-i intent.json \
--non-strict \
"data.policy.allow"
```
This evaluates your Rego v1 policy locally with Newton crypto extensions (`newton.crypto.ecdsa_recover_signer`, `newton.crypto.ecdsa_recover_signer_personal`). Newton's Regorus 0.9.0 fork implements an OPA v1.2.0-compatible subset, not every OPA built-in. Use `--non-strict` for OPA-style non-strict evaluation; it does not enable unavailable built-ins.
For writing Rego unit tests with `opa test`, see [Testing Policies & Oracles](/developers/guides/testing-policies).
## Local Oracle Simulation
Test your WASM data oracle without deploying:
```bash
newton-cli --chain-id 11155111 policy-data simulate \
--wasm-file policy.wasm \
--input-json '{"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"}'
```
The CLI runs your WASM component in a sandboxed environment with the Newton HTTP host. The `--input-json` value is passed to your `run` function as the `input` string.
## Gateway Simulation
The policy-set endpoints resolve a configured client on the specified chain. Use `policy_client`, numeric `chain_id`, `intent`, and positional `wasm_args` arrays. SDK `simulatePolicy` supports this schema in v1.2.2; its `simulateTask` wrapper does not.
| Endpoint | Executes WASM? | Input | Result |
|---|---|---|---|
| `newt_simulatePolicyData` | Yes, one policy's module | Policy address, chain, scalar args, optional inline secrets | Oracle output |
| `newt_simulatePolicyDataWithClient` | Yes, one policy's module | Policy address, chain, client, scalar args; stored secrets | Oracle output |
| `newt_simulatePolicy` | Yes, each policy's optional module | Current client set and ordered oracle inputs | Per-policy verdicts and aggregate `allowed` |
| `newt_simulateTask` | No | Current client set and ordered precomputed oracle outputs | Per-policy verdicts and aggregate `allowed` |
These simulations run in-process on the gateway. They do not produce BLS quorum signatures or execute application transactions. `newt_createTask` / `newt_sendTask` exercise live operator evaluation.
A completed denial has `success: true, allowed: false`; a simulation error has `success: false, allowed: null`. Inspect `policies[i].policy_index`, `policy`, `allowed`, and `result` to find the denying rule. See [RPC examples](/developers/reference/rpc-api#newt_simulatepolicy).
### Historical task investigation
Inspect the original task's frozen `policies`, `policyRevision`, `policyId`, ordered evidence, and aggregate verdict. Neither simulation endpoint replays the original snapshot: both resolve the client's current set. After reconfiguration, the same inputs can produce a different verdict. Reproduce a historical evaluation using its captured snapshot and matching evaluator revision in a local test.
Follow the [policy-set validation checklist](/developers/guides/testing-policies#policy-set-validation-checklist) before claiming end-to-end success.
## Newton Explorer
The [Newton Explorer](/developers/resources/newton-explorer) provides a visual interface for inspecting tasks, attestations, and policy evaluations on-chain. Use it to:
* View task status and results
* Inspect attestation details (signers, quorum, expiration)
* Trace the evaluation flow from intent to on-chain verification
### From the terminal
The same data is available from the [Explorer API](/developers/reference/explorer-api) with your `NEWTON_API_KEY`. Fetch a live task by the `task_id` from `newt_createTask` and pull out the fields that explain its result:
```bash
curl -sS https://explorer.api.newton.xyz/v1/task/$TASK_ID \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet" \
| jq '{evaluation_result, error, operator_errors, policy_task_data}'
```
`policy_task_data` contains ordered evidence from the original evaluation. Preserve the original snapshot for historical reproduction; `newt_simulateTask` resolves the current set and is not a replay endpoint.
## BLS Diagnostics
If an attestation fails on-chain verification, check:
| Check | How |
|-------|-----|
| Attestation expiration | Compare `attestation.expiration` against current block number |
| Signer bitmap | Verify the signer bitmap includes enough operators for quorum |
| Reference block | Ensure the reference block matches the operator set used for verification |
| Chain ID | Confirm the intent's `chainId` matches the chain where the PolicyClient is deployed |
## Debugging Patterns
| Symptom | Likely Cause | Diagnostic Step |
|---------|-------------|-----------------|
| `newt_simulateTask` returns `non-compliant` | Policy logic rejects the intent | Run `newt_simulatePolicy` with the same inputs to isolate the Rego evaluation |
| Oracle returns empty data | WASM `run` function error or HTTP fetch failure | Test locally with `newton-cli policy-data simulate` |
| Attestation expires before on-chain submission | Too much time between evaluation and transaction | Reduce latency or increase the `expiry_offset` parameter |
| On-chain verification fails | Operator set rotated or wrong chain | Check reference block and chain ID |
| `TaskAlreadyExists` error | Duplicate intent hash | Modify the intent nonce or wait for the existing task |
| Policy returns `CAP` instead of `ALLOW` | Intent value exceeds policy threshold | Check `data.params` values on your PolicyClient |
| A live task was non-compliant or failed and you need to know why | Oracle data, params, or operator errors | Fetch the task from the [Explorer API](/developers/reference/explorer-api#check-a-task-after-submitting-it) and inspect `error`, `operator_errors`, and `policy_task_data` |
## SDK Debugging
Enable verbose logging in the Newton SDK:
```typescript
import { createPublicClient, http } from 'viem';
import { sepolia } from 'viem/chains';
import { newtonPublicClientActions } from '@newton-xyz/sdk';
const client = createPublicClient({
chain: sepolia,
transport: http(),
}).extend(newtonPublicClientActions());
// Simulate first to debug
const simulation = await client.simulateTask({
policyClient: '0xYourPolicyClientAddress',
intent: { /* ... */ },
});
console.log('Simulation result:', JSON.stringify(simulation, null, 2));
```
## Next Steps
Pre-launch verification checklist
Complete error code reference
# Deployment Checklist \[Pre-launch verification checklist for deploying Newton Protocol policies, oracles, and PolicyClient contracts]
Use this checklist to verify your Newton Protocol integration before going to production.
## Policy Development
:::steps
### Rego policy passes local evaluation
```bash
newton-cli regorus eval \
--policy policy.rego \
--input '{"from":"0x...","to":"0x...","value":"0x0","data":"0x","chainId":"0xaa36a7","functionSignature":"0x"}' \
--data '{"params":{...},"data":{...}}'
```
Verify `allow`, `deny`, and `cap` paths all produce expected results.
### Data oracle returns valid JSON
```bash
newton-cli policy simulate -p policies/my-oracle
```
Set the oracle's input in `configs/wasm_args.json`, then confirm the output matches your policy's `data.wasm` expectations.
### Gateway simulation succeeds
Run `newt_simulatePolicy` with realistic intent parameters, and verify aggregate and per-policy results. Simulation returns no attestation. Separately obtain live signatures and verify an allowed application transaction.
### Edge cases tested
* Zero-value transactions
* Maximum-value transactions
* Addresses not on allowlists
* Expired or missing data
* Invalid function signatures
:::
## On-Chain Deployment
:::steps
### Mainnet policy allowlisting confirmed
Mainnet policy usage requires allowlisting by the Newton team. Before deploying or using policies on mainnet, [reach out through the intake form](https://newton.xyz/intake-form).
### Independent policies deployed
```bash
newton-cli policy deploy -p policies/my-policy
```
Each policy has Rego, schema, metadata, code hash, and optional WASM/secrets-schema CIDs. Pure-Rego needs no WASM module. Require a successful receipt, nonempty code, and registration with the TaskManager's factory.
### PolicyClient configured
```bash
cast send "$POLICY_CLIENT" \
'setPolicies((address,(bytes,uint32))[])' \
"[(\"$POLICY_0\",(\"$PARAMS_0\",25))]" \
--rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY"
```
Deploy using the [contract integration guide](/developers/guides/smart-contract-integration), then configure the complete ordered set with owner `setPolicies`. Read `getPolicySetSnapshot()` and check ID, revision, params, order, and nonzero expirations.
### Ownership and registry verified
Verify `getOwner()` matches the intended owner. Register for identity flows where required by the target deployment; a registry owner record is separate from the client's owner authority.
:::
## API & Secrets
:::steps
### API key created with correct permissions
Verify your key has `rpc_read` for task submission and simulation, `rpc_write` if you need to manage secrets. See [Dashboard & API Keys](/developers/overview/dashboard-api-keys).
### Encrypted secrets stored (if applicable)
If your oracle requires API keys, store them via `newt_storeEncryptedSecrets`. Verify with `newt_simulatePolicyDataWithClient`. See [Encrypting Secrets](/developers/advanced/encrypting-secrets).
### Secrets match schema
Confirm your uploaded secrets match the `secretsSchemaCid` defined on your Policy contract. Schema mismatches produce validation errors.
:::
## Frontend Integration
:::steps
### SDK installed and configured
```bash
pnpm add @newton-xyz/sdk viem
```
Verify `newtonPublicClientActions` and `newtonWalletClientActions` are properly extended on your viem clients.
### Environment variables set
* `NEWTON_API_KEY` — your Gateway API key
* `NEWTON_GATEWAY_URL` — the correct Gateway URL for your environment
* PolicyClient address for your target chain
### End-to-end flow verified
1. Submit an evaluation via `evaluateIntentDirect` or `submitEvaluationRequest`
2. Receive attestation
3. Execute the attested transaction on-chain
4. Verify the transaction succeeds
### Error handling implemented
Handle all SDK error codes: `TASK_TIMEOUT`, `POLICY_NOT_FOUND`, `ATTESTATION_EXPIRED`, `UNAUTHORIZED`. See [Error Reference](/developers/reference/error-reference).
:::
## Security
* \[ ] PolicyClient ownership verified on-chain (not just in your local config)
* \[ ] API keys stored in environment variables, never committed to source control
* \[ ] WASM oracle does not log or expose secrets
* \[ ] Attestation expiration is checked before on-chain submission
* \[ ] Challenge window (100 blocks) is accounted for in your UX
* \[ ] Contract uses `_validateAttestation` or `_validateAttestationDirect` correctly
* \[ ] Intent fields (`from`, `to`, `value`, `data`, `chainId`) are validated before submission
## Pre-Launch Verification
```bash
newtup
newton-cli --version
newton-cli doctor
```
Record the chain, TaskManager, configured factory, and gateway deployment. Require a gateway task ID, aggregate verdict, and—on allow—a successful application transaction hash and state readback. Verify denial and replay leave state unchanged.
## Policy-set validation checklist
Use two independent deterministic test policies, each with allow and deny fixtures. Keep the same client and record its ordered set before each run.
| Policy 0 | Policy 1 | Aggregate | Application state |
|---|---|---|---|
| Allow | Allow | Allow | Changes only after successful attested transaction |
| Allow | Deny | Deny | Unchanged |
| Deny | Allow | Deny | Unchanged |
| Deny | Deny | Deny | Unchanged |
* Check every input and result maps to its policy-set position; preserve repeated entries.
* Replace with a reordered set, then an identical set. Each successful replacement advances the revision and changes the ID. Old attestations must fail.
* Reject empty sets, more than 8 entries, unregistered policies, zero expiry, oversized params, and unauthorized updates on the pinned contract.
* Reject reused attestations and changes to sender, target, chain, value, calldata, or protected arguments. Verify expired responses and mismatched task snapshots fail.
* Read state before and after denial and failed validation. An API denial alone does not prove the contract rejects a deny attestation.
Record evidence separately:
| Level | Evidence |
|---|---|
| Local policy tests | Inputs, expected and actual Rego verdicts |
| Local contract tests | Configuration, intent binding, deny, stale response, and replay assertions |
| Gateway simulation | Endpoint, current set ID, per-policy results, aggregate verdict; no signatures |
| Live operator evaluation | Gateway task ID, frozen task, aggregate `allowed`, signed response |
| On-chain validation | Successful BLS validation or expected revert |
| Application execution | Transaction hash, receipt, and resulting application state |
Do not use wall-clock conditions for a deterministic tutorial. An allow decision applies to evaluation time; block-based expiration is not a promise that external conditions remain true until execution.
## Next Steps
Common questions and error solutions
Deployed contracts on all networks
# Newton Explorer \[The Newton Explorer showcases the key information available in the protocol. At its core, there are two components: **tasks** and **policies**. This document provides context on the data fields in the explorer and the various states that are possible.]
**Newton Explorer:** https://explorer.newton.xyz/
## Programmatic access
The tasks and policies shown in the Explorer are also available from the [Explorer API](/developers/reference/explorer-api), authenticated with your [Newton API key](/developers/overview/dashboard-api-keys). Use it to look up tasks and policies from scripts, CLIs, and backends:
```bash
curl -sS https://explorer.api.newton.xyz/v1/task/ \
-H "Authorization: Bearer $NEWTON_API_KEY" \
-H "X-Protocol-Network: testnet"
```
## Definitions
### General
| **Term** | **Definition** |
| ------------- | ------------------------------------------------------------------------------------------------- |
| Task | The atomic unit that the protocol validates. It includes an intent and a reference to the policy. |
| Policy | User-specific policy with parameters set by a user when they choose to use it. |
| Compliant | When a task meets the criteria defined by a policy. |
| Non-compliant | When a task does not meet the criteria defined by a policy. |
### Task
| **Term** | **Definition** |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| Current Status | Information on the stage a particular task is in. See section below for possible values. |
| Sender | The address of the wallet that initiated the transaction to the protocol. |
| Policy Client Address | The address of the policy-guarded smart contract. This could represent a vault for an end-user. |
| Evaluation Result | Indicates whether a policy is passed or not. Possible values: compliant or non-compliant. |
| Date Created | The date the task was first recognized by the protocol. |
| Last Updated | The date the task last changed status. |
| Intent | The actual EVM transaction for which the policy is being evaluated. |
### Policy Evaluation
| **Term** | **Definition** |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| Policy address | A generic policy written by a developer. Policies can be customized by end-users as used by a task for evaluation |
| Policy ID | Unique identifier representing a user-specific policy. |
| Date Created | The time at which a policy was created for the user. |
| Time of Evaluation | The time at which the task was evaluated as compliant or non-compliant. |
| Evaluation Result | Indicates whether a policy is passed or not. Possible values: compliant or non-compliant. |
| Intent | The actual EVM transaction for which the policy is being evaluated. |
| Policy Data | Offchain data produced by the data oracle as defined in the policy. |
| Policy Params | User-specific parameters used to customize a policy. |
| Policy Code | The Rego code that defines the logic of a policy. |
| Policy Simulation | A local browser evaluation of an intent, used to keep operators honest. |
***
## Task Lifecycle
1. **Intent evaluation requested:** The task is sent to an AVS operator for evaluation.
2. **Intent evaluated:** The AVS operator has evaluated the task against the policy to determine compliance.
3. **Proof consumed / Proof expired:** Once a proof is generated by the protocol and provided to the developer, it can be used by the policy client to complete the transaction. If the proof is used, it is “consumed”. If the expiration window passes, as defined by the policy, it is “expired”.
# Developer Terms of Service \[Developer Terms of Service for accessing and using the Newton Protocol developer platform, APIs, SDKs, dashboards, documentation, and related tools.]
**Effective Date:** June 17, 2026
These Developer Terms of Use ("Developer Terms") govern your access to and use of the Newton Protocol developer platform, including any APIs, SDKs, developer dashboards, documentation, and related tools (collectively, the "Developer Services") provided by Magic Newton Operations Ltd., a British Virgin Islands business company ("Platform Operator," "we," "us," or "our").
These Developer Terms supplement and incorporate by reference the general Terms of Use available at newton.xyz ("General Terms"). Capitalized terms used but not defined here have the meanings given in the General Terms. In the event of a conflict between these Developer Terms and the General Terms, these Developer Terms control with respect to your use of the Developer Services.
By accessing or using the Developer Services, or by clicking "I agree" or a similar button, you agree to be bound by these Developer Terms. If you are accessing the Developer Services on behalf of an organization, you represent that you have authority to bind that organization, and references to "you" and "Developer" include that organization.
If you do not agree to these Developer Terms, do not access or use the Developer Services.
## 1. The Newton Protocol and Developer Services
Newton Protocol is a pre-settlement transaction authorization layer that enables applications to evaluate transactions against programmable policy rules before on-chain settlement. Operators run policy evaluation, and results are cryptographically attested for consumption by integrated smart contracts.
The Developer Services provide tools that facilitate interaction with Newton Protocol, including:
* the Newton Protocol SDK (`@magicnewton/newton-protocol-sdk`) and any successor or companion SDKs (collectively, "Newton SDKs");
* any developer dashboards;
* access keys and associated authentication credentials;
* documentation, sample code, and reference implementations published by the Platform Operator; and
* any other tools, services, or functionality we make available to developers from time to time.
The Developer Services are interfaces to Newton Protocol. They are not essential to accessing the Protocol directly. Developers and other parties are free to interact with Newton Protocol's underlying smart contracts and open-source components without using the Developer Services, subject to any applicable open-source license terms available at https://github.com/Magic-Newton-Foundation.
The Platform Operator does not control the actions of network operators, the outcome of policy evaluations, or transactions executed on any blockchain. Use of the Developer Services does not guarantee any particular compliance outcome or that any transaction or application will satisfy applicable legal or regulatory requirements. You remain solely responsible for ensuring your application and its use of Newton Protocol comply with all applicable laws and regulations.
## 2. Eligibility
To access the Developer Services, you must:
* be at least 18 years of age;
* not be a Restricted Person as defined in the General Terms; and
* if acting on behalf of an organization, have the authority to bind that organization to these Developer Terms.
We may require you to provide accurate registration information, including a valid email address or wallet address, in order to obtain keys or access the developer dashboard. You represent that all information you provide is accurate and current, and you agree to update it promptly if it changes.
## 3. Access Keys and Account Security
Access to certain Developer Services requires one or more access keys issued by the Platform Operator. Access keys are associated with specific permissions, which may include task submission, secrets management, or administrative access, as described in our developer documentation.
With respect to your keys and developer account credentials, you agree to:
* keep all keys and credentials confidential and not share them with unauthorized parties;
* use keys only for the purposes authorized under these Developer Terms;
* implement reasonable security measures to prevent unauthorized access to or use of your keys;
* promptly notify us at legal@newton.xyz if you suspect unauthorized access or compromise of any key; and
* not transfer, sell, sublicense, or otherwise assign keys or developer account access to any third party without our prior written consent.
You are responsible for all activity that occurs under your keys, whether or not authorized by you. The Platform Operator is not liable for any loss or damage arising from unauthorized use of your keys or credentials.
We reserve the right to revoke, suspend, or rate-limit any key at any time, including if we reasonably believe the key is being used in violation of these Developer Terms or in a manner that could harm the Developer Services, the Protocol, or other users.
## 4. Permitted Use and License
Subject to your compliance with these Developer Terms, we grant you a limited, non-exclusive, non-transferable, revocable license to access and use the Developer Services solely to:
* build, test, and deploy applications that integrate with Newton Protocol (each, an "Application");
* access the Developer Services to submit policy evaluation requests and receive attestations;
* use documentation and sample code published by the Platform Operator to support your integration; and
* evaluate and test the Developer Services for integration purposes, including through simulated task flows.
This license does not include the right to resell or sublicense access to the Developer Services themselves as a standalone product; provided, however, you may build Applications that incorporate the Developer Services so as to leverage Newton Protocol, commercialize and market those Applications to your customers, and charge fees for your Applications, provided that the primary value of your Application reflects your own development, features, and functionality rather than merely repackaging raw access to the Developer Services. If you build an Application that allows your own end users to interact with Newton Protocol through your integration, that is a permitted use, provided you comply with Section 6 (Developer Obligations) and all other applicable terms.
## 5. Restrictions
You agree not to, and not to permit any third party to:
* use the Developer Services to build any application or product that facilitates, enables, or promotes activity that violates applicable laws or regulations, including sanctions laws, anti-money laundering laws, or laws prohibiting the provision of services to Restricted Persons;
* use the Developer Services to process, store, or transmit data belonging to Restricted Persons or to circumvent sanctions screening or other policy enforcement mechanisms;
* use bots, automated tools, or scripting to generate API requests at volumes materially exceeding normal application usage patterns, or in a manner intended to circumvent rate limits or abuse detection;
* attempt to probe, scan, or test the vulnerability of the Developer Services or any related infrastructure, or attempt unauthorized access to any API endpoint, database, or system;
* reverse-engineer, decompile, or attempt to derive the source code of any non-open-source components of the Developer Services;
* misrepresent the source, nature, or properties of policy evaluation results or attestations generated through Newton Protocol, including by presenting attestations as guarantees of legal compliance or as investment advice;
* use the Developer Services in a manner that interferes with or degrades the operation of Newton Protocol, the Gateway, or the experience of other developers or end users;
* use the Developer Services to develop, train, or improve any machine learning model or AI system without our prior written consent; or
* encourage, assist, or permit any third party to engage in any of the foregoing.
## 6. Developer Obligations
### 6.1 End User Compliance
If your Application is made available to end users, you are responsible for ensuring that your end users are not Restricted Persons and that their use of your Application complies with applicable laws and regulations. You may not use the Developer Services as a substitute for your own compliance obligations, and Newton Protocol attestations do not constitute legal compliance determinations.
You agree to implement and maintain appropriate terms of service and privacy notices for your Application that accurately describe how you collect, use, and process data, including any data submitted to Newton Protocol through your integration.
### 6.2 Data Handling
To the extent your integration with Newton Protocol involves the submission of personal data or other sensitive information (including KYC credentials, wallet addresses associated with individuals, or identity-linked data), you are responsible for:
* having a lawful basis for collecting and processing such data;
* providing appropriate disclosures to the individuals whose data is processed;
* ensuring that any third-party identity verification, KYC, or data vendor agreements applicable to your integration permit the use, submission, or transmission of resulting verification data or outputs through Newton Protocol, including any domain-scoped or linked credential submissions;
* complying with applicable data protection laws, including but not limited to the GDPR and any other jurisdiction-specific requirements applicable to your operations; and
* not submitting data to Newton Protocol that you are not authorized to process or share.
The Platform Operator makes no representation that your use of Newton Protocol satisfies the terms of any such vendor agreement, and compliance with those agreements is solely your responsibility. The Platform Operator is not a data controller or data processor with respect to data submitted through your Application, except to the limited extent described in our Privacy Policy. Your use of Newton Protocol's identity and credential features does not transfer data protection liability to the Platform Operator.
### 6.3 Accurate Representations
You agree not to make representations to your end users or any third party that overstate the capabilities of Newton Protocol or the Developer Services. In particular, you agree not to represent that Newton Protocol:
* guarantees legal compliance with any regulatory framework;
* eliminates sanctions or AML risk;
* constitutes legal, financial, or investment advice; or
* is error-free, always available, or operates in any specific manner (e.g., centralized, permissioned, etc.)
Accurate descriptions include language such as "supports," "enables configurable policy controls," "provides verifiable authorization constraints," or "helps enforce." The Platform Operator reserves the right to require you to correct inaccurate public representations about Newton Protocol.
### 6.4 Open Source Compliance
Portions of Newton Protocol and the Developer Services are subject to open-source licenses. You are responsible for reviewing and complying with any applicable open-source license terms for software components you use in your integration. Open-source license terms are available at https://github.com/newt-foundation.
## 7. Fees and Usage
Access to the Developer Services may require the payment of protocol-level fees denominated in NEWT or other digital assets, as set by the Protocol from time to time. The applicable fee structure, if any, will be described in our developer documentation or communicated to you separately.
The Platform Operator reserves the right to introduce, modify, or discontinue fees at any time, with reasonable notice where practicable. Continued use of the Developer Services after notice of fee changes constitutes your acceptance of those changes.
You are responsible for all taxes, duties, and similar charges arising from your use of the Developer Services or your Application's use of Newton Protocol, except for taxes on the Platform Operator's net income.
## 8. Intellectual Property
### 8.1 Platform Operator IP
Except for components subject to open-source licenses, the Developer Services, including the Newton SDKs, documentation, and all related materials, are owned by or licensed to the Platform Operator and are protected by applicable intellectual property laws. These Developer Terms do not grant you any ownership interest in the Developer Services or any Platform Operator intellectual property.
### 8.2 Your Applications
As between you and the Platform Operator, you retain ownership of your Application and any original code, content, or materials you develop independently. You grant the Platform Operator a non-exclusive, royalty-free license to use and display your name, logo, and Application description for the purpose of identifying you as a developer building on Newton Protocol, including in developer directories, case studies, and marketing materials, unless you notify us in writing that you object to such use.
### 8.3 Feedback
If you submit feedback, suggestions, or ideas about the Developer Services ("Feedback"), you grant the Platform Operator a perpetual, irrevocable, royalty-free, worldwide license to use, modify, and incorporate that Feedback into the Developer Services or any other product or service, without any obligation of attribution or compensation to you.
## 9. Confidentiality
From time to time, the Platform Operator may provide you with access to non-public information about the Developer Services, including pre-release features, technical roadmaps, or other materials designated as confidential ("Confidential Information"). You agree to:
* hold Confidential Information in strict confidence;
* not disclose Confidential Information to any third party without the Platform Operator's prior written consent; and
* use Confidential Information only for the purpose of developing your Application or otherwise exercising your rights under these Developer Terms.
These obligations do not apply to information that: (a) is or becomes publicly available through no fault of yours; (b) was rightfully known to you before disclosure by the Platform Operator; (c) is disclosed to you by a third party without confidentiality obligations; or (d) you are required to disclose by law, provided you give us reasonable advance notice and cooperate with any effort to seek a protective order.
## 10. Term and Termination
These Developer Terms are effective when you first access the Developer Services and continue until terminated.
Either party may terminate these Developer Terms at any time:
* you, by ceasing to use the Developer Services and deleting all keys associated with your developer account; or
* the Platform Operator, by providing notice to you via email, the developer dashboard, or other reasonable means, or immediately and without notice if we determine that you have materially violated these Developer Terms.
Upon termination, your license to use the Developer Services ends immediately, and you must cease all use of keys, Newton SDKs, and other Developer Services materials. Sections 5, 6, 8, 9, 10, 11, 12, and 13 survive termination.
We may also suspend your access to the Developer Services immediately and without prior notice if we reasonably believe your use poses a security risk, violates applicable law, or could harm the Developer Services, the Protocol, or other users. We will endeavor to provide notice and an opportunity to cure where practicable.
## 11. Disclaimer of Warranties
**THE DEVELOPER SERVICES ARE PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT ANY WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE PLATFORM OPERATOR EXPRESSLY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, OR STATUTORY, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, ACCURACY, AND SECURITY.**
**THE PLATFORM OPERATOR DOES NOT WARRANT THAT THE DEVELOPER SERVICES WILL BE UNINTERRUPTED, ERROR-FREE, OR FREE OF VULNERABILITIES; THAT API RESPONSES OR POLICY EVALUATION RESULTS WILL BE ACCURATE, COMPLETE, OR TIMELY; OR THAT ANY ATTESTATION OR AUTHORIZATION GENERATED THROUGH NEWTON PROTOCOL WILL SATISFY ANY LEGAL OR REGULATORY STANDARD APPLICABLE TO YOUR APPLICATION OR YOUR END USERS.**
**YOU ASSUME FULL RESPONSIBILITY FOR DETERMINING WHETHER USE OF THE DEVELOPER SERVICES AND NEWTON PROTOCOL IS APPROPRIATE FOR YOUR APPLICATION AND FOR YOUR COMPLIANCE WITH ALL APPLICABLE LAWS AND REGULATIONS.**
## 12. Limitation of Liability
**TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL THE PLATFORM OPERATOR, ITS AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, AGENTS, LICENSORS, OR SERVICE PROVIDERS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, INCLUDING LOSS OF PROFITS, REVENUE, DATA, GOODWILL, OR BUSINESS OPPORTUNITIES, ARISING OUT OF OR IN CONNECTION WITH THESE DEVELOPER TERMS OR YOUR USE OF OR INABILITY TO USE THE DEVELOPER SERVICES, EVEN IF THE PLATFORM OPERATOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.**
**TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE PLATFORM OPERATOR'S TOTAL AGGREGATE LIABILITY TO YOU ARISING OUT OF OR IN CONNECTION WITH THESE DEVELOPER TERMS, WHETHER IN CONTRACT, TORT, OR OTHERWISE, WILL NOT EXCEED THE GREATER OF (A) THE AMOUNTS YOU HAVE PAID TO THE PLATFORM OPERATOR FOR THE DEVELOPER SERVICES IN THE TWELVE MONTHS PRECEDING THE CLAIM, OR (B) ONE HUNDRED UNITED STATES DOLLARS (US$100).**
**THE LIMITATIONS IN THIS SECTION REFLECT A REASONABLE ALLOCATION OF RISK BETWEEN THE PARTIES AND ARE A FUNDAMENTAL ELEMENT OF THE BASIS OF THE BARGAIN BETWEEN YOU AND THE PLATFORM OPERATOR. SOME JURISDICTIONS DO NOT PERMIT THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OF THESE LIMITATIONS MAY NOT APPLY TO YOU.**
## 13. Indemnification
You agree to defend, indemnify, and hold harmless the Platform Operator and its affiliates, directors, officers, employees, agents, and licensors from and against any claims, liabilities, damages, losses, and expenses (including reasonable attorneys' fees) arising out of or in connection with:
* your Application, including any end user's use of your Application;
* your violation of these Developer Terms or the General Terms;
* your violation of any applicable law or regulation, including data protection laws or sanctions regulations;
* your infringement of any third-party intellectual property or other rights; or
* your breach of any representation or warranty made in these Developer Terms.
The Platform Operator reserves the right to assume exclusive control of the defense of any matter subject to indemnification at your expense, and you agree to cooperate with us in that defense.
## 14. Modifications to Developer Terms
We reserve the right to modify these Developer Terms at any time. If we make material changes, we will provide notice by posting the updated Developer Terms to our developer documentation, by email, or by other reasonable means. Your continued access to or use of the Developer Services after the effective date of any modifications constitutes your acceptance of the updated Developer Terms.
If you do not agree to any modifications, you must stop using the Developer Services. We may also modify, suspend, or discontinue any aspect of the Developer Services at any time, with or without notice, and without liability to you.
## 15. Governing Law and Dispute Resolution
These Developer Terms are governed by and construed in accordance with the laws of the British Virgin Islands, without regard to conflict of law principles.
The dispute resolution provisions of the General Terms, including the mandatory arbitration clause, the class action waiver, and the opt-out procedure, apply to disputes arising under or in connection with these Developer Terms as if fully set forth here. Please review Section 15 of the General Terms carefully.
For clarity, the exclusive jurisdiction for any disputes not subject to arbitration under the General Terms is the courts of the British Virgin Islands, and each party waives any objection to jurisdiction or venue in those courts.
## 16. Miscellaneous
These Developer Terms, together with the General Terms and any other agreements or policies incorporated by reference, constitute the entire agreement between you and the Platform Operator with respect to the Developer Services and supersede any prior agreements or understandings on the same subject matter.
If any provision of these Developer Terms is found to be invalid, unlawful, or unenforceable, that provision will be limited or severed to the minimum extent necessary, and the remaining provisions will continue in full force and effect.
The Platform Operator's failure to enforce any right or provision of these Developer Terms will not constitute a waiver of that right or provision.
You may not assign or transfer any of your rights or obligations under these Developer Terms without the prior written consent of the Platform Operator. The Platform Operator may assign these Developer Terms or any of its rights or obligations hereunder at any time without notice or consent.
Nothing in these Developer Terms creates a joint venture, partnership, employment, agency, or franchise relationship between you and the Platform Operator.
Notices to the Platform Operator under these Developer Terms should be sent to legal@newton.xyz. Notices to you will be sent to the email address associated with your developer account, or as otherwise provided in the General Terms.
These Developer Terms are in addition to, and do not replace, any obligations you have under applicable law. Where applicable law imposes obligations that cannot be waived by contract, those obligations apply notwithstanding anything to the contrary in these Developer Terms.
If you have questions about these Developer Terms, please contact us at legal@newton.xyz or by mail at: Magic Newton Operations Ltd., 2nd Floor, Jayla Place, Road Town, Tortola, British Virgin Islands.
# Agent Skills \[Install Newton Agent Skills for versioned policy, PolicyClient, and application workflows]
Newton Agent Skills replace the previous standalone LLM context files. Skills are versioned alongside templates and references, split by task, and designed to resume work through machine-readable handoff files.
:::warning
The skills are under active dogfood testing. Review generated code and report friction instead of silently working around it.
:::
## Available skills
| Skill | Use when |
|---|---|
| `newton-policy` | Installing `newton-cli`, authoring Rego + WASM, and running the local build/simulate loop |
| `newton-policy-client` | Adding `_validateAttestationDirect` to a Solidity integration and testing intent binding |
| `newton-demo` | Turning a brief into a policy, PolicyClient, and local Next.js application |
The skills preserve state in `policy-handoff.json`, `client-handoff.json`, and `demo-config.json`. These files contain addresses and intent configuration, but never credentials.
## Install
```bash
git clone https://github.com/newt-foundation/newton-agent-skills.git
cd newton-agent-skills
```
For local Cursor or Codex discovery while the skills repository is the workspace:
```bash
mkdir -p .agents
ln -sfn ../skills .agents/skills
```
For Claude Code, copy the folders into `.claude/skills/` or `~/.claude/skills/`. Other Agent Skills clients can load the required folders from `skills/` using their supported discovery directory.
## Safety model
* Local policy work does not require login or deploy credentials.
* The agent must not request private keys, API keys, JWTs, or RPC credentials in chat.
* Deployment, gateway evaluation, the first attested transaction, and hosting require separate user confirmation.
* `NEWTON_API_KEY` belongs on the server, never in a `NEXT_PUBLIC_*` variable or browser bundle.
Install the skills, write a brief, review local results, and approve live checkpoints
Browse the current skills, references, templates, and sample briefs
# Technical Whitepaper \[Technical specification of Newton Protocol — an EigenLayer Actively Validated Service (AVS) for policy-based authorization of blockchain transactions. Covers cryptographic primitives, consensus, and security model.]
## Introduction
This document provides a comprehensive technical specification of the Newton Protocol, an Autonomous Verifiable Service (AVS) built on EigenLayer for policy-based authorization of blockchain transactions. The intended audience is protocol engineers, security auditors, and system integrators who require precise understanding of Newton's cryptographic primitives, consensus mechanisms, cross-chain architecture, and security model.
Newton Protocol operates as an authorization layer between transaction intent and on-chain execution. Applications submit transaction intents to a decentralized operator network. Operators independently evaluate those intents against Rego policies, fetch external data through sandboxed WASM plugins, and produce BLS signatures over the evaluation result. The protocol uses NATS streaming messaging for non-blocking, pipelined communication between the gateway and operators, enabling sub-second consensus with early quorum exit. An aggregation layer collects these signatures, verifies stake-weighted quorum, and produces a compact aggregate BLS signature that smart contracts can verify on-chain. A challenge mechanism backed by zero-knowledge proofs provides cryptographic dispute resolution, and a slashing mechanism enforces economic accountability.
The system comprises off-chain services (gateway, operators, aggregator, challenger, and data providers), on-chain smart contracts (TaskManager, ServiceManager, PolicyFactory, PolicyClientRegistry, IdentityRegistry, and cross-chain registries), and a Rego policy engine with Newton-specific cryptographic extensions. The gateway orchestrates the authorization flow by publishing tasks to operators via NATS streaming, collecting responses as they arrive, computing median-based consensus over divergent numeric fields, and aggregating BLS signatures with early quorum exit. Operators are stateless validators that fetch policies from IPFS, execute WASM data providers, evaluate Rego rules, and sign responses with BLS keys. The challenger monitors on-chain responses and generates zero-knowledge proofs when discrepancies are detected.
This document covers privacy architecture, the BLS attestation protocol, the streaming consensus protocol, cross-chain interoperability, slashing and challenge mechanisms, the policy engine, decentralization guarantees, and security properties.
For protocol vision, market context, and use cases, see the [Newton Protocol Whitepaper](https://newton.xyz/whitepaper). For developer integration details (JSON-RPC API, simulation modes, attestation validation paths), see the [RPC API Reference](/developers/reference/rpc-api) and the [Smart Contract Integration Guide](/developers/guides/smart-contract-integration).
# Privacy Architecture \[Newton Protocol's layered privacy architecture: HPKE encryption, threshold decryption, MPC evaluation, and the Newton Privacy Envelope.]
## Privacy Architecture
Newton Protocol provides a layered privacy architecture that protects sensitive policy inputs (identity data, financial records, compliance credentials) from exposure at every stage — transport, storage, and evaluation. The architecture is designed around three progressive layers: threshold HPKE decryption where operators collectively decrypt data without any single entity holding the complete key (standard mode), multi-party computation (MPC) for privacy-preserving policy evaluation where operators evaluate policies over secret-shared data without any individual operator learning the inputs (advanced mode), and a future path to fully homomorphic encryption (FHE) where operators evaluate policies directly over encrypted data without decryption at any stage. Individual operator keys for peer-to-peer communication are derived deterministically from existing ECDSA keys, while the system's threshold decryption key is produced by an interactive Distributed Key Generation (DKG) protocol that runs during operator set changes.
### HPKE Ciphersuite
The encryption layer uses Hybrid Public Key Encryption as specified in RFC 9180, operating in Base mode with the following ciphersuite:
| Component | Algorithm | Specification |
|-----------|--------------------|------------------------------------------------|
| KEM | X25519-HKDF-SHA256 | Diffie-Hellman key encapsulation on Curve25519 |
| KDF | HKDF-SHA256 | HMAC-based key derivation |
| AEAD | ChaCha20-Poly1305 | Authenticated encryption with 256-bit key |
**Design rationale.** HPKE provides a standardized KEM/KDF/AEAD composition with formal security proofs (the HPKE standard has been mechanically verified), native support for authenticated associated data (critical for policy-context binding), and a KEM-agnostic design that enables post-quantum migration by replacing X25519 with ML-KEM without changing the protocol structure.
Each encryption operation generates a fresh ephemeral keypair, providing per-message forward secrecy. Compromise of the recipient's long-term private key does not retroactively compromise past messages encrypted with different ephemeral keys.
### Key Derivation Chain
The privacy layer uses two types of HPKE keys: **individual operator keys** derived deterministically from existing ECDSA operator keys (used for peer-to-peer encrypted messaging such as threshold share exchange), and a **system threshold key** produced by an interactive DKG protocol (used by clients for encrypting privacy data). The individual key derivation chain proceeds through three stages:
```mermaid
flowchart LR
A[ECDSA private key - secp256k1]
B[Ed25519 signing key]
C[X25519 private key - Curve25519]
D[X25519 public key]
A -->|HKDF-SHA256| B
B -->|SHA-512 seed to key| C
C -->|Scalar multiplication| D
```
**Stage 1: ECDSA to Ed25519.** The 32-byte ECDSA private key (secp256k1) is used as input key material to HKDF-SHA256. The salt is the fixed string `newton-privacy-ed25519`. The HKDF expand step produces a 32-byte output key material that serves as the Ed25519 seed. This derivation is deterministic: the same ECDSA key always produces the same Ed25519 key.
**Stage 2: Ed25519 to X25519.** The Ed25519 seed is hashed with SHA-512. The first 32 bytes of the SHA-512 output become the X25519 private key (static secret). This follows the standard Ed25519-to-X25519 conversion used in libsodium and other implementations.
**Stage 3: X25519 to public key.** The X25519 public key is computed via scalar multiplication of the private key against the Curve25519 base point. This public key is the HPKE recipient key that clients encrypt to.
Each operator derives its individual HPKE keypair from its ECDSA key at startup using a unified derivation function that composes all three stages. These individual keys are used for operator identification and direct encrypted messaging (e.g., threshold share exchange between peers). During operator registration, each operator registers its individual X25519 public key on-chain in the operator registry — alongside its BLS public key, stake, and quorum membership.
**Threshold key generation (DKG).** The system's combined HPKE public key — the key that clients encrypt to — is NOT derived from individual operator keys. Instead, it is produced by an interactive Distributed Key Generation (DKG) protocol run whenever the operator set changes (registration, deregistration, or stake updates). The DKG protocol (based on FROST DKG or Pedersen VSS) proceeds in two rounds:
1. **Commitment round.** Each operator generates a random polynomial of degree t-1 and broadcasts commitments (Feldman VSS commitments on Curve25519) to all other operators via NATS.
2. **Share distribution round.** Each operator evaluates its polynomial at each peer's index and sends the resulting share to that peer via encrypted NATS messages. Each operator verifies received shares against the published commitments.
The protocol produces: (a) a combined X25519 public key that is the sum of all operators' commitment constant terms, and (b) a private key share for each operator that is the sum of all shares received at its index. The combined public key is stored on-chain in a single storage slot on the operator registry contract. Clients read this combined key with a single contract call — no client-side aggregation is needed.
This DKG approach provides **key independence**: the threshold private key shares are cryptographically unrelated to operators' ECDSA or BLS keys. Compromise of an operator's ECDSA key does not compromise their threshold decryption share, and vice versa. The DKG ceremony is infrequent (only triggered by operator set changes) and does not affect task evaluation latency. Resharing protocols (proactive secret sharing) enable operator rotation without changing the combined public key, ensuring that clients' previously encrypted data remains decryptable by the updated operator set.
Decryption requires cooperation from at least t operators (where t matches the BLS quorum threshold), and no single entity — including the gateway — ever holds the complete private key.
### Newton Privacy Envelope (NPE) and AAD Binding
The combination of HPKE encryption with Ed25519 dual-signature authorization forms Newton's core privacy primitive: the **Newton Privacy Envelope (NPE)**. The NPE composes authenticated encryption (HPKE) with explicit authorization signatures (Ed25519) to ensure that encrypted data can only be accessed when both the user and the application have cryptographically consented to a specific policy evaluation context.
Encrypted data is transported in a SecureEnvelope structure — the on-the-wire representation of an NPE — that bundles the HPKE ciphertext with the metadata required for decryption and policy binding:
| Field | Type | Size | Encoding | Description |
|--------------------|---------------------|------------------------------------|--------------------|----------------------------------------------------------------|
| `enc` | hex string | 32 bytes | Hex (64 chars) | HPKE encapsulated key (X25519 ephemeral public key) |
| `ciphertext` | hex string | Variable (msg + 16 bytes AEAD tag) | Hex | HPKE ciphertext including ChaCha20-Poly1305 authentication tag |
| `policy_client` | 0x-prefixed address | 20 bytes | EIP-55 checksummed | Ethereum address of the policy client contract |
| `chain_id` | u64 | 8 bytes | Decimal string | Chain ID for context binding |
| `recipient_pubkey` | hex string | 32 bytes | Hex (64 chars) | Recipient Ed25519 public key (for sender verification) |
For the complete binary wire format specification with byte offsets and serialization rules, see [Appendix A](/whitepaper/references#appendix-a-newton-privacy-envelope-wire-format).
The additional authenticated data (AAD) is computed as:
```
AAD = keccak256(abi.encodePacked(policy_client_bytes, chain_id_be_bytes))
```
This AAD binding serves a critical security function: it cryptographically ties the ciphertext to a specific policy client on a specific chain. If an attacker attempts to replay a SecureEnvelope against a different policy client or chain, the AAD mismatch causes the ChaCha20-Poly1305 authentication check to fail, and decryption is rejected. The seal operation computes AAD automatically during encryption, and the open operation recomputes it from the stored `policy_client` and `chain_id` fields during decryption.
### Dual-Signature Authorization
Task creation with encrypted data references requires dual Ed25519 signatures to prevent unauthorized use of encrypted data across policy contexts.
**User signature.** The user signs a message binding their identity to the specific data references and intent:
```
user_message = keccak256(abi.encodePacked(policy_client, intent_hash, ref_id_1, ref_id_2, ...))
```
The user produces an Ed25519 signature over this digest using their derived Ed25519 signing key.
**Application signature.** The application signs a message that chains the user's authorization:
```
app_message = keccak256(abi.encodePacked(policy_client, intent_hash, user_signature))
```
The application produces an Ed25519 signature over this digest, creating a chain of authorization: the application attests that it received and verified the user's consent for this specific intent and data reference combination. Both signatures must verify against their respective Ed25519 public keys for the gateway to proceed with task creation.
This dual-signature scheme prevents several attack vectors: a compromised application key cannot fabricate user consent, a compromised user key cannot authorize data access without a valid application context, and neither signature can be replayed across different policy clients or intents because the `policy_client` and `intent_hash` are bound into both signing messages.
### Privacy Data Flow
The complete privacy data flow from client encryption through operator evaluation proceeds as follows:
```mermaid
flowchart LR
C[Client] -->|Read threshold public key| REG[Operator Registry]
C -->|HPKE encrypt| E[SecureEnvelope]
E -->|Upload via RPC| GW[Gateway]
GW -->|Store encrypted| DB[Encrypted Data Store]
DB -->|Distribute via NATS| OP[Operators]
OP -->|Compute partial share| NATS[NATS Share Exchange]
NATS -->|Reconstruct plaintext| PD[Plaintext - local]
PD -->|Evaluate + BLS sign| AGG[Aggregator]
```
The client reads the system's combined X25519 public key from the on-chain operator registry with a single contract call. This combined key is the output of the DKG protocol ([Key Derivation Chain](#key-derivation-chain)) — it corresponds to a threshold keypair distributed across the operator set, and no single entity holds the complete private key. The client encrypts sensitive data into one or more SecureEnvelopes using this combined key and uploads them. Each upload returns a reference UUID. At task creation time, the client includes these reference UUIDs along with dual-signature authorization.
The gateway distributes the encrypted envelopes to operators as part of the task payload via NATS. Each operator computes a partial decryption share using its DKG-produced share of the threshold private key, and publishes the partial share to the recipient operators via NATS operator-specific subjects (`newton.operators.{operator_id}.shares.{task_id}`). Shares are encrypted end-to-end to the recipient operator's individual X25519 public key (derived from their ECDSA key per the [Key Derivation Chain](#key-derivation-chain)) before publishing, ensuring that the NATS broker — and the current gateway operator — sees only ciphertext. Once an operator collects t partial shares from peers (where t is the threshold matching the BLS quorum requirement), it reconstructs the plaintext locally and evaluates the policy over the decrypted data. No single operator, and no central entity including the gateway, ever holds the complete decryption key. The verdict is then BLS-signed and aggregated as in the standard task flow.
### Multi-Party Computation for Policy Evaluation
Newton's privacy architecture provides two layers of privacy-preserving evaluation, each offering progressively stronger guarantees:
**Layer 1: Threshold Decryption (Standard).** The standard privacy mode described in [Privacy Data Flow](#privacy-data-flow). Operators collectively decrypt the HPKE-encrypted inputs using threshold decryption, evaluate the Rego policy locally over the plaintext, and BLS-sign the result. Each operator sees the decrypted data during evaluation but cannot decrypt it alone — cooperation from at least t operators is required. This provides strong protection against external adversaries and minority operator collusion, while keeping the evaluation model simple: standard Rego policies execute over plaintext data, and the existing two-phase consensus protocol ([Streaming Consensus Protocol](/whitepaper/streaming-consensus)) applies without modification.
**Layer 2: Secure Function Evaluation (Advanced).** For workloads requiring that no individual operator learns the underlying data — even during evaluation — Newton supports a full MPC evaluation mode. In this mode, operators execute the policy evaluation circuit over secret-shared data using a multi-party computation protocol. For Newton's policy workloads — primarily boolean logic, threshold comparisons, and set membership tests — the computation decomposes into arithmetic and boolean circuits that honest-majority MPC protocols handle efficiently. Recent three-party honest-majority protocols achieve throughput exceeding one billion 32-bit multiplications per second in LAN settings and maintain practical performance in WAN environments with asymmetric latency, where only one inter-party link is latency-critical during the online phase. The MPC protocol produces a single policy result (authorized/denied) that all honest operators agree on. Each operator then BLS-signs this result as in the standard attestation flow. The MPC evaluation is transparent to both the client (which sees the same HPKE encryption interface) and the smart contract layer (which sees the same BLS-attested response format).
**Collusion resistance.** Both layers depend on the honest-majority assumption: at least t of the n operators must be non-colluding. In Layer 1, collusion of t operators could reconstruct decrypted data. In Layer 2, collusion of t operators could reconstruct secret shares. EigenLayer's restaking mechanism provides the economic enforcement — corrupting t operators requires risking their combined staked capital, which is subject to slashing. The cost of collusion scales linearly with the total stake delegated to the operator set, creating an economic barrier proportional to the value of the data being protected. Applications choose the appropriate layer based on their data sensitivity requirements: Layer 1 for most compliance workloads (sanctions screening, KYC verification), Layer 2 for high-sensitivity data (medical records, financial portfolios, sealed-bid auctions).
### Research Horizon: Fully Homomorphic Evaluation
Newton's long-term privacy research tracks Fully Homomorphic Encryption (FHE) — the ability to evaluate arbitrary functions over ciphertexts without any decryption, even distributed decryption via MPC. Gentry's 2009 construction proved this is theoretically possible via a bootstrapping technique that refreshes noise accumulated during homomorphic operations, enabling unbounded-depth computation over encrypted data.
**Threshold FHE (ThFHE).** The most relevant construction for Newton is Threshold FHE, where a t-of-n threshold decryption key would be distributed across operators. The evaluation would proceed as follows:
1. **Encryption.** The client encrypts policy inputs under the system's combined FHE public key, producing an FHE ciphertext.
2. **Homomorphic evaluation.** Operators evaluate the policy circuit directly over the FHE ciphertext. All operators process the same encrypted data — no secret sharing or inter-operator communication is needed during evaluation.
3. **Partial decryption.** Each operator computes a partial decryption share of the result ciphertext. Each share is a noise-masked intermediate value that reveals nothing about the plaintext by itself.
4. **Threshold reconstruction.** Any t partial decryption shares are combined to recover the policy verdict. Only the final boolean result (authorized/denied) emerges in the clear — the underlying data remains encrypted throughout.
**Current limitations.** FHE evaluation remains approximately 1,000-1,000,000x slower than plaintext computation depending on circuit complexity. For Newton's policy workloads — boolean logic and threshold comparisons rather than floating-point arithmetic — the overhead is at the lower end of this range, but production-grade ThFHE for general policy evaluation remains a multi-year research effort. The protocol architecture is designed so that a future transition from MPC-based to FHE-based evaluation would be transparent to clients, policy authors, and the smart contract layer: the same HPKE-encrypted inputs, the same Rego policies, the same BLS-attested outputs.
# BLS Attestation Protocol \[BLS signature scheme on BN254, the Two-Digest System, aggregation algorithm, and ECDSA attestations in the Newton Protocol.]
## BLS Attestation Protocol
Newton Protocol uses two complementary signature schemes: BLS (Boneh-Lynn-Shacham) signatures for aggregatable operator consensus, and ECDSA signatures for individual data attestations. The combination provides both compact multi-party proofs and individually attributable data provenance.
### BLS Signature Scheme
Newton uses BLS signatures on the BN254 (alt\_bn128) elliptic curve, chosen for its compatibility with Ethereum's precompiled contracts at addresses 0x06 (EIP-196: addition and scalar multiplication) and 0x08 (EIP-197: pairing check). Operators hold BLS private keys and register their corresponding public keys in the BLS APK registry contract.
The curve provides two groups: G1 (signatures, 32 bytes compressed) and G2 (public keys, 64 bytes compressed). The bilinear pairing function `e: G1 x G2 -> GT` enables signature aggregation: given individual signatures `sigma_1, ..., sigma_n` over the same message, the aggregate signature `sigma = sigma_1 + ... + sigma_n` can be verified against the aggregate public key `apk = pk_1 + ... + pk_n` with a single pairing check.
The on-chain verification equation is:
```
e(sigma, -G2) * e(H(m), apkG2) == 1
```
Where `sigma` is the aggregated BLS signature (G1 point), `G2` is the generator of the G2 group, `H(m)` is the message hash mapped to G1 (the consensus digest), and `apkG2` is the aggregated public key in G2 queried from the BLS APK registry at the task's reference block.
For multi-quorum verification, a gamma randomization factor prevents rogue public key attacks:
```
e(sigma + gamma * apk, -G2) * e(H(m) + gamma * G1, apkG2) == 1
```
The gamma factor binds each quorum's signature to its public key, preventing an attacker from choosing a rogue public key that cancels another operator's key during aggregation.
### Two-Digest System
BLS signature aggregation requires all operators to sign the exact same message. However, operators independently generate unique ECDSA attestations over the policy data they fetched (each operator signs with their own ECDSA key, producing a different signature). This creates a fundamental conflict: the task response includes attestation fields that differ per operator, so the hash of the full response differs.
The Two-Digest System resolves this by computing two distinct digests from the same task response:
| Digest Type | Computation | Attestation Fields | Used For |
|------------------|-----------------------|--------------------------|-------------------------------------------|
| Consensus Digest | Consensus digest hash | Zeroed out (empty bytes) | BLS signing and on-chain BLS verification |
| Full Digest | Full digest hash | Included | Contract storage, challenge verification |
**Consensus digest computation** (implemented in both the off-chain service layer and the on-chain contract library):
1. Clone the task response.
2. For each policy data entry, set the attestation field to empty bytes.
3. ABI-encode the modified response.
4. Compute keccak256 over the encoded bytes.
Because all operators zero out attestations before hashing, they all produce the same consensus digest regardless of their individual ECDSA signatures. This enables BLS aggregation while preserving the full attestation data in the actual response for challenge verification.
The verification flow on-chain proceeds through a middleware chain: the TaskManager delegates to a response handler, which computes the consensus digest and passes it to the BLS signature checker for pairing verification.
### BLS Aggregation Algorithm
The aggregation process proceeds as follows:
```mermaid
flowchart TD
GW[Gateway publishes task] --> O1[Operator 1: Evaluate + BLS sign]
GW --> O2[Operator 2: Evaluate + BLS sign]
GW --> ON[Operator N: Evaluate + BLS sign]
O1 --> AGG[Aggregation Layer]
O2 --> AGG
ON --> AGG
AGG --> V[Verify individual BLS signatures]
V --> Q[Check stake-weighted quorum]
Q --> A[Aggregate BLS signatures]
A --> SUB[Submit to TaskManager.respondToTask]
```
1. **Individual verification.** Each operator's BLS signature is verified against their registered public key before being accepted for aggregation. This prevents rogue key attacks where a malicious operator submits a crafted signature.
2. **Quorum check.** The aggregator computes the total stake of signing operators and compares it against the per-task quorum threshold percentage (0-100). The quorum check is: `signed_stake >= total_stake * quorum_threshold / 100`. The aggregator returns early once quorum is met to minimize latency.
3. **Signature aggregation.** Once quorum is reached, the BLS aggregation layer produces the aggregate signature along with non-signer stakes and signature data needed for on-chain verification.
The protocol selects a reference block for aggregate public key lookups that ensures validity at on-chain verification time.
### ECDSA Attestations
Each operator produces an ECDSA attestation over the policy data it fetched, providing individually attributable proof of data provenance. The attestation message hash is:
```
message_hash = keccak256(abi.encodePacked(
policy_data_bytes,
policy_data_address,
expire_block,
wasm_cid,
args_cid
))
```
The operator signs this hash with its ECDSA private key (secp256k1). The resulting signature is included in the attestation field of the policy task data. On-chain, the TaskManager validates these attestation signatures during response submission, ensuring that the policy data was genuinely fetched and attested by registered operators.
# Streaming Consensus Protocol \[Newton's two-phase streaming consensus protocol: distributed WASM data collection, median-based normalization, and NATS messaging architecture.]
## Streaming Consensus Protocol
### Problem Statement
When operators independently fetch time-sensitive data from external sources (price feeds, sanctions lists, risk scores), they may receive different values due to timing differences, API response variability, or data source updates between requests. Since BLS signature aggregation requires all operators to sign the identical message, even minor numeric discrepancies in policy task data produce different consensus digests, causing aggregation to fail.
### Protocol Architecture
Newton solves this through a streaming two-phase consensus protocol built on NATS messaging. The protocol separates data collection (Prepare phase) from policy evaluation and BLS signing (Evaluate phase), using pipelined streaming to minimize end-to-end latency. Although the Evaluate phase performs two logical steps — policy evaluation and BLS signing — these execute atomically within a single NATS round-trip: each operator evaluates the policy, immediately signs the result, and streams the signed response back to the gateway.
```mermaid
flowchart TB
subgraph PREPARE[PREPARE PHASE: Distributed WASM Data Collection]
P1Pub[Gateway publishes Prepare request via NATS]
subgraph P1Stream[Operators execute WASM independently]
O1P1[Op 1: execute WASM]
O2P1[Op 2: execute WASM]
O3P1[Op 3: execute WASM]
end
Consensus[Gateway computes consensus at quorum]
P1Pub --> P1Stream
P1Stream --> Consensus
end
subgraph EVALUATE[EVALUATE PHASE: Policy Evaluation + BLS Signing]
P2Pub[Gateway publishes Evaluate request via NATS]
subgraph P2Stream[Operators evaluate + BLS-sign]
O1P2[Op 1: evaluate + sign]
O2P2[Op 2: evaluate + sign]
O3P2[Op 3: quorum already met]
end
P2Pub --> P2Stream
end
Consensus --> P2Pub
```
The protocol achieves low latency through three key properties:
* **Non-blocking fan-out.** The gateway publishes a single NATS message that reaches all subscribed operators simultaneously, rather than making sequential per-operator requests.
* **Streaming response collection.** Operator responses are processed individually as they arrive over the NATS subscription, not collected in batch.
* **Early quorum exit.** Quorum is checked on every incoming Evaluate response. The moment the stake-weighted threshold is met, the protocol completes without waiting for remaining operators.
**Pipelining.** Operators that complete the Prepare phase first begin the Evaluate phase immediately upon receiving the consensus data, while slower operators are still completing their Prepare phase work. This pipelining eliminates sequential gaps between phases and reduces end-to-end latency to approximately the time of the slowest operator needed for quorum, rather than the slowest operator in the set.
### Prepare Phase: Distributed WASM Data Collection
The gateway publishes a Prepare request to the NATS task subject for the relevant chain. All operators subscribed to that chain's task subject receive the request simultaneously. Each operator independently executes the WASM data provider — the "policy data WASM" — in its own sandboxed Wasmtime environment ([WASM-Sandboxed Data Providers](/whitepaper/policy-engine#wasm-sandboxed-data-providers)), fetches external data from the configured data sources, and publishes a Prepare response to the task-specific response subject containing:
* The generated policy task data with ECDSA attestation
* A hash of the data for quick comparison
* A timestamp for freshness verification
This distributed WASM execution is a foundational property of Newton's decentralized architecture: every operator in the active validator set independently runs the data provider, ensuring that no single entity controls the inputs to policy evaluation. Each operator fetches data from external sources (sanctions feeds, oracle prices, risk scores) through its own network path, producing independent attestations over the data it observed. This redundancy provides both fault tolerance (no single data source failure blocks the protocol) and tamper resistance (manipulating the data input requires compromising a quorum of independent data paths).
Prepare phase responses are unsigned (no BLS signature). Operators do not evaluate the Rego policy during this phase; they only fetch and attest data. Responses stream back to the gateway as each operator completes, enabling the gateway to begin consensus computation as soon as a quorum of responses has arrived.
### Consensus Computation
The gateway applies median-based normalization to produce a single canonical dataset from the Prepare phase responses:
1. **Early consensus check.** If all operator responses contain identical policy task data, the gateway skips median computation entirely and uses the common data as-is. This avoids unnecessary processing for deterministic data sources.
2. **Numeric field extraction.** The gateway parses each operator's policy data JSON and extracts all numeric fields (integers and floating-point values) by path.
3. **Median computation.** For each numeric field across all operator responses, the gateway computes the median value. The median is chosen over the mean because it is robust to outliers: a single operator returning an anomalous value does not skew the result.
4. **Tolerance verification.** Each operator's value is checked to ensure it falls within a configurable tolerance of the computed median. Values outside tolerance are rejected. This prevents operators with stale or manipulated data from influencing the consensus dataset.
5. **Normalization.** All operator responses are normalized to use the median values, producing a canonical policy task dataset that all operators will use in the Evaluate phase.
| Parameter | Reference Default | Description |
|---------------------|-------------------|------------------------------------------------|
| Tolerance threshold | 10% | Maximum deviation from median before rejection |
### Evaluate Phase: Policy Evaluation and BLS Signing
The gateway publishes an Evaluate request to the NATS task subject with the consensus policy task data, the policy configuration fetched from on-chain, the original intent, and the task's reference block. Each operator:
1. Receives the Evaluate request via its NATS subscription.
2. Constructs the task response using the consensus policy task data (not their own data).
3. Fetches the Rego policy from IPFS by CID.
4. Evaluates the Rego policy against the canonical data.
5. Computes the consensus digest (attestations zeroed).
6. Signs the consensus digest with their BLS key.
7. Publishes the BLS-signed response to the task-specific NATS response subject.
Because all operators evaluate with identical data and the same deterministic Rego policy, they produce identical evaluation results and identical consensus digests, enabling BLS aggregation. Policy evaluation and BLS signing execute atomically within a single operator step — there is no separate signing phase, because the deterministic evaluation guarantees that all honest operators arrive at the same result without requiring an intermediate consistency check.
The gateway checks quorum on every incoming Evaluate response. As soon as the stake-weighted quorum threshold is met, the aggregation completes and the aggregate BLS signature is submitted on-chain. Remaining operator responses are discarded. This early quorum exit means that in a network of N operators where quorum requires K, the protocol completes as soon as the K-th fastest operator responds — regardless of how slow the remaining N-K operators may be.
**Pipelining detail.** Because NATS delivers the Evaluate request to all operators simultaneously, operators that completed the Prepare phase earliest begin evaluation immediately. In practice, the fastest operators may complete Prepare and Evaluate before the slowest operators finish Prepare, collapsing two-phase latency toward a single-phase equivalent for the quorum-critical subset of operators.
### NATS Messaging Architecture
Newton uses NATS as its core messaging layer for all gateway-operator communication. NATS provides sub-millisecond message dispatch, automatic reconnection with configurable backoff, guaranteed per-subject ordering, and natural publish-subscribe fan-out.
**Subject hierarchy.** All NATS subjects follow a structured namespace:
```
newton.
tasks.
{chain_id}.
prepare.request # Prepare requests (Gateway → Operators)
evaluate.request # Evaluate requests with consensus data (Gateway → Operators)
cancel # Task cancellation
broadcast # All-chain broadcasts
responses.
{task_id}.
prepare # Prepare responses — unsigned (Operators → Gateway)
evaluate # Evaluate responses — BLS-signed (Operators → Gateway)
errors # Error responses
operators.
{operator_id}.
heartbeat # Health checks
direct # Direct messages
shares.{task_id} # Threshold decryption shares (Operator → Operator, E2E encrypted)
announce # Operator announcements
dkg.
{ceremony_id}.
commitments # DKG commitment broadcasts (Operator → All)
shares.{operator_id} # DKG share distribution (Operator → Operator, E2E encrypted)
```
Chain-specific subjects ensure operators only receive tasks for the chains they are registered to validate. Task-specific response subjects enable the gateway to subscribe to a single wildcard subject per task and receive Prepare and Evaluate responses in a unified stream. Operator share subjects (`shares.{task_id}`) carry threshold decryption partial shares encrypted end-to-end to the recipient operator's individual X25519 public key. DKG subjects are used during key generation ceremonies when the operator set changes.
**Message authentication.** All gateway-published messages include a gateway signature (EIP-712 typed structured data) that operators verify before processing. This prevents unauthorized parties from injecting tasks into the NATS subject namespace.
**Deployment modes.** NATS supports two deployment configurations:
| Mode | Description | Use Case |
|----------|---------------------------------------------------|-----------------------------------|
| Embedded | NATS server runs in-process within the gateway | Local development and testing |
| Cluster | External multi-node NATS cluster with replication | Staging and production deployment |
The embedded mode requires zero additional infrastructure, enabling developers to run the full protocol locally. The cluster mode provides high availability through multi-node replication across availability zones, with automatic failover and partition tolerance.
**Operator discovery.** Operators discover tasks through subject-based routing rather than explicit registration with the gateway. An operator subscribes to its chain's task subjects upon startup and automatically receives all relevant tasks. This decouples operator lifecycle management from the gateway and enables seamless operator additions and removals without gateway reconfiguration.
### Centralized Single-Phase Mode
For tasks where policy data is deterministic or pre-cached (no operator-independent data fetching needed), the protocol supports a simplified single-phase mode. The gateway generates the policy task data centrally and publishes a single Evaluate request — skipping the Prepare phase entirely. Operators evaluate the policy, BLS-sign, and stream responses. This mode reduces latency to a single NATS round-trip and is appropriate for policies that do not depend on external data sources.
# Cross-Chain Interoperability \[Source and destination chain model, operator table synchronization, BLS verification, and cross-chain security model in the Newton Protocol.]
## Cross-Chain Interoperability
### Source and Destination Chain Model
Newton Protocol supports cross-chain authorization through a source chain / destination chain architecture compliant with EigenLayer's ELIP-008 specification:
| Chain Type | Role | Examples |
|-------------------|-----------------------------------------------------------------------------|------------------------------------------|
| Source Chain | Operator registration, BLS key management, staking, slashing via EigenLayer | Ethereum mainnet (1), Sepolia (11155111) |
| Destination Chain | Task execution, policy evaluation, transaction authorization | Arbitrum, Optimism, Polygon, Base |
The source chain is always an EigenLayer-supported chain (Ethereum mainnet or Sepolia). Operators register once on the source chain, and their registration data (BLS public keys, stake amounts, operator set membership) is synchronized to destination chains through Newton's consensus-based operator table synchronization protocol.
### Operator Table Synchronization
Newton uses a decentralized, consensus-based mechanism to synchronize operator set state from the source chain to destination chains. The protocol separates two concerns:
* **Slow path (operator table updates).** When operator membership changes on the source chain — registration, deregistration, stake updates, or slashing events — Newton operators collectively produce a BLS-signed Merkle root of the current operator table. This signed root serves as a portable, cryptographically verifiable snapshot of the operator set at a specific block height.
* **Fast path (task certificate verification).** Destination chains verify individual task certificates against the already-synchronized operator table. This is frequent and cheap — a single BLS pairing check against the stored aggregate public key.
The synchronization flow proceeds as follows:
```mermaid
flowchart TB
subgraph SOURCE[SOURCE CHAIN - Ethereum]
EL[EigenLayer Contracts]
ASM[AVS Service Manager]
OR[Operator Registry]
end
subgraph CONSENSUS[Operator Table Consensus]
OTC[Operators produce BLS-signed Merkle root]
end
subgraph TRANSPORT[Permissionless Transport]
REL[Relayer delivers signed root to destination chains]
end
subgraph DEST[DESTINATION CHAIN - L2]
OTU[Operator Table Updater]
CV[Certificate Verifier]
TM[TaskManager]
end
EL --> OTC
ASM --> OTC
OR --> OTC
OTC --> REL
REL --> OTU
OTU --> CV
CV --> TM
```
**Operator Table Updater.** The on-chain contract on each destination chain that receives and verifies BLS-signed operator table roots. It validates the aggregate signature against the previously known operator set (bootstrapped during initial deployment) and updates the local operator table. Anyone can submit a valid signed root — the transport layer is permissionless and requires no trusted intermediary.
**Certificate Verifier.** The single integration point for applications on destination chains. When a task is completed and a BLS certificate is produced, the Certificate Verifier checks the aggregate signature against the operator table snapshot at the reference block height. It supports both proportion-based thresholds (e.g., >66% of stake signed) and nominal thresholds (e.g., absolute stake amount).
**Staleness protection.** Each operator set has a configurable maximum staleness period. If the operator table has not been updated within this period, the Certificate Verifier rejects new certificates until a fresh update is delivered. This ensures destination chains do not operate on stale operator sets that may include deregistered or slashed operators.
### Operator Table Calculator
Each destination chain maintains an operator table: a mapping of operator IDs to BLS public keys and stake weights. The table calculator contract computes and stores aggregate public keys per quorum, enabling efficient BLS verification without per-operator key lookups. When an operator table update arrives (via a BLS-signed Merkle root), the calculator processes the delta — adding newly registered operators, removing deregistered ones, and updating stake weights. The aggregate public key is recomputed to reflect the current set.
This design decouples destination chains from live source chain connectivity. Once an operator table is synchronized, destination chains can independently verify task certificates until the next table update is required.
### Cross-Chain BLS Verification
On source chains, BLS verification queries the BLS APK registry directly for operator public keys at the specified reference block.
On destination chains, a BN254 certificate verification path is used instead. The BN254 certificate contains the aggregated BLS signature and the quorum APK snapshot from the source chain. This allows destination chains to verify BLS signatures without maintaining a live connection to the source chain's registry.
Both paths share common verification logic for consensus digest computation and signature validation, ensuring consistent behavior across chain types.
### EIP-712 BN254Certificate
Cross-chain BLS verification uses EIP-712 typed structured data for certificate encoding. The certificate binds the operator set snapshot (quorum APK, total stake, non-signer information) to a specific source chain and block number, preventing cross-chain replay of certificates. The reference timestamp in each certificate must correspond to an actual recorded operator table update, ensuring certificates are always verified against a known-good snapshot of the operator set — not the current state, which may have changed since the task was created.
### Security Model
The cross-chain synchronization protocol inherits its security from the same BLS-attested consensus used for task verification:
* **Economic security.** Operators who sign an incorrect operator table root (e.g., omitting a registered operator or including a deregistered one) are subject to the same slashing conditions as operators who sign incorrect task responses. The cost of producing a fraudulent operator table update equals the cost of corrupting a quorum of the operator set.
* **Liveness.** Operator table updates require a quorum of operators to sign. If fewer than the quorum threshold are online, updates stall but existing destination chain operations continue using the last valid operator table until the staleness period expires.
* **ZK verification path.** As an alternative to BLS-signed roots, Newton's architecture supports a future transition to zero-knowledge proofs of operator set state transitions. A ZK proof that the source chain's operator registry transitioned from state A to state B can be verified on any destination chain with a single proof verification, achieving 1-of-N honest relayer liveness with full cryptographic security — eliminating the need for a quorum of operator signatures during table updates.
# Slashing and Challenge Mechanism \[Economic deterrents, challenge windows, ZK proof generation, and dispute resolution in the Newton Protocol.]
## Slashing and Challenge Mechanism
### Economic Deterrent
Newton's slashing mechanism creates an economic cost for incorrect attestations. The slashing amount is a governance-configurable percentage of the operator's staked ETH/LST (reference default: 10%).
Slashing is executed through EigenLayer's instant slashing mechanism, which immediately reduces the operator's stake in the delegation manager. The severity of the penalty is calibrated to be high enough to deter rational economic actors from producing false attestations while remaining proportional to a single offense.
### Challenge Window
The challenge window is the period during which any party can dispute a task response. The window is governance-configurable (reference default: 100 blocks, approximately 20 minutes on Ethereum mainnet). The window begins when the task response is recorded on-chain and ends at the response block plus the configured challenge window length.
Tasks can be evaluated and used as soon as the attestation is available. The challenge window provides a dispute period during which incorrect attestations can be challenged and slashed, but it does not block task consumption.
### ZK Proof Generation
When a challenger detects a discrepancy between the on-chain response and their independent evaluation, they generate a zero-knowledge proof using zero-knowledge virtual machines. The proof circuit is the complete Newton Rego evaluation engine compiled to a RISC-V target — a full policy interpreter, not a bespoke per-policy circuit. This means any policy written in Rego is automatically ZK-provable without specialized circuit engineering. The compiled engine:
1. Takes as public inputs: the task, the task response (including policy task data), and the expected evaluation result.
2. Re-evaluates the Rego policy against the policy task data contained in the response.
3. Produces a proof that the correct evaluation result differs from the on-chain response.
The circuit does not re-verify ECDSA attestations (that is handled on-chain by the TaskManager). It focuses solely on proving that the policy evaluation was incorrect given the attested inputs.
### Challenge Verification Flow
```mermaid
flowchart TD
A[Detect task response] --> B[Re-evaluate policy]
B --> C{Result matches?}
C -->|No| D[Generate ZK proof]
D --> E[Submit challenge]
E --> F[Contract verifies]
F --> G[Identify signers]
G --> H[Slash via InstantSlasher]
C -->|Yes| I[No action needed]
```
The challenge resolution function on the TaskManager:
1. Validates the challenge is within the challenge window.
2. Verifies the ZK proof against the configured verifier.
3. Confirms that the proven result differs from the stored response.
4. Identifies which operators signed the incorrect response using the aggregate signature data.
5. Executes slashing through EigenLayer's instant slashing mechanism.
### Direct Attestation Challenge Path
Newton supports an optimistic direct attestation path where policy clients validate attestations before the regular on-chain flow completes. This introduces a separate challenge vector:
1. The client validates the attestation directly on the TaskManager, which verifies the BLS signature and stores task and response hashes.
2. The regular path (task creation and response) completes asynchronously.
3. If the direct path hashes differ from the regular path hashes, any party can challenge the mismatch.
4. The function compares both hash sets, re-verifies signatures, slashes signing operators (on source chains where the BLS APK registry is available), and invalidates the direct attestation.
A spent sentinel value prevents double-spending: once an attestation is consumed via either the regular or direct flow, the sentinel is written to the attestation expiration mapping, and any subsequent attempt to use the same attestation reverts.
# Policy Engine and Composability \[Newton Rego policy language, cryptographic extensions, WASM-sandboxed data providers, content addressing, and multi-plugin composition.]
:::note
The policy-set implementation composes an ordered set of independent policies. Each owns its params and optional WASM oracle; approval is the AND of their verdicts. Tasks freeze the set ID, revision, and specs, and owner replacement invalidates old attestations. See [Core Concepts](/developers/overview/core-concepts#policy-set) for implementation details.
:::
## Policy Engine and Composability
### Rego Policy Language
Newton's policy engine supports **Rego v1 syntax** through Newton's fork of **Regorus 0.9.0**. Its compatibility target is an **OPA v1.2.0-compatible subset plus Newton-specific extensions**. Newton extends the engine with privacy-preserving operations and cryptographic primitives for cross-chain signature verification and hash computation, but it does not implement every OPA built-in. Existing OPA policies and libraries must be checked for unsupported standard crypto/JWT, GraphQL, JSON Patch, network/CIDR/DNS, AWS provider, metadata, and module-parsing functions before use.
Rego policies are pure functions: given the same input data and policy rules, evaluation always produces the same result. This determinism is essential for Newton's security model -- it enables challengers to independently verify any evaluation and prove correctness via zero-knowledge proofs. Policies are content-addressed on IPFS via CID, ensuring all operators fetch and evaluate the exact same rule set.
### Newton-Crypto Extensions
Newton Rego adds Ethereum-specific cryptographic builtins to the Rego evaluator:
| Function | Arguments | Return | Purpose |
|---------------------------------------------------------|---------------------------------------------------|--------------------------------------------|-------------------------------------------|
| `newton.crypto.ecdsa_recover_signer(sig, hash)` | 65-byte hex signature, 32-byte hex keccak256 hash | Checksummed Ethereum address (0x-prefixed) | Recover signer from raw hash |
| `newton.crypto.ecdsa_recover_signer_personal(sig, msg)` | 65-byte hex signature, raw message string | Checksummed Ethereum address (0x-prefixed) | Recover signer from personal\_sign message |
These builtins enable policies to verify Ethereum signatures within the Rego evaluation context, supporting use cases such as verifying that a transaction intent was signed by an authorized party, checking multi-signature approvals, or validating delegation chains.
Extensions follow the `newton.*` namespace convention and are registered at engine initialization. The cryptographic extensions are isolated to minimize upstream divergence from the base Rego engine. The extension model is designed for cross-chain extensibility — the `newton.crypto` namespace provides a unified surface for adding signature verification and hash computation primitives across blockchain cryptographic schemes, enabling policies that operate across chain boundaries without per-chain policy rewrites.
### WASM-Sandboxed Data Providers
External data (sanctions lists, risk scores, oracle feeds, API responses) is fetched through WASM plugins that execute in a sandboxed Wasmtime environment. Each plugin implements the WebAssembly Component Model interface, receiving JSON arguments and returning JSON data.
The sandbox enforces strict resource limits to prevent denial-of-service and resource exhaustion:
| Resource | Reference Default | Purpose |
|-------------------|-------------------|------------------------|
| CPU instructions | 100 million | Prevent infinite loops |
| Stack memory | 64 MiB | Prevent stack overflow |
| HTTP requests | 50 per execution | Rate limiting |
| HTTP request size | 1 MiB per request | Memory protection |
| WASM binary cache | 100 MiB | Disk usage limit |
### NPE-Encrypted Secrets
Policy data providers often require API keys, authentication tokens, or other secrets to access external data sources. Newton encrypts these secrets using the Newton Privacy Envelope (NPE), the same HPKE-based construction used for policy data privacy ([Privacy Architecture](/whitepaper/privacy-architecture)):
1. Clients encrypt secrets into a SecureEnvelope using the system's combined X25519 public key (the DKG-produced threshold key stored on-chain in the operator registry).
2. Encrypted envelopes are uploaded and stored in the encrypted data store.
3. During the [Prepare Phase](/whitepaper/streaming-consensus#prepare-phase-distributed-wasm-data-collection), when operators execute WASM data providers, the encrypted secrets are distributed alongside the task payload.
4. Each operator produces a partial decryption share using its DKG-produced share of the threshold private key and exchanges shares with peers via encrypted NATS messages (see [Privacy Data Flow](/whitepaper/privacy-architecture#privacy-data-flow)). Once t shares are collected, the operator reconstructs the secret locally.
5. The decrypted secret is injected into the WASM sandbox for the duration of the data provider execution. Decrypted secrets exist only in memory during execution and are never persisted in plaintext.
6. After WASM execution completes, the decrypted material is dropped.
This approach eliminates external key management dependencies and central points of trust. No single entity — including the gateway — ever holds the complete decryption key for policy client secrets. The threshold decryption mechanism is identical to the one used for user privacy data ([Privacy Data Flow](/whitepaper/privacy-architecture#privacy-data-flow)), meaning policy client secrets and user privacy data flow through the same cryptographic pipeline, simplifying the trust model to a single well-audited construction.
### Policy Versioning and Content Addressing
Policy versioning operates at two levels:
**Semantic versioning at the contract level.** The TaskManager enforces a minimum compatible policy version, rejecting tasks that reference policy factories below the minimum. This prevents protocol-level incompatibility between factory versions.
**Content addressing at the storage level.** Policies, schemas, and metadata are stored on IPFS and referenced by CID: policy CID (Rego rules), schema CID (JSON schema for secrets validation), and metadata CID (policy metadata). CIDs are immutable by definition -- the same content always produces the same CID, and any change produces a different CID. This provides tamper-evident policy storage without requiring on-chain storage of full policy code.
### Multi-Plugin Composition
A single policy evaluation can invoke multiple WASM data providers in sequence. The policy task data supports multiple policy data entries, each with an independent WASM binary, arguments CID, and attestation. This enables composable data pipelines: one plugin fetches sanctions data, another fetches risk scores, a third fetches on-chain state, and the Rego policy evaluates all results together.
# Decentralization Guarantees \[Operator set model, BLS quorum requirements, deterministic evaluation, and mechanisms ensuring no single point of failure in the Newton Protocol.]
## Decentralization Guarantees
### Operator Set Model
Newton operators are permissioned for quality and accountability but decentralized for neutrality and resilience. The operator set is not permissionless: operators must meet operational requirements (uptime, response time, geographic distribution) and compliance requirements (legal entity, jurisdiction). This creates a "credibly vetted decentralized operator set" -- no single entity operates enough stake to unilaterally determine outcomes.
### BLS Quorum Requirement
No single operator (or small coalition below the quorum threshold) can produce a valid attestation. The BLS aggregation mechanism requires a configurable majority of staked operators to agree. With a 67% quorum threshold and no operator controlling more than 33% of total stake, at least three independent operator entities must agree for any attestation to be valid.
### Deterministic Evaluation
Policy evaluation is deterministic: the same Rego policy evaluated against the same policy task data always produces the same result. This property is foundational to Newton's security model:
* **Verifiability.** Any party can independently verify an evaluation by fetching the same policy (by IPFS CID) and the same data (from the on-chain task response).
* **Challengeability.** Incorrect evaluations are provable via zero-knowledge proofs because the circuit can re-execute the same deterministic computation.
* **Auditability.** Regulators can replay evaluations to verify that policies were applied correctly.
### Geographic and Jurisdictional Distribution
Operators are encouraged to distribute across multiple geographic regions and legal jurisdictions. This distribution provides resilience against:
* Regional network outages or partitions.
* Jurisdiction-specific regulatory actions that might compel a subset of operators to behave differently.
* Physical access attacks targeting a single data center.
The protocol automatically routes around unavailable operators, maintaining service continuity even if a geographic cluster becomes unreachable.
### Trustless Dispute Resolution
The challenge mechanism does not rely on any trusted party. Any entity (not just registered operators) can submit a challenge with a valid zero-knowledge proof. The on-chain verification is purely mathematical: the contract verifies the ZK proof, compares the result, and executes slashing if a mismatch is confirmed. No governance vote, multi-sig approval, or human judgment is required.
### EigenLayer Restaking Security
Economic security scales with the total stake restaked through EigenLayer. As more ETH/LSTs are delegated to Newton operators, the cost of a 33% attack (controlling enough stake to produce false attestations) increases proportionally. This creates a quantifiable security parameter: the economic cost of attacking Newton equals the value of stake that must be accumulated and would be slashed.
### Compliance Receipt as Public Good
Every policy evaluation produces an immutable compliance receipt on-chain: a record binding the transaction intent, the policy evaluated, the operator responses, the aggregate signature, and the block number. These receipts are publicly verifiable. Any party -- regulators, auditors, counterparties, or the public -- can inspect the receipt to confirm that a specific policy was evaluated for a specific transaction and what the outcome was. This transparency is a structural property of the system, not an administrative decision. The on-chain audit trail cannot be selectively edited or suppressed because it is replicated across all Ethereum nodes.
### Rotating Gateway via VRF/VDF Leader Selection
The gateway role — orchestrating task distribution, collecting responses, and triggering BLS aggregation — is not a permanent infrastructure position held by a single entity. Newton implements a rotating gateway model where operators take turns serving as the gateway for each epoch, selected through a verifiable random process analogous to Ethereum's consensus leader selection.
**Epoch-based rotation.** Time is divided into epochs of configurable length (measured in blocks on the source chain). At the start of each epoch, a new gateway operator is selected from the active operator set. The selected operator assumes the gateway role for the duration of the epoch: it runs the NATS orchestration layer, publishes task requests, collects operator responses, and coordinates BLS aggregation. At epoch boundaries, the role transitions to the next selected operator.
**VRF leader selection.** The gateway for each epoch is determined by a Verifiable Random Function (VRF). Each operator computes a VRF output using its private key and the epoch number as input: `(output, proof) = VRF(sk_operator, epoch_number)`. The operator with the lowest VRF output is selected as the gateway for that epoch. The VRF proof is published on-chain, allowing any party to verify that the selection was correct without learning other operators' VRF outputs in advance. This provides:
* **Unpredictability.** No party can predict the next gateway before the epoch boundary, preventing targeted DoS attacks against the upcoming gateway.
* **Unbiasability.** No operator can influence the selection outcome without abandoning their VRF output (which would forfeit their chance to be selected).
* **Verifiability.** Any observer can verify the VRF proof on-chain, ensuring the selection is legitimate.
**VDF time-lock (optional hardening).** For additional security against adversaries who might precompute VRF outputs across multiple operator keys, a Verifiable Delay Function (VDF) can be composed with the VRF output. The VDF introduces a sequential computation barrier — a minimum wall-clock time that must elapse to compute the final selection value — preventing an adversary from evaluating all possible selections faster than real time.
**Failover.** If the selected gateway fails to publish heartbeats within a configurable timeout, the operator with the next-lowest VRF output assumes the gateway role for the remainder of the epoch. This provides automatic failover without manual intervention or governance action. The failover sequence is fully determined by the VRF outputs, so all operators independently agree on the backup gateway without coordination.
**Operator as gateway candidate.** Every registered operator is a gateway candidate. The gateway role requires no additional infrastructure beyond what operators already run — the NATS orchestration, response collection, and aggregation coordination are lightweight stateless functions that any operator can perform. This ensures that the gateway role adds no centralization pressure and scales naturally with the operator set.
### No Single Point of Failure
Newton's architecture eliminates single points of failure at every layer. The gateway is a rotating orchestration role assigned to operators via VRF-based leader selection ([Rotating Gateway](#rotating-gateway-via-vrfvdf-leader-selection)), ensuring that no single entity permanently controls task distribution. Operators are independent processes run by different entities across different infrastructure providers. The NATS messaging layer supports clustered deployment with automatic failover and partition tolerance. Privacy data is protected by threshold decryption — no single operator or gateway holds the complete decryption key. Policy storage uses content-addressed distribution with built-in redundancy. The only centralized components are the Ethereum smart contracts themselves, which inherit Ethereum's own decentralization and availability guarantees.
# Security Properties \[Formal threat model, rogue key attack prevention, cryptographic security reductions, post-quantum readiness, and performance characteristics of the Newton Protocol.]
## Security Properties
### Threat Model
Newton's security analysis is grounded in a formal threat model that defines the system's actors, adversary capabilities, and security goals.
**Actors.** The system comprises three actor classes:
| Actor | Role | Trust Assumption |
|-----------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Policy Client (Application) | Submits transaction intents and policy evaluation requests | Semi-trusted: may attempt to bypass policies but cannot forge operator signatures |
| Operator | Evaluates policies, signs responses with BLS keys, participates in MPC evaluation | Honest-majority: fewer than 1/3 of staked operators are adversarial |
| Gateway (rotating) | Orchestrates task distribution via NATS, coordinates BLS aggregation; role rotates among operators each epoch via VRF ([Rotating Gateway](/whitepaper/decentralization#rotating-gateway-via-vrfvdf-leader-selection)) | Trusted for liveness and routing during its epoch; not trusted with data confidentiality (threshold decryption ensures no single party holds the decryption key) |
Additional actors include credential issuers (trusted for attribute correctness), the EigenLayer protocol (trusted for stake accounting and slashing execution), and end users (untrusted — all user inputs are validated).
**Adversary capabilities.** The threat model considers an adversary with the following capabilities:
1. **Network eavesdropping.** The adversary can observe all messages between clients, gateway, and operators on public networks.
2. **Message replay.** The adversary can capture and retransmit valid messages to any endpoint.
3. **Message modification.** The adversary can alter message contents in transit (active MITM).
4. **Operator compromise.** The adversary can compromise up to `t - 1` operators (below the quorum threshold `t`), obtaining their BLS private keys and ECDSA keys.
5. **Key compromise (targeted).** The adversary may obtain a single operator's or user's long-term private key through side-channel or social engineering.
6. **Collusion.** Compromised operators may collude to reconstruct secret-shared data or produce coordinated false attestations.
7. **Replay across contexts.** The adversary may attempt to reuse valid cryptographic artifacts (signatures, ciphertexts, attestations) across different policy clients, chains, or intents.
**Security goals.** The protocol is designed to satisfy the following properties under the adversary model above:
| Goal | Property | Defended By |
|--------------------------------|------------------------------------------------------------------------------------------------|----------------------------------------------------|
| **Confidentiality** | Encrypted policy inputs remain secret from any party without the decryption key | HPKE (IND-CCA2), MPC secret sharing |
| **Attestation unforgeability** | No adversary can produce a valid BLS aggregate signature without quorum agreement | BLS (EUF-CMA on BN254), stake-weighted quorum |
| **Authorization integrity** | Privacy data cannot be accessed without both user and application consent | Dual Ed25519 signatures (EUF-CMA) |
| **Context binding** | Cryptographic artifacts cannot be replayed across policy clients, chains, or intents | AAD binding (keccak256), intent hash in signatures |
| **Evaluation correctness** | Policy evaluations are deterministic and verifiable; incorrect results are provably detectable | ZK challenge proofs, deterministic Rego evaluation |
| **Accountability** | Operators who sign incorrect responses face economic penalties | Slashing (10% of stake), challenge window |
The following subsections detail how each security property is achieved.
### Rogue Key Attack Prevention
Rogue key attacks exploit BLS aggregation: an attacker chooses a public key that, when aggregated with honest keys, allows them to forge aggregate signatures. Newton prevents this through:
* **Individual signature verification before aggregation.** Every operator's BLS signature is verified against their registered public key before being accepted by the aggregator. A rogue key that does not correspond to a valid private key cannot produce a valid individual signature.
* **Gamma randomization for multi-quorum.** The pairing equation includes a gamma factor that binds each quorum's signature to its public key, preventing cross-quorum rogue key construction.
* **Proof of possession at registration.** Operators prove possession of their BLS private key during EigenLayer registration, preventing key-only attacks.
**Security reduction.** The unforgeability of BLS aggregate signatures reduces to the computational Diffie-Hellman (co-CDH) assumption on BN254. Under co-CDH, an adversary controlling fewer than `t` operators cannot produce a valid aggregate signature for any message not signed by the honest quorum. The proof-of-possession registration requirement eliminates the rogue key attack vector that would otherwise weaken the plain aggregate BLS scheme.
### Constant-Time Cryptographic Operations
All signature verification and key derivation operations use constant-time implementations from audited cryptographic libraries (secp256k1 for ECDSA, Ed25519 for signing, X25519 for key agreement, and RFC 9180 for HPKE). This prevents timing side-channel attacks that could leak key material through operation duration.
### BLS Subgroup Validation
BLS public keys and signatures are validated to be in the correct subgroups of G1 and G2 during deserialization. Points not in the correct subgroup would compromise the security of the pairing equation. The BLS libraries perform subgroup checks on all incoming points.
### HPKE AAD Binding
The AAD computation (`keccak256(abi.encodePacked(policy_client, chain_id))`) cryptographically binds each SecureEnvelope to a specific policy client and chain. This prevents:
* **Cross-context ciphertext reuse.** An encrypted envelope created for policy client A on chain X cannot be decrypted in the context of policy client B or chain Y, because the AAD mismatch causes the ChaCha20-Poly1305 authentication check to fail.
* **Chain replay attacks.** An envelope encrypted for a testnet chain ID cannot be replayed on mainnet.
* **Policy client impersonation.** An envelope encrypted for one policy client cannot be used by another, even if both share the same gateway.
**Security reduction.** The confidentiality of HPKE in Base mode reduces to the IND-CCA2 security of the underlying AEAD (ChaCha20-Poly1305) and the CDH assumption on Curve25519. An adversary who cannot solve CDH on Curve25519 cannot distinguish HPKE ciphertexts from random, even with access to a decryption oracle (excluding the challenge ciphertext). The AAD binding extends this guarantee: any modification to `policy_client` or `chain_id` produces an AAD mismatch that the AEAD authentication check rejects with overwhelming probability.
### Dual-Signature Authorization Binding
The dual-signature scheme for privacy authorization prevents several attack classes:
* **Unauthorized data access.** Encrypted data references require both user and application signatures, preventing either party from unilaterally accessing encrypted data.
* **Cross-intent replay.** Both signatures bind the intent hash, preventing reuse of authorization across different transactions.
* **Cross-client replay.** Both signatures bind the policy client address, preventing reuse across different applications.
* **Signature ordering.** The application signature covers the user's signature bytes, creating an unforgeable chain of authorization.
**Security reduction.** The unforgeability of the dual-signature scheme reduces to the EUF-CMA (Existential Unforgeability under Chosen Message Attack) security of Ed25519. An adversary who cannot break Ed25519's EUF-CMA property cannot forge either the user signature or the application signature. The chained structure — where the application signature covers the user's signature bytes — ensures that forging authorization requires breaking EUF-CMA for both keys independently: a valid application signature over a forged user signature is computationally infeasible, and vice versa.
### Composed Security of the Newton Privacy Envelope
The Newton Privacy Envelope (NPE) composes three independently secure primitives: HPKE encryption (IND-CCA2), Ed25519 dual-signature authorization (EUF-CMA), and BLS attestation (co-CDH on BN254). The composed protocol satisfies the following properties:
1. **Confidentiality.** Under the CDH assumption on Curve25519 and the IND-CCA2 security of ChaCha20-Poly1305, no adversary without the recipient's X25519 private key can recover the plaintext content of an NPE, even with access to a decryption oracle for other ciphertexts.
2. **Authorization unforgeability.** Under EUF-CMA of Ed25519, no adversary can produce a valid dual-signature authorization without both the user's and application's Ed25519 signing keys. The intent hash and policy client binding in both signatures prevent cross-context reuse.
3. **Attestation integrity.** Under co-CDH on BN254 and the honest-majority assumption (adversary controls fewer than `t` of `n` staked operators), no adversary can produce a valid BLS aggregate signature for an incorrect policy evaluation result.
These reductions are modular: compromising one primitive does not weaken the others. For example, if an adversary compromises HPKE confidentiality (breaking CDH on Curve25519), they learn plaintext data but cannot forge attestations (BLS) or authorization signatures (Ed25519). If an adversary compromises a single operator's BLS key, they cannot decrypt NPE ciphertexts (HPKE) or forge user authorization (Ed25519).
### Post-Quantum Readiness
Newton's cryptographic primitives rely on elliptic curve assumptions (CDH on Curve25519, co-CDH on BN254, DLP on secp256k1) that are vulnerable to quantum computers running Shor's algorithm. The protocol's design mitigates future quantum risk through HPKE's KEM-agnostic architecture:
| Component | Current Primitive | Post-Quantum Replacement | Standardization Status |
|--------------------------|-------------------|------------------------------------|------------------------------|
| NPE encryption | X25519 (DHKEM) | ML-KEM-768 (FIPS 203) | NIST standardized (2024) |
| Authorization signatures | Ed25519 | ML-DSA-65 (FIPS 204) | NIST standardized (2024) |
| BLS aggregate signatures | BN254 | Lattice-based aggregate signatures | Active research, no standard |
HPKE's modular KEM/KDF/AEAD composition means that replacing X25519 with ML-KEM requires changing only the KEM instantiation — the KDF, AEAD, and application-layer protocol remain unchanged. Ed25519 can be replaced with ML-DSA in the dual-signature scheme without structural changes. BLS aggregate signatures present the most significant migration challenge, as no standardized post-quantum aggregate signature scheme with comparable compactness exists. Until such schemes mature, BLS remains the practical choice given Ethereum's BN254 precompile support.
### Performance Characteristics
The cryptographic operations underlying Newton's protocol have been benchmarked to validate that sub-second consensus is achievable under production workloads. Detailed benchmark data — including per-operation latencies for HPKE encrypt/decrypt, Ed25519 sign/verify, BLS sign/verify/aggregate, and end-to-end task flow timing — is maintained in a companion performance report. The protocol's latency budget is dominated by network round-trips and policy evaluation rather than cryptographic operations, which complete in microsecond-to-low-millisecond ranges on commodity hardware.
# References and Appendix \[Standards, cryptographic primitives, privacy-preserving computation references, and the Newton Privacy Envelope binary wire format specification.]
## References
### Standards and Specifications
* **RFC 9180** -- Hybrid Public Key Encryption (HPKE). IRTF. [rfc-editor.org/rfc/rfc9180](https://www.rfc-editor.org/rfc/rfc9180)
* **EIP-196** -- Precompiled contracts for addition and scalar multiplication on the elliptic curve alt\_bn128. Ethereum Improvement Proposals. [eips.ethereum.org/EIPS/eip-196](https://eips.ethereum.org/EIPS/eip-196)
* **EIP-197** -- Precompiled contracts for optimal ate pairing check on the elliptic curve alt\_bn128. Ethereum Improvement Proposals. [eips.ethereum.org/EIPS/eip-197](https://eips.ethereum.org/EIPS/eip-197)
* **ELIP-008** -- Cross-Chain Operator Set Management. EigenLayer Improvement Proposals. [github.com/eigenfoundation/ELIPs](https://github.com/eigenfoundation/ELIPs)
### Messaging Infrastructure
* **NATS** -- Cloud native messaging system. [nats.io](https://nats.io/)
### Policy and Compliance
* **Open Policy Agent (OPA)** -- Policy engine and Rego language documentation. [openpolicyagent.org/docs/latest](https://www.openpolicyagent.org/docs/latest/)
### Newton Protocol Documentation
* [Newton Protocol Whitepaper](https://newton.xyz/whitepaper) -- Protocol vision, market context, use cases, and ecosystem overview.
* [RPC API Reference](/developers/reference/rpc-api) -- JSON-RPC API surface, request/response structures, error codes, and simulation modes.
* [Smart Contract Integration Guide](/developers/guides/smart-contract-integration) -- INewtonPolicyClient interface, PolicyClientRegistry, and attestation validation paths.
### Cryptographic Primitives
* **BLS on BN254** -- BLS signature operations on the BN254 (alt\_bn128) curve, compatible with Ethereum precompiles (EIP-196, EIP-197).
* **EigenLayer BLS Middleware** -- On-chain BLS verification and operator set management. [github.com/Layr-Labs/eigenlayer-middleware](https://github.com/Layr-Labs/eigenlayer-middleware)
* **HPKE (RFC 9180)** -- Hybrid Public Key Encryption for privacy-preserving data encryption. [rfc-editor.org/rfc/rfc9180](https://www.rfc-editor.org/rfc/rfc9180)
* **HPKE Formal Verification** -- Bhargavan, K. and Lipp, B. (2024). "A Mechanized Cryptographic Proof of the HPKE Standard." IACR ePrint 2024/243. [eprint.iacr.org/2024/243](https://eprint.iacr.org/2024/243)
* **Ed25519** -- EdDSA signing and verification on Curve25519.
* **X25519** -- Diffie-Hellman key agreement on Curve25519.
* **secp256k1 ECDSA** -- Ethereum-native signature scheme for attestations and key derivation.
### Privacy-Preserving Computation
* **Secure Multi-Party Computation (MPC)** -- Lindell, Y. (2020). "Secure Multiparty Computation (MPC)." *Communications of the ACM*, 64(1), 86-96. [dl.acm.org/doi/10.1145/3387108](https://dl.acm.org/doi/10.1145/3387108)
* **Honest-Majority MPC** -- Araki, T. et al. (2017). "Optimized Honest-Majority MPC for Malicious Adversaries -- Breaking the 1 Billion-Gate Per Second Barrier." *IEEE S\&P*. [ieeexplore.ieee.org/document/7958613](https://ieeexplore.ieee.org/document/7958613/) -- See also: Escudero, D. et al. (2022). "High-Throughput Secure Multiparty Computation with an Honest Majority in Various Network Settings." [arxiv.org/abs/2206.03776](https://arxiv.org/abs/2206.03776)
* **Fully Homomorphic Encryption (FHE)** -- Gentry, C. (2009). "A Fully Homomorphic Encryption Scheme." *PhD thesis, Stanford University*. [crypto.stanford.edu/craig/craig-thesis.pdf](https://crypto.stanford.edu/craig/craig-thesis.pdf)
* **Threshold FHE** -- Boneh, D. et al. (2024). "Toward Practical Threshold Fully Homomorphic Encryption." *ACM CCS*. [dl.acm.org/doi/10.1145/3658644.3690861](https://dl.acm.org/doi/10.1145/3658644.3690861)
### Post-Quantum Cryptography
* **ML-KEM (FIPS 203)** -- Module-Lattice-Based Key-Encapsulation Mechanism Standard. NIST, August 2024. [csrc.nist.gov/pubs/fips/203/final](https://csrc.nist.gov/pubs/fips/203/final)
* **ML-DSA (FIPS 204)** -- Module-Lattice-Based Digital Signature Standard. NIST, August 2024. [csrc.nist.gov/pubs/fips/204/final](https://csrc.nist.gov/pubs/fips/204/final)
***
*Newton Protocol -- Technical architecture for verifiable, privacy-preserving transaction authorization.*
***
## Appendix A: Newton Privacy Envelope Wire Format
This appendix specifies the binary serialization format for the Newton Privacy Envelope (SecureEnvelope) to enable interoperable implementations across languages and platforms.
### A.1 JSON Transport Format
The primary transport encoding is JSON, used in the JSON-RPC API between clients and the gateway:
```json
{
"enc": "",
"ciphertext": "",
"policy_client": "<0x-prefixed EIP-55 checksummed address>",
"chain_id": "",
"recipient_pubkey": ""
}
```
All hex strings are lowercase without `0x` prefix (except `policy_client` which follows Ethereum address conventions).
### A.2 Binary Wire Format
For compact transport and storage, the SecureEnvelope can be serialized to a binary format:
```
NPE_ENVELOPE_V1 ::= SEQUENCE {
version UINT8 (0x01),
enc BYTES (SIZE 32),
ciphertext_len UINT32_LE,
ciphertext BYTES (SIZE ciphertext_len),
policy_client BYTES (SIZE 20),
chain_id UINT64_LE,
recipient_pubkey BYTES (SIZE 32)
}
```
**Field-by-field specification:**
| Offset | Field | Size (bytes) | Encoding | Description |
|---------------------|--------------------|--------------|----------------------|--------------------------------------------------------------|
| 0 | `version` | 1 | Unsigned integer | Format version. Must be `0x01`. |
| 1 | `enc` | 32 | Raw bytes | X25519 ephemeral public key (HPKE encapsulated key). |
| 33 | `ciphertext_len` | 4 | Little-endian uint32 | Length of ciphertext field in bytes. |
| 37 | `ciphertext` | Variable | Raw bytes | HPKE ciphertext (plaintext + 16-byte ChaCha20-Poly1305 tag). |
| 37 + ciphertext\_len | `policy_client` | 20 | Raw bytes | Ethereum address (no checksum in binary). |
| 57 + ciphertext\_len | `chain_id` | 8 | Little-endian uint64 | EVM chain ID. |
| 65 + ciphertext\_len | `recipient_pubkey` | 32 | Raw bytes | Ed25519 public key of the intended recipient. |
**Total size:** 97 + ciphertext\_len bytes (minimum 113 bytes for a 16-byte ciphertext with empty plaintext).
### A.3 AAD Computation (Canonical)
The Additional Authenticated Data for HPKE seal/open operations is computed deterministically:
```
function compute_aad(policy_client: bytes20, chain_id: uint64) -> bytes32:
// policy_client: 20 raw bytes (no 0x prefix, no checksum)
// chain_id: 32 bytes, big-endian, zero-padded uint256
packed = policy_client || to_uint256_be(chain_id)
return keccak256(packed)
```
The `chain_id` is encoded as a 32-byte big-endian unsigned integer (Solidity `uint256` encoding) to match `abi.encodePacked` behavior. This ensures that AAD computed off-chain matches AAD computed on-chain via Solidity's `keccak256(abi.encodePacked(address, uint256))`.
### A.4 Versioning
The `version` field enables future format evolution:
| Version | Description | Status |
|---------|------------------------------------------------------------------------------|----------|
| `0x01` | Initial format as specified in this appendix | Active |
| `0x02`+ | Reserved for future extensions (e.g., post-quantum KEM, additional metadata) | Reserved |
Implementations must reject envelopes with unrecognized version numbers.
# Project & Protocol \[Newton Protocol overview — decentralized policy engine for onchain transaction authorization. Enabling compliant DeFi, institutional crypto adoption, and enterprise blockchain applications.]
### Overview
The global financial system is being reshaped by two powerful trends. Public blockchains are now core financial infrastructure, settling trillions through stablecoins, RWAs, and DeFi—but institutions remain cautious due to the lack of trusted compliance frameworks. At the same time, AI agents are emerging as autonomous market participants, yet their rapid growth has outpaced the guardrails needed to prevent misuse, fraud, or regulatory violations.
Newton Protocol addresses these gaps with a decentralized policy engine that embeds compliance, risk, and business rules directly into transaction execution. By enabling programmable, privacy-preserving enforcement, Newton transforms compliance from a costly afterthought into a foundational layer institutions, regulators, and AI systems can rely on.
To learn more, read the Newton Protocol litepaper [here](https://blog.newton.xyz/the-litepaper/).
Newton Protocol is the first policy protocol built to govern the new era of AI and onchain assets such as stablecoins and RWAs—domains where traditional smart contracts lack the flexibility and compliance required. By embedding programmable policies directly into execution, Newton provides the secure foundation needed to bring global assets onchain with trust and composability.
### Mission and Vision
Magic Newton Foundation’s mission is to steward Newton as a neutral, decentralized policy engine for programmable compliance. By combining cryptographic proofs, cross-chain operability, and integration of real-time offchain data, Newton transforms compliance from a fragmented afterthought into a composable layer institutions, issuers, and developers can build on with confidence.
### Ecosystem and Use Cases
Newton establishes a framework to bring the $250T global investable asset market—and hundreds of trillions more in RWAs—onchain by enforcing policy at the point of execution. Core use cases include:
* **Stablecoin and RWA applications** with embedded compliance
* **Issuers and institutions** maintaining compliance and market integrity
* **Autonomous AI agents** that operate safely with policy guardrails
* **Defi vaults** running automated trading and yield strategies securely
### How Newton Works
* **Cross-Chain Expressive Policies:** Developer-friendly, user-friendly guardrails that work seamlessly across chains
* **Dynamic Offchain Data Integration**: Real-time market signals and compliance input directly into policies
* **Trust-Minimized with Guarantees**: Verifies every onchain action taken by any entity is within predefined policy
# Security (Bug Bounty) \[Report security vulnerabilities in Newton Protocol through the bug bounty program]
We take the security of our systems and users seriously. If you believe you’ve discovered a security vulnerability in our applications, services, or smart contracts, we encourage you to report it to us responsibly. We operate a bug bounty program and welcome submissions that follow our guidelines.
Please avoid any activity that could harm our users or data, including denial of service, accessing unauthorized data, or degrading our services. If you follow responsible disclosure principles, we will not pursue legal action and will work with you to validate and remediate the issue promptly.
To report a vulnerability, please submit your finding through our bug bounty platform at [https://hackerone.com/magic-bbp/](https://hackerone.com/magic-bbp/). We can also be reached by email at [security@magic.link](mailto\:security@magic.link).
# Structure & Key Contributors \[Magic Newton Foundation organizational structure and key contributors]
### Foundation Legal Structure
The Magic Newton Foundation, a Cayman Islands foundation company, was formed in October 2024 to drive adoption, enable open development, and support decentralization of Newton Protocol. It wholly owns two key operating subsidiaries: Magic Newton Foundry Ltd., responsible for token issuance, and Magic Newton Operations Ltd., which manages developmental and operational needs for the Protocol. Both subsidiaries are incorporated in the British Virgin Islands. \
\
The NEWT multifunctional utility token is issued by Magic Newton Foundry Ltd. Newton Protocol (including use of NEWT) is made available globally, excluding jurisdictions that are subject to international sanctions or other applicable legal restrictions as stated in the Magic Newton Operations Ltd.’s Terms of Use, accessible at [newt.foundation](http://newt.foundation).
### Foundation Management
Governance of the Magic Newton Foundation is overseen by a Board of Directors. \
\
Mohammad Akhavannik serves as the Foundation’s Managing Director. He brings over a decade of experience across law, technology, and policy, with prior roles at Magic Labs, Meta, and top law firms including Latham & Watkins and Kirkland & Ellis.
Jacobus Pietersen serves as Director of the Magic Newton Foundation and is a highly experienced independent director with over 14 years in the offshore financial services industry. He specializes in DAOs and foundation companies, and has served on the boards of several major Web3 organisations including Arbitrum Gaming, Kava, and Lido. Recognised as an early mover in the digital assets space, he has been instrumental in the development and governance of Cayman Islands structures for blockchain-based ventures. His prior experience includes launching and heading up the corporate Web3 practice at global offshore law firm Walkers, and senior roles at leading global firms such as KPMG, UBS, and MUFG where he developed deep expertise across traditional finance and digital assets.
David Jeong serves as Director of the Magic Newton Foundation. He is the CEO of [Tread.fi](https://tread.fi), a company building distributed trading and lending infrastructure for digital assets. Prior to founding [Tread.fi](https://tread.fi), David spent more than seven years at Morgan Stanley as a Vice President in Quantitative Research within the Institutional Equity Division. There, he specialized in algorithmic execution, transaction cost analysis, and execution consulting. David brings deep technical and ecosystem expertise at the intersection of traditional finance and decentralized trading systems.
### Role of Key Participants and Third Parties
Magic Labs is a key contributor to the development of Newton Protocol. As an independent company contracted by the Magic Newton Foundation, Magic Labs assists with building and maintaining core protocol infrastructure and tooling, supporting the technical evolution of the ecosystem. The engagement between the Foundation and Magic Labs is structured as an arm’s-length agreement at market rates, designed to ensure alignment with the Protocol’s long-term development goals.
Founded in 2018, Magic Labs is a leading infrastructure company that invented embedded wallet technology and defined the modern Web3 onboarding experiences. The company has created over 50 million non-custodial wallets for a variety of clients and supports a global community of more than 200,000 developers, with 2M+ average monthly active wallets. Its tools are widely used to enable seamless and secure wallet integration across widely recognizable consumer-facing applications today. As a contributor to Newton Protocol, Magic Labs brings deep technical expertise and a proven track record in blockchain infrastructure at scale.
The Foundation also receives strategic and operational support from Cayman based advisory firms and vendors that support crypto foundations on governance, operations, and ecosystem development in order to help design scalable structures and implement best practices. Their support helps the Foundation navigate key areas such as governance implementation, budgeting, and contributor coordination.
In addition, the Magic Newton Foundation works with a number of leading third-party service providers for institutional-grade custody for granted tokens and enforcement of contractual lockup schedules, ensuring the safekeeping and integrity of token allocations.
# Conflict of Interest & Code of Conduct \[Foundation conflict of interest policy and code of conduct]
### Trading and Token Policies
Team members, advisors, and core contributors to Magic Newton Foundation and Magic Labs are subject to an internal policy that prohibits the use of material nonpublic information for personal gain, including trading or making recommendations involving the NEWT token. Short-term speculative trading by insiders is strictly prohibited. See Exhibit D: Financial Policies for a copy of the Foundation’s Trading Policy.
NEWT token allocations for Core Contributors, Early Backers, Magic Labs, as well as Foundation employees are subject to a 36-month vesting schedule with a 12-month cliff. These lock-ups are designed to ensure long-term alignment with the Protocol’s success and prevent potential conflicts of interest related to token liquidity.
To promote transparency, fairness, and market integrity, all leadership of Magic Newton Foundation and executive members of the Core Contributors are required to participate in a third-party-managed structured selling program for any sales of their NEWT token allocations. This program is modeled on industry best practices and is designed to prevent opportunistic trading or misuse of material non-public information. Key features of the program include:
* **Pre-Adoption Certification**: Plans must be adopted only when the individual possesses no material non-public information (MNPI).
* **Cooling-Off Period**: To prevent the use of insider information, sales may not begin immediately upon plan adoption and must be delayed for a pre-determined period to limit access to material non-public information.
* **Sale Frequency Limits:** Each structured plan allows for only periodic, pre-scheduled sales to maintain consistency and avoid discretionary timing.
* **Sale Caps:** Structured plan sales follow volume-aligned limits to support transparency, predictability, and long-term alignment.
* **Eligibility Restrictions**: Only fully vested, unlocked, and transferable tokens are eligible for sale—locked or unvested tokens are excluded.
* **Execution Requirements**: Sales are managed by an independent third-party and must occur through approved exchanges or over-the-counter (OTC) desks.
* **Suspension Clause**: Active plans may be paused by the plan administrator during major protocol events (e.g., governance votes, protocol upgrades, or security incidents) to prevent misalignment with broader ecosystem interests.
* **Restricted Intervention**: Once a plan is active, strict limitations exist on modifying the timing, price, or size of any scheduled sale.
### Board and Investor Conflicts
As of the date of this disclosure, the Foundation is not aware of any material conflicts of interest among its board members, advisors, or major stakeholders.
David Jeong, a member of the Foundation’s Board, is also the Chief Executive Officer of [Tread.fi](http://Tread.fi), a platform that provides advanced trading infrastructure and execution tools. Mr. Jeong has fully disclosed this affiliation and will recuse himself from all Foundation discussions and decisions related to [Tread.fi](http://Tread.fi).
### Related Party Transactions
There are no related-party transactions to disclose. As of the date of publication, no advisory payments have been made to Foundation-affiliated insiders. If any such payments occur, they will be disclosed in future quarterly transparency reports.
### Transparency Commitments
Magic Newton Foundation is committed to maintaining transparency with the community. The Foundation will publish quarterly transparency reports on its website, which will include material updates on governance, operations, ecosystem funds usage, and other areas discussed herein. Each report will also provide an accounting of the Foundation’s token treasury, outlining how tokens have been allocated and used across distinct treasury categories. Expenditures will be disclosed in aggregate across categories—as an illustrative example only, such as operations, protocol development, community initiatives, and ecosystem growth. The Foundation will disclose any material related party or insider transaction as part of its quarterly transparency reports and include: the nature of the transaction, the related person, the basis on which the person is a related person, and the number of tokens or funds involved in the transaction.
# Characteristics & Utility \[NEWT token characteristics, utility, and role in the Newton Protocol ecosystem]
## **Token Summary**
* Token Name / Ticker: NEWT
* Contract Address: 0xd0ec028a3d21533fdd200838f39c85b03679285d
* Blockchain: Ethereum
* Token Standard: ERC-20
* Total Supply: 1,000,000,000 NEWT
## **Supply and Economic Model**
NEWT has a fixed total supply of 1,000,000,000 tokens. No inflationary minting or deflationary mechanics are intended post-launch.
## **Token Type and Technology**
NEWT is the native coordination token of Newton Protocol, deployed as an ERC-20 token on Ethereum. Newton Protocol is designed to operate as an Actively Validated Service (AVS) built on EigenLayer. Operators stake collateral through EigenLayer's AVS framework and register BLS keys through EigenLayer's registry contracts, with eligible collateral types determined by the Foundation and eventually protocol governance. NEWT serves as the economic coordination and governance asset within this operator network. In no way are NEWT tokens an investment or income-producing asset, either in the United States or elsewhere, and they are not marketed as such.
## **Core Functions**
NEWT is a multifunctional utility token used throughout Newton Protocol for the following purposes.
**1. Operator Staking and Network Security**
Operators participating in policy evaluation stake NEWT to register and maintain active status within the Newton Protocol AVS. Staking aligns economic incentives: operators are eligible to receive fees for accurate and timely policy evaluations and are subject to slashing penalties for provable misbehavior, downtime, or submission of invalid attestations. Token holders who do not wish to operate infrastructure directly may delegate NEWT to operator nodes. Staked NEWT is subject to a 14-day unstaking (cool-down) period, during which tokens remain locked and non-transferable. Partial withdrawal of staked NEWT is not available.
**2. Policy Evaluation Fees**
Applications that submit transaction intents or policy evaluation requests to the network pay service fees for protocol usage. Fees are distributed to active operators in proportion to their contribution to the policy evaluation process, including WASM instruction execution, data provider calls, and bandwidth consumed. This fee structure is intended to incentivize continuous participation and performance among the operator set.
**3. Challenge and Dispute Resolution**
Challengers help maintain accountability by verifying operator responses and submitting fraud proofs during challenge windows. Challengers stake NEWT to raise a dispute and may receive rewards if a challenge is upheld, creating adversarial checks within the system.
**4. Governance**
Once the Protocol achieves sufficient decentralization, NEWT holders who stake their tokens will be able to participate in governance. This includes voting on matters such as treasury disbursements, protocol parameter changes, and ecosystem priorities. Core protocol upgrades require validator coordination and are not upgradeable via governance vote alone.
## **Network Fees and Operator Rewards**
Operators receive fees for securing the network and processing policy evaluation operations. Fee distribution to operators is determined programmatically at the Protocol level. Rewards, neither in form nor in function, serve as any kind of "interest," "dividend," or return on any "investment."
# Distribution & Vesting \[NEWT token distribution allocations and vesting schedules]
The initial allocation and vesting schedule of NEWT tokens is designed to align incentives across contributors, early backers, and the broader Newton Protocol community, while ensuring long-term sustainability of the ecosystem. A full breakdown of the token distribution is provided in the table below.
As of the date of publication, Magic Newton Foundation has not sold any NEWT tokens. The Foundation will provide quarterly transparency reports detailing any NEWT sales or treasury activity, including how funds are used across distinct treasury categories such as operations, development, and community initiatives.
### Genesis Allocation
At the time of network launch, the total supply of 1,000,000,000 NEWT tokens will be allocated across multiple intended use cases and stakeholder groups, as outlined in the distribution table below. The Circulating Supply as of the date of the NEWT token launch will be 21.5%, including allocations for community rewards, validator rewards, and liquidity support. See below for an explanation on Circulating Supply and how it differs from Distributed Supply.
Of the total genesis allocation, 60% has been designated for Community categories, while 40% has been allocated to Internal categories.
* “Community” allocations refer to tokens set aside to directly support the growth, technical development, and decentralization of Newton Protocol through initiatives such as ecosystem grants, network rewards, community rewards, and operational support via the Onchain Foundation Treasury. The Treasury supports the operational capacity of the Foundation to serve the Protocol and its community—covering essential functions like contributor coordination, governance support, and public infrastructure that enable the ecosystem to grow.
* “Internal” allocations refer to tokens allocated to Core Contributors, Early Backers, and Magic Labs in recognition of their foundational work in building and supporting the launch of the Protocol. These allocations represent essential resources dedicated to the continued development of the ecosystem.
Token allocations to the Onchain Ecosystem Development Fund, Onchain Ecosystem Growth Fund, and Onchain Foundation Treasury are subject to a 48-month linear unlocking schedule, with 20% of the respective allocations unlocking at launch allowing immediate support of critical early developmental, growth, and operational needs for the Protocol. Tokens allocated to Core Contributors, Early Backers, and Magic Labs are subject to a 36-month vesting schedule, including a 12-month initial lock-up period. All allocations of locked or unvested tokens—whether from Internal or Community allocations—are prohibited from selling or transferring those tokens through secondary OTC transactions until they are fully vested and unlocked.
#### Community Rewards (Community Allocation)
10% of NEWT token supply has been allocated to early Protocol adopters and participants in Newton Protocol ecosystem growth programs, including Portal, agent usage on [newton.xyz](http://newton.xyz), and ecosystem initiatives. A portion of this allocation—0.90% of the total NEWT token supply—has been designated for Kaito to support a Newton Protocol rewards campaign. These tokens are fully unlocked at launch. Any unclaimed or unused NEWT from this allocation will remain unlocked and be transferred to the Onchain Ecosystem Growth Fund.
#### Network Rewards (Community Allocation)
8.5% of NEWT token supply has been reserved for validator incentives. The rewards will be deposited into the Newton Protocol staking contract and Newton Protocol will distribute these rewards programatically to encourage network security and broader participation in staking. The final size and timing of distributions will depend in part on ongoing staking engagement.
#### Liquidity Support (Community Allocation)
4% of NEWT supply has been allocated to support liquidity on centralized exchanges and/or decentralized onchain liquidity pools, available concurrently with or after launch. These tokens are fully unlocked and held in a Foundation-controlled multisig. This allocation is intended to improve early access to the community across supported centralized and/or decentralized exchanges.
A portion of this allocation will be used with providers via structured call-option loan agreements. These arrangements are designed to support community token access. Tokens remain subject to contractual restrictions until options are exercised. See Section 10: Financial Overview and Transparency for more information on token loan arrangements.
#### Onchain Ecosystem Growth Fund (Community Allocation)
15.5% of NEWT token supply has been allocated for any campaigns, partners, or programs that support the growth of the Newton Protocol ecosystem. 20% of this allocation is unlocked upfront (3.1% of the total NEWT supply), with the remainder unlocking over 48 months. Tokens are held in a multisig wallet controlled by the Foundation subject to Newton Protocol governance, and disbursements will be disclosed through quarterly transparency reports.
#### Onchain Ecosystem Development Fund (Community Allocation)
12.5% of NEWT token supply has been allocated to support technical and infrastructure development of Newton Protocol, including developer rewards, hackathons, and core protocol enhancements. Like the growth fund, 20% is unlocked upfront (2.5% of the total NEWT supply), with the remaining tokens unlocking over 48 months and held in a multisig wallet controlled by the Foundation subject to Newton Protocol governance.
#### Onchain Foundation Treasury (Community Allocation)
9.5% of NEWT token supply has been allocated to the Foundation’s onchain treasury for its operational purposes and initiatives in support of Newton Protocol, including hiring employees, unaffiliated vendors, or paying for services. Like the development and growth funds, 20% is unlocked upfront (1.9% of the total NEWT supply), with the remaining tokens unlocking over 48 months. These tokens are held in a Foundation-controlled multisig wallet.
#### Core Contributors (Internal Allocation)
18.5% of NEWT token supply has been allocated to core contributors at Magic Labs who have played a foundational role in contributing to the development of Newton Protocol. These tokens are subject to a 12-month cliff, with 33.3% unlocked at the cliff, and a 36-month linear vesting schedule.
#### Early Backers (Internal Allocation)
16.5% of NEWT token supply has been allocated to early backers of Magic Labs. These tokens follow the same 12-month cliff and 36-month vesting schedule as Core Contributors.
#### Magic Labs (Internal Allocation)
5% of the NEWT token supply has been allocated to support Magic Labs’ ongoing operational needs. This includes funding for the continued development, maintenance, and expansion of products within the Newton Protocol ecosystem. These tokens follow the same 12-month cliff and 36-month vesting schedule as Core Contributors and Early Backers.
Allocation Chart

Allocation Table

Emissions Schedule

### Token Circulation
To provide greater clarity around token availability at launch and over time, the Foundation distinguishes between: (i) Circulating Supply and (ii) Distributed Supply.
Circulating Supply refers to the total amount of NEWT that is unlocked (i.e., tradeable, not subject to transfer restrictions) and designated for release into the market at or following launch. This includes allocations for community rewards (e.g., airdrops, Kaito), liquidity support, and the unlocked portions of the Onchain Ecosystem Development and Growth Funds and the Onchain Foundation Treasury. However, not all tokens within the Circulating Supply are expected to be actively used at or immediately following launch. Some NEWT tokens may remain inactive for various reasons—for example, tokens available for airdrop may go unclaimed or ecosystem token grants may not be issued.
To distinguish between tokens that are unlocked for circulation and those that are actively accessible and transferable, the Foundation also reports a second metric: Distributed Supply. This includes all NEWT tokens that have been claimed through community programs (e.g., airdrops, Kaito), distributed as staking rewards, provided for liquidity support, or deployed from Foundation-controlled wallets—and that are no longer subject to material restrictions on transfer or use.
The sections below provide a detailed breakdown of each category and the specific token allocations they include.
#### Circulating Supply
The Circulating Supply as of the date of the NEWT token launch is 21.5% and is allocated as follows:
* 10% - Community Rewards (e.g., airdrop)
* This allocation of NEWT is unlocked at launch and available to be distributed to early users and campaign participants to incentivize awareness, adoption, and engagement with Newton Protocol. Following completion of the initial NEWT token airdrop, the Foundation will publish a clear, auditable CSV list of recipients, including addresses and amount claimed.
* 4% - Liquidity Support
* This allocation of NEWT tokens is unklocked at launch and intended to increase community access to NEWT by supporting liquidity on centralized exchanges and/or decentralized onchain liquidity pools, including the payment of related expenses. These tokens will be deployed concurrently with, or following, the NEWT token launch, depending on ecosystem development needs.
* To promote transparency and mitigate concerns around liquidity withdrawal, the Foundation will place liquidity pool (LP) tokens it receives under time-based or governance-controlled lockup mechanisms. These safeguards are designed to prevent abrupt removal of liquidity while preserving the ability to rebalance pools if required. Details regarding liquidity deployments and LP token status will be disclosed in the Foundation’s quarterly transparency reports.
* A portion of this allocation will be used for structured loan agreements that include call options, allowing counterparties to purchase NEWT at predetermined terms. The underlying NEWT tokens remain subject to contractual restrictions until the option is exercised. See Section 10: Financial Overview and Transparency for more information about these arrangements.
* 3.1% - Onchain Ecosystem Growth Fund
* At launch, this represents the unlocked portion of the Foundation’s Onchain Ecosystem Growth Fund that is available immediately for deployment.
* 2.5% - Onchain Ecosystem Development Fund
* At launch, this represents the unlocked portion of the Foundation’s Onchain Ecosystem Development Fund that is available immediately for deployment.
* 1.9% - Onchain FoundationTreasury
* At launch, this represents the unlocked portion of the Foundation’s Onchain Treasury that is available immediately for deployment.
#### Distributed Supply
* The Distributed Supply refers to the portion of the Circulating Supply that has been claimed, distributed, or deployed and is no longer subject to material restrictions on transfer or use. Following token launch, the Foundation will provide regular updates on the Distributed Supply as part of its quarterly transparency reports. It is composed of the following:
* Claimed Community Rewards
* Tokens from airdrops or campaigns that have been successfully claimed by recipients. Any unclaimed or unused NEWT from this allocation will remain unlocked and transferred to the Onchain Ecosystem Growth Fund for future use (e.g., future rewards, growth campaigns).
* Distributed Validator Rewards
* NEWT tokens that have been deployed into the Newton Protocol validator rewards pool smart contracts to be made programatically available by Newton Protocol for claiming by stakers each rewards period.
* Initially, approximately every seven days additional tokens will enter the Distributed Supply. Only NEWT tokens that have been made available for stakers to claim are included in this calculation.
* These additional NEWT staking rewards will continue for approximately four years following launch, subject to Protocol governance.
* Liquidity Support
* NEWT tokens that are actively deployed into centralized exchanges or decentralized liquidity pools or other DeFi protocols by the Foundation, or otherwise used to support such liquidity initiatives, including paying related expenses.
* Any NEWT tokens that have been purchased and exercised under structured call-option agreements.
* Fund and Treasury Token Deployments
* Any NEWT tokens deployed from the Onchain Ecosystem Development or Growth Funds or the Onchain Foundation Treasury that are no longer subject to lockups or transfer limitations.
# Newton Protocol & NEWT Transparency Reports \[Newton Foundation quarterly transparency reports]
[Q2 2025: Newton Protocol & NEWT Transparency Report](https://assets.newt.foundation/NewtonProtocolandNEWTTransparencyReportFinal.pdf)
[Q3 2025: Newton Protocol & NEWT Transparency Report](https://assets.newt.foundation/October-2025+Update-Newton+Protocol-NEWT-Disclosure-Transparency-Report.pdf)
[Q4 2025: Newton Protocol & NEWT Transparency Report](https://assets.newt.foundation/December2025UpdatetotheNewtonProtocolNEWTDisclosureTransparencyReport.pdf)
[Q1 2026: Newton Protocol & NEWT Transparency Report](https://assets.newt.foundation/Q12026UpdateNewtonProtocolNEWTDisclosureTransparencyReport.pdf)
[Q2 2026: Newton Protocol & NEWT Transparency Report](https://assets.newt.foundation/newton_q2_2026_transparency_report.docx.pdf)
# Newton Protocol Governance Model \[Newton Protocol governance model and decision-making framework]
[September 2025: Newton Protocol Governance Model, Phase 0](https://assets.newt.foundation/NewtonProtocol_GovernanceModel_Final.pdf)
# Balancer Data Checks \[Use keyless Balancer data in Newton policies for vault manager-action gates.]
The Balancer policy pack uses keyless Balancer data in composite policies. It is useful when a manager action depends on Balancer pool or market state.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `balancer` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| None | Keyless | [balancer.fi](https://balancer.fi) |
## Usage
Install the pack package and add it with `definePolicy(...).with(balancer)`.
This pack ships **no `prepareQuery`** — `poolId` and `chain` name a pool the SDK cannot read off the vault — so its `wasmArgs` are curator-supplied per call, under `wasmArgs.balancer` rather than `prepareQueryOptions.balancer`:
```typescript
await shield.morpho.reallocate(vault, allocations, {
wasmArgs: { balancer: { poolId, chain: 'base' } },
})
```
`wasmArgs` requires `@newton-xyz/vaultkit` 2.2.0 or later. See [Policies](/developers/vaults/sdk/policies#per-call-inputs).
# Blockaid Transaction Screening \[Use Blockaid data in Newton policies to screen transaction and address risk before vault manager actions execute.]
The Blockaid policy pack screens transaction or address risk before a manager action executes. Use it when a vault wants an external security signal in the same composite policy as market, compliance, or oracle checks.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `blockaid` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `BLOCKAID_API_KEY` | Required | [blockaid.io](https://www.blockaid.io) |
## Usage
Install the pack package, add it with `definePolicy(...).with(blockaid)`, upload the API key with `client.uploadSecrets(...)`, and pass any per-call inputs its `prepareQuery` requires under `prepareQueryOptions.blockaid`.
# Guardrail Risk Checks \[Use Guardrail data in Newton policies as an optional risk signal for vault manager actions.]
The Guardrail policy pack adds a Guardrail risk signal to composite policies for manager-action gates.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `guardrail` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `GUARDRAIL_API_KEY` | Optional | [guardrail.so](https://guardrail.so) |
## Usage
Install the pack package, add it with `definePolicy(...).with(guardrail)`, and upload `GUARDRAIL_API_KEY` only when your deployment requires it. Per-call inputs go under `prepareQueryOptions.guardrail`.
# Persona Identity Checks \[Use Persona data in Newton policies for identity or verification-based vault gates.]
The Persona policy pack lets a composite policy include identity or verification signals from Persona before a manager action executes.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `persona` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secret
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `PERSONA_API_KEY` | Required | [withpersona.com](https://withpersona.com) |
## Usage
Install the pack package, add it with `definePolicy(...).with(persona)`, upload the API key with `client.uploadSecrets(...)`, and pass any per-call identity input its `prepareQuery` requires under `prepareQueryOptions.persona`.
# Sumsub Verification Checks \[Use Sumsub data in Newton policies for KYC or verification-aware vault gates.]
The Sumsub policy pack lets a composite policy include Sumsub verification data before a manager action executes.
## Deployment
| Field | Value |
| --- | --- |
| Pack id | `sumsub` |
| PolicyData addresses | [VaultKit address table](/developers/vaults/policy-packs#deployed-policydata-addresses) |
| Canonical deployments | [`deployments.json`](https://github.com/newt-foundation/newton-policy-packs/blob/main/deployments.json) |
### Secrets
| Secret | Required? | Where to get it |
| --- | --- | --- |
| `SUMSUB_APP_TOKEN` | Required | [sumsub.com](https://sumsub.com) |
| `SUMSUB_SECRET_KEY` | Required | [sumsub.com](https://sumsub.com) |
Both values are required because Sumsub requests are HMAC-signed.
## Usage
Install the pack package, add it with `definePolicy(...).with(sumsub)`, upload both secrets with `client.uploadSecrets(...)`, and pass any per-call verification input its `prepareQuery` requires under `prepareQueryOptions.sumsub`.
# SDK Concepts \[How VaultKit 2.1 models policies, resolved modules, vendor extensions, intents, and verification.]
## Policy Inputs and Resolved Policies
`createShield(...)` accepts either a `PolicyDraft` created with `definePolicy({ chainId, env }).with(...)` or a `BarePolicy` created with `policyFromAddress(...)`.
After onchain resolution, both become a `ResolvedPolicy` exposed as `client.policy`. It contains the deployed address, chain, environment, optional params codec, and `modules`.
## Policy Packs and Oracles
A policy pack is a typed TypeScript wrapper for one policy-data oracle. It provides schemas, deployment records, `prepareQuery(...)`, and metadata. Published `@newton-xyz/policy-pack-*` packages and locally authored `defineOracle(...)` values share this interface.
The SDK packages do not contain the oracle's Rego or WASM. Those artifacts are built and deployed separately.
## Params, Secrets, and `wasmArgs`
* Params are curator-controlled configuration encoded into the onchain policy manifest.
* Secrets are encrypted locally and uploaded per oracle.
* `wasmArgs` are per-call evaluation inputs produced by each oracle's `prepareQuery(...)`.
Callers pass typed overrides under `prepareQueryOptions.`. The policy pack, not the vendor module, owns the resulting `wasmArgs`.
## Vendor Modules and `.extend(...)`
```typescript
import { morphoActions } from '@newton-xyz/vaultkit/vendors/morpho'
const client = (await createShield(config)).extend(morphoActions)
await client.morpho.reallocate(vault, allocations)
```
| Import | Namespace | Scope |
| --- | --- | --- |
| `vendors/morpho` | `client.morpho.*` | MetaMorpho curator and allocator actions |
| `vendors/morpho-blue` | `client.morphoBlue.*` | Morpho Blue position operations for managed sleeves |
| `vendors/euler` | `client.euler.*` | Euler Earn manager and allocator actions |
| `vendors/euler-vault` | `client.eulerVault.*` | EVault governor actions |
| `vendors/superform` | `client.superform.*` | SuperVault strategy-manager actions and typed hooks |
`euler` and `eulerVault` are distinct APIs. Likewise, `morpho` targets MetaMorpho aggregation vaults while `morphoBlue` targets the Morpho Blue singleton.
## Generic Calls
`sendCall(...)` is the escape hatch for an unwrapped action:
```typescript
await client.sendCall({
to,
data,
value,
functionSignature: 'someManagerAction(uint256)',
prepareQueryOptions,
})
```
The integration owns calldata integrity when using this method.
## Intent
An `Intent` binds the caller, target, value, calldata, chain, and human-readable function signature. Operators evaluate that exact action; the SDK rejects task and response data that do not match.
## Verification Ladder
Attachment verification is monotonic:
1. Policy address
2. Oracle set and WASM identities
3. Parameter decoding
4. Canonical byte equality against `expectedParams`
`client.verification` is a snapshot. Call `client.reverify()` after configuration changes. A bare policy with no modules can satisfy address and codec checks but does not prove that an oracle set exists.
## Blocked-Intent Assertions
`assertIntentBlocked(...)` expects a policy denial and confirms through `eth_call` that the denied attestation cannot execute. It is a negative assertion for integration tests and monitoring, not a transaction.
## Browser Safety
VaultKit core avoids `node:*` imports. A vendor SDK or custom oracle helper may introduce its own runtime requirements; check those dependencies before bundling for the browser.
# Shield Architecture \[How Shield contracts are deployed, addressed, initialized, and bounded onchain.]
A Shield is the onchain policy client that stands between a curator and a vault manager action. It validates Newton attestations, then forwards approved calls to the underlying vault.
## One Implementation per Chain
Each supported chain has a shared `Shield` implementation. Per-curator Shields are EIP-1167 minimal-proxy clones deployed from that implementation.
This keeps the surface small:
* One audited implementation per chain.
* Every clone shares the same bytecode and storage layout.
* Curators opt into new implementations by deploying fresh clones rather than accepting proxy-admin upgrades.
## Deterministic Clones
`ShieldFactory.createShield(curator, vault, version, policy, bypassDelaySeconds)` deploys a clone with CREATE2. The salt is derived from:
* `curator`
* `vault`
* `version`
The `version` value lets the same curator and vault deploy multiple Shields when they need a new policy, a new bypass delay, or a clean recovery slot.
Anyone can predict the address before deployment:
```solidity
function predictShieldAddress(
address curator,
address vault,
uint64 version
) external view returns (address);
```
The SDK uses this to make `createShield(...)` idempotent.
## Permissionless Factory
`ShieldFactory` is intentionally not owned. It has no pause function, admin setters, or ownership transfer. Anyone can call `createShield`, but the clone owner is the `curator` address passed at deployment, not the caller.
The factory immutably pins:
* The Shield implementation.
* The Newton task manager used for attestation validation.
## Initialization
Each clone runs `initialize(...)` once. Initialization:
1. Wires the Newton policy-client mixin.
2. Sets the curator owner.
3. Binds the initial policy address when provided.
4. Stores `bypassDelaySeconds`.
5. Enforces a minimum bypass delay of one day.
`bypassDelaySeconds` does not change after initialization. To use a different delay, deploy a new clone.
## Forwarding Behavior
The Shield forwards approved calls with low-level `call`:
```solidity
(bool success, bytes memory returnData) = to.call{value: value}(data);
```
If the target vault reverts with a reason, Shield bubbles that revert data. If the target reverts without a reason, Shield reverts with `IntentExecutionFailed()`.
## Reentrancy
`execute`, `executeDirect`, and `executeBypass` are protected by `nonReentrant`. Attestation replay is blocked by the AVS task manager, and the local guard hardens the bypass and forwarding paths against reentrancy through the target vault.
## Upgrade Posture
Shield clones are not upgradeable. There is no UUPS hook, transparent-proxy admin, or implementation setter. An implementation change means:
1. Deploy a new implementation.
2. Deploy a new factory pointing at it.
3. Let curators opt in by creating fresh clones.
This makes the active code path inspectable for curators and depositors.
# Deployment \[Deployment and ownership notes for Shield implementations, factories, clones, and upstream Newton addresses.]
This page is for teams operating Shield deployments or reviewing how a VaultKit deployment is wired.
## Per-Chain Artifacts
Each supported chain and environment has a Shield implementation and ShieldFactory deployment. The SDK registry maps `(chainId, env)` to those addresses so `createShield(...)` can predict, attach, or deploy curator clones.
Runtime registry entries and deployment artifact JSON should be updated together when a new deployment lands.
## Upstream Newton Addresses
Shield contracts depend on Newton Policy Protocol addresses, especially the task manager used for attestation validation. Those upstream addresses should come from the pinned Newton contracts deployment artifacts, not from stale hand-maintained tables.
When Newton upstream contracts migrate:
1. Refresh the upstream deployment artifacts.
2. Re-resolve task manager and related policy-client addresses.
3. Update Shield deployment artifacts.
4. Update the SDK deployment registry.
5. Release an SDK version that points at the new registry values.
## Supported Chains
VaultKit currently documents support for:
| Chain | Chain ID |
| --- | --- |
| Ethereum | `1` |
| Base | `8453` |
| Sepolia | `11155111` |
| Base Sepolia | `84532` |
If a chain is unknown to the SDK registry, `createShield(...)` throws `UnsupportedChainError` before sending transactions.
## Ownership Handoff
The factory is not owned and has no admin handoff. Ownership handoff applies to per-curator Shield clones.
The Shield owner can:
* Queue, execute, and cancel bypasses.
* Update policy params.
* Change the bound policy address.
* Transfer Shield ownership.
After deploying a clone with an EOA, curators commonly transfer ownership to a Safe by calling `setPolicyClientOwner(safeAddress)`.
## Post-Handoff Checks
After transferring ownership, verify:
* `getPolicyClientOwner()` returns the Safe address.
* Owner-gated calls can only be made by the Safe.
* Monitoring watches `BypassQueued`, `BypassExecuted`, and `BypassCancelled`.
* The Shield policy address and policy id match the expected pack and params.
## Local Testing
For local testing, use a local chain and local-only deployment artifacts. Local artifacts should not be treated as audit records for production deployment.
## Operational Rule
Do not hardcode upstream Newton addresses in app docs or integration code. Use the SDK registry and deployment artifacts so changes are auditable and versioned.
# Shield Flows \[Execution modes and bypass-timelock flows for Newton Shield.]
Shield supports three execution paths: standard attested execution, direct attested execution, and owner bypass.
## Standard Execution
Standard mode uses `Shield.execute(attestation)`.
1. The SDK submits a task to Newton Gateway.
2. Operators evaluate the policy.
3. The aggregator commits the approved task response through the AVS task manager.
4. The SDK calls `Shield.execute(attestation)`.
5. Shield validates the attestation through the task manager.
6. Shield forwards the approved calldata to the vault.
Standard mode is lower gas because BLS verification happens through the AVS task manager, but it waits for the aggregator commit.
## Direct Execution
Direct mode uses `Shield.executeDirect(task, taskResponse, signatureData)`.
1. The SDK submits a task to Newton Gateway.
2. Operators evaluate the policy.
3. The SDK receives the raw task response and BLS signature data.
4. The SDK calls `Shield.executeDirect(...)`.
5. Shield verifies the signatures inline.
6. Shield forwards the approved calldata to the vault.
Direct mode can reduce end-to-end wait time, but costs more gas.
## Execution Comparison
| Trait | Standard | Direct |
| --- | --- | --- |
| Method | `execute(attestation)` | `executeDirect(task, taskResponse, signatureData)` |
| Aggregator commit | Required | Not required |
| BLS verification | AVS task manager | Shield contract |
| Gas | Lower | Higher |
| Latency | Higher | Lower |
| Best for | Routine curator actions | Latency-sensitive actions |
## Bypass Timelock
Bypass is an owner-only recovery path for protocol outages or emergency operations. It does not require a Newton attestation, so it is deliberately delayed and observable.
```mermaid
stateDiagram-v2
[*] --> Queued: queueBypass(to, value, data)
Queued --> Executed: executeBypass(id) after eta
Queued --> Cancelled: cancelBypass(id)
Executed --> [*]
Cancelled --> [*]
```
The bypass id is derived from the target, value, data, nonce, and Shield address. Identical calls queued twice receive different ids because the nonce advances.
## Bypass Rules
* Only the Shield owner can queue, execute, or cancel bypasses.
* The minimum bypass delay is one day.
* `executeBypass` deletes the entry before making the outbound call.
* Cancelled and executed ids cannot be reused.
* All bypass state changes emit events for monitoring.
Bypass is not a normal operating path. It exists so a curator can recover from policy infrastructure outages without hiding the action from depositors.
# Shield Reference \[Public contract surface for Shield and ShieldFactory.]
This page summarizes the onchain surface VaultKit uses. Solidity names are shown for integrators, indexers, and auditors.
## `Shield`
### Constants
```solidity
uint256 public constant MIN_BYPASS_DELAY = 1 days;
```
### Public Reads
```solidity
uint256 public bypassDelaySeconds;
mapping(bytes32 id => BypassEntry) public bypasses;
```
`bypasses(id)` returns the queued bypass target, value, calldata, eta, and existence flag.
### `initialize`
```solidity
function initialize(
address policyTaskManager,
address policy,
address policyClientOwner,
uint256 bypassDelaySeconds
) external initializer;
```
Called once by `ShieldFactory.createShield`. Reverts if the bypass delay is below `MIN_BYPASS_DELAY`.
### `execute`
```solidity
function execute(
NewtonMessage.Attestation calldata attestation
) external nonReentrant onlyApprovedDelegate returns (bytes memory);
```
Validates an AVS-issued attestation, forwards the approved intent, emits `ShieldExecuted`, and returns target `returnData`. Reverts with `NotApprovedDelegate(caller)` when `msg.sender` is not approved.
### `executeDirect`
```solidity
function executeDirect(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
bytes calldata signatureData
) external nonReentrant onlyApprovedDelegate returns (bytes memory);
```
Validates the task response and signature data inline, forwards the approved intent, emits `ShieldExecutedDirect`, and returns target `returnData`. The task intent and task-response intent must match byte-for-byte or the call reverts with `IntentMismatch()`.
### Delegates
`execute` and `executeDirect` are gated by the `approvedDelegates` map. The policy-client owner controls that map separately from bypass and policy ownership.
```solidity
mapping(address delegate => bool approved) public approvedDelegates;
function setApprovedDelegate(
address delegate,
bool approved
) external onlyPolicyClientOwner;
function isApprovedDelegate(address delegate) external view returns (bool);
```
The initial `policyClientOwner` is approved during `initialize`, so the owner can execute immediately after deployment. There is no implicit live-owner overlay after ownership changes: if ownership is handed to a Safe or another EOA, the new executor must be approved with `setApprovedDelegate(newExecutor, true)`, and old delegates should be revoked with `setApprovedDelegate(oldDelegate, false)`.
### Bypass Methods
```solidity
function queueBypass(
address to,
uint256 value,
bytes calldata data
) external onlyPolicyClientOwner returns (bytes32 id);
function executeBypass(
bytes32 id
) external nonReentrant onlyPolicyClientOwner returns (bytes memory);
function cancelBypass(bytes32 id) external onlyPolicyClientOwner;
```
Bypass entries are one-shot. `executeBypass` is only valid after the queued eta, and `cancelBypass` permanently removes the entry.
### Events
```solidity
event ShieldExecuted(bytes32 indexed taskId, NewtonMessage.Intent intent, bytes returnData);
event ShieldExecutedDirect(bytes32 indexed taskId, NewtonMessage.Intent intent, bytes returnData);
event BypassQueued(bytes32 indexed id, address indexed to, uint256 value, bytes data, uint256 eta);
event BypassExecuted(bytes32 indexed id, address indexed to, uint256 value, bytes data, bytes returnData);
event BypassCancelled(bytes32 indexed id);
event DelegateUpdated(address indexed delegate, bool indexed approved);
```
### Common Errors
```solidity
error InvalidAttestation();
error IntentExecutionFailed();
error IntentMismatch();
error NotApprovedDelegate(address caller);
error BypassDelayBelowMinimum(uint256 provided, uint256 minimum);
error BypassAlreadyQueued(bytes32 id);
error BypassUnknown(bytes32 id);
error BypassNotReady(bytes32 id, uint256 eta);
```
The SDK surfaces many of these through `ShieldExecutionError.cause`.
## Inherited Policy Client Methods
The following controls describe the legacy Shield interface. Verify the deployed Shield ABI before using them. A policy-set-capable Shield instead inherits `setPolicies(PolicySpec[])`, `getPolicies()`, `policyRevision()`, `getPolicyId()`, and `getPolicySetSnapshot()`; see [Smart Contract Integration](/developers/guides/smart-contract-integration#api-reference). Upgrading the protocol does not automatically upgrade an existing Shield.
Legacy owner controls:
```solidity
function setPolicy(INewtonPolicy.PolicyConfig memory policyConfig)
external onlyPolicyClientOwner returns (bytes32 policyId);
function setPolicyAddress(address policy) public onlyPolicyClientOwner;
function setPolicyClientOwner(address policyClientOwner) public onlyPolicyClientOwner;
function getPolicyAddress() external view returns (address);
function getPolicyConfig() external view returns (INewtonPolicy.PolicyConfig memory);
function getPolicyId() external view returns (bytes32);
```
`setPolicy(...)` updates policy params and returns the new policy id. `setPolicyAddress(...)` binds a different policy contract. `setPolicyClientOwner(...)` transfers owner authority, commonly to a Safe.
## `ShieldFactory`
### Immutables
```solidity
address public immutable implementation;
address public immutable taskManager;
```
### `createShield`
```solidity
function createShield(
address curator,
address vault,
uint64 version,
address policy,
uint256 bypassDelaySeconds
) external returns (address shield);
```
Deploys and initializes a deterministic clone. Reverts if curator or vault is zero, if the predicted clone already exists, or if initialization fails.
### View Helpers
```solidity
function predictShieldAddress(
address curator,
address vault,
uint64 version
) external view returns (address);
function isDeployed(
address curator,
address vault,
uint64 version
) external view returns (bool);
```
The SDK uses these helpers to predict addresses and make `createShield(...)` idempotent.
# Storage Layout \[How Shield storage layout is protected for EIP-1167 clones.]
Shield clones are EIP-1167 minimal proxies. Every curator clone shares the implementation's storage layout slot-for-slot, so storage changes are treated as compatibility changes.
## Rule
The layout is append-only:
* Existing slots cannot move.
* Existing slots cannot change type.
* Existing slots cannot be deleted.
* New storage variables go after the existing layout.
If a change needs to reorder, retype, or remove storage, deploy a new implementation and factory. Curators opt in by deploying new clones.
## Snapshot
The canonical snapshot is `contracts/.storage-layouts/Shield.json` in the Shield repo. It is regenerated from compiled artifacts:
```bash
cd contracts
forge inspect Shield storage-layout --json > .storage-layouts/Shield.json
```
The snapshot records each field's contract, label, slot, offset, and type, plus the type map.
## CI Checks
The Shield repository runs two storage-layout checks:
| Check | What it catches |
| --- | --- |
| Append-only validator | Any changed label, slot, offset, or type for existing entries, plus inserted entries below the baseline tail. |
| Freshness check | A stale committed snapshot that no longer matches `forge inspect`. |
Both must pass before a layout-affecting PR can merge.
## Inherited Storage
Shield inherits `ReentrancyGuardUpgradeable` and `NewtonPolicyClient`. Changes in either inherited contract can still move the effective layout, so the snapshot intentionally catches upstream dependency drift as well as edits to `Shield.sol`.