Skip to main content
This guide walks through the end-to-end integration of Newton Verifiable Credentials into your app. Before starting, read the overview to understand how Newton VC works.

Example Repo

Clone the repo and follow the README to run the full VC flow locally

Live Demo

Try the live demo

Prerequisites

RequirementDetails
Newton SDK@magicnewton/newton-protocol-sdk installed in your JavaScript/TypeScript app
Newton API keyCreate one in the dashboard
Policy client contractMust inherit NewtonPolicyClient + EIP712 and be registered with PolicyClientRegistry
Identity domainA fixed bytes32 hex value for your application — the output of keccak256("kyc")
KYC vendorA third-party vendor to collect user identity data. The vendor’s output must map to Newton’s IdentityData 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:
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 for all available built-in functions.

1b. Write your params_schema.json

Define the schema for your policy params, including the identity_domain:
{
  "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

{
  "name": "Newton Verifiable Credential Policy",
  "version": "1.0.0",
  "author": "Newton",
  "link": "https://newton.xyz",
  "description": "This policy enforces KYC compliance"
}
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. Deploy the policy contract Once you have policy_cids.json, run:
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 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
After registering, call the following on your deployed policy client contract:
  1. setPolicyAddress(policyContractAddress) — point it to the policy deployed in Step 1
  2. setPolicy(params) — set the policy params, including your identity domain:
{
  "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 — 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:
const walletClient = createWalletClient({ ... }).extend(
  newtonWalletClientActions({ apiKey: 'YOUR_API_KEY' })
)

await walletClient.registerUserData({
  userData: kycDataFromVendor,
  appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
  policyClient: '0xYourPolicyClientAddress',
})
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. Call linkApp to link the user’s registered identity to your policy client contract:
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. 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.
// 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 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
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
// 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
const result = await newtonWalletClient.evaluateIntentDirect({
  policyClient: yourPolicyClientAddress,
  intent,
  intentSignature,
  timeout: 60,
  identityDomain: IDENTITY_DOMAIN,
})
// result.evaluationResult — 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.
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:
await walletClient.unlinkApp({
  appWalletAddress: userWalletAddress,
  appClientAddress: yourPolicyClientAddress,
  appIdentityDomain: '0xa29cad97d4110e8822dedef32879f84a8e8889bf965ff9c57998689e2fdafbc4',
})

Reference Pages

SDK & Contract Reference

Full SDK method signatures and contract functions

Identity Policy Reference

All Rego built-in functions for identity checks

Have questions?

Get in touch with someone from the Newton team to discuss your integration