# Attestation Source: https://docs.predicate.io/api-reference/v2/endpoint/attestation POST /v2/attestation Evaluate a transaction against configured policy and return an attestation (chain-name based, supports EVM and Solana) Request a compliance attestation for a user. The API evaluates the user against your configured policy and returns a signed attestation. ## Request Parameters | Field | Type | Required | Description | | ------------------- | ------ | ----------- | ------------------------------------------------------------------------------ | | `from` | string | Yes | User's wallet address to evaluate | | `chain` | string | Yes | Chain name (see supported chains below) | | `verification_hash` | string | Conditional | Your project identifier from the dashboard. Required for offchain integration. | | `to` | string | Conditional | Your contract address. Required for onchain integration. | | `data` | string | No | Encoded function call data. Required when using `PredicateClient`. | | `msg_value` | string | No | Transaction value in wei. Required when using `PredicateClient`. | Use `verification_hash` for offchain integration (Phase 1). Use `to` with your contract address for onchain integration (Phase 2). ## Supported Chains | Chain | Value | | --------------- | ----------------- | | Ethereum | `ethereum` | | Base | `base` | | Arbitrum | `arbitrum` | | Hyperliquid | `hyperliquid` | | Plume | `plume` | | BSC | `bsc` | | Plasma | `plasma` | | World | `worldchain` | | Sepolia | `sepolia` | | Solana | `solana` | | MegaETH | `megaeth` | | MegaETH Testnet | `megaeth-testnet` | *** Use `verification_hash` to evaluate compliance without a deployed contract. #### Request ```bash cURL theme={null} curl -X POST "https://api.predicate.io/v2/attestation" \ -H "Content-Type: application/json" \ -H "x-api-key: $PREDICATE_API_KEY" \ -d '{ "verification_hash": "x-abc123def456", "from": "0x8ba1f109551bD432803012645Hac136c", "chain": "ethereum" }' ``` #### Response (Compliant) ```json theme={null} { "policy_id": "policy_abc123def456", "policy_name": "Standard AML Compliance", "verification_hash": "x-abc123def456", "is_compliant": true, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 1696640400, "attester": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "signature": "0x1b2c3d4e..." } } ``` #### Response (Non-Compliant) Some policies return a `reason` object when the user is not compliant, providing a machine-readable code and a human-readable message explaining why the evaluation failed. Both the `code` and `message` values are fully customizable when authoring a policy. ```json theme={null} { "policy_id": "x-dev-trm-risk-abc123", "policy_name": "TRM Risk Score", "verification_hash": "x-abc123def456", "is_compliant": false, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 0, "attester": "", "signature": "" }, "reason": { "code": "trm_high_risk", "message": "TRM risk score is 10 which exceeds the maximum allowed of 7" } } ``` Use `to` with your contract address or program ID for onchain enforcement. #### Request (BasicPredicateClient) ```bash cURL theme={null} curl -X POST "https://api.predicate.io/v2/attestation" \ -H "Content-Type: application/json" \ -H "x-api-key: $PREDICATE_API_KEY" \ -d '{ "to": "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", "from": "0x8ba1f109551bD432803012645Hac136c", "chain": "base" }' ``` #### Request (PredicateClient) Include `data` and `msg_value` for policies that validate function calls. ```bash cURL theme={null} curl -X POST "https://api.predicate.io/v2/attestation" \ -H "Content-Type: application/json" \ -H "x-api-key: $PREDICATE_API_KEY" \ -d '{ "to": "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6", "from": "0x8ba1f109551bD432803012645Hac136c", "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6", "msg_value": "0", "chain": "base" }' ``` #### Response ```json theme={null} { "policy_id": "policy_abc123def456", "policy_name": "Standard AML Compliance", "verification_hash": "x-abc123def456", "is_compliant": true, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 1696640400, "attester": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "signature": "0x1b2c3d4e..." } } ``` #### Request ```bash cURL theme={null} curl -X POST "https://api.predicate.io/v2/attestation" \ -H "Content-Type: application/json" \ -H "x-api-key: $PREDICATE_API_KEY" \ -d '{ "to": "5iejgxCq2vnpiwWpf4qwziVhbX2irmgMEghBrD9tmk5p", "from": "8SfpAAUkA4E1ZTSzXiAR51f1iGuVQU4r7kiNUxh7GpVM", "chain": "solana" }' ``` #### Response ```json theme={null} { "policy_id": "policy_abc123def456", "policy_name": "Standard AML Compliance", "verification_hash": "x-abc123def456", "is_compliant": true, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 1696640400, "attester": "8SfpAAUkA4E1ZTSzXiAR51f1iGuVQU4r7kiNUxh7GpVM", "signature": "0x5e4f..." } } ``` ## Response Fields | Field | Type | Description | | ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------- | | `policy_id` | string | Unique identifier for the policy | | `policy_name` | string | Human-readable policy name | | `verification_hash` | string | Project identifier | | `is_compliant` | boolean | Whether the user passed the policy evaluation | | `attestation` | object | Signed attestation to submit onchain | | `attestation.uuid` | string | Unique identifier for replay protection | | `attestation.expiration` | number | Unix timestamp when the attestation expires | | `attestation.attester` | string | Address of the Predicate attester | | `attestation.signature` | string | Cryptographic signature | | `reason` | object | Present on non-compliant responses when the policy provides a reason. Not included for compliant responses. | | `reason.code` | string | Machine-readable error code defined by the policy | | `reason.message` | string | Human-readable explanation of why the evaluation failed | # Stream Session Status (SSE) Source: https://docs.predicate.io/api-reference/v2/endpoint/identity/realtime Real-time session status updates via Server-Sent Events Stream real-time status updates for a verification session using Server-Sent Events (SSE). The SSE endpoint requires your API key. Since browsers' `EventSource` API doesn't support custom headers, you must create a backend proxy. See the [Integration Guide](/v2/identity/integration) for implementation examples. #### Request ```bash cURL theme={null} curl -N "https://api.identity.predicate.io/api/v1/status/realtime/individual/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Request ```bash cURL theme={null} curl -N "https://api.identity.predicate.io/api/v1/status/realtime/business/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Event Stream ``` data: {"sessionId":"550e8400-e29b-41d4-a716-446655440000","status":"pending","verifiedWallets":[],"unverifiedWallets":[{"id":"wallet-uuid","address":"0x1234...","chain":"ethereum"}]} : keep-alive data: {"sessionId":"550e8400-e29b-41d4-a716-446655440000","status":"completed","userId":"user-uuid-here","completedAt":"2025-01-21T10:30:00Z","verifiedWallets":[{"id":"wallet-uuid-1","address":"0x1234...","chain":"ethereum"}],"unverifiedWallets":[]} ``` ## Endpoint ``` GET https://api.identity.predicate.io/api/v1/status/realtime/{type}/{sessionId} ``` ## Path Parameters Verification type: `individual` or `business` The session ID from the registration response (UUID format) ## Event Format Events are sent as `data:` lines with JSON payloads. The server also sends periodic `: keep-alive` comments to maintain the connection. The session ID Current status: `pending`, `submitted`, `completed`, `rejected`, or `retry` User ID (present when session has an identity) ISO 8601 timestamp (only present when `completed`) Wallets connected to this session. Only populated when session status is `completed`. Wallets connected to this session. Populated when session is not yet `completed`. ## Status Values | Status | Description | | ----------- | ----------------------------------------------------- | | `pending` | Session created, user in verification process | | `submitted` | Documents submitted, awaiting manual operator review | | `completed` | Verification successful, user is verified | | `rejected` | Verification permanently failed (fraud, sanctions) | | `retry` | Fixable issue (e.g., blurry document), user can retry | **Important:** A `submitted` status means documents are under manual review. The wallet is **not** verified until status becomes `completed`. ## Authentication ``` Authorization: Bearer YOUR_API_KEY ``` # Register Session Source: https://docs.predicate.io/api-reference/v2/endpoint/identity/register Create a new KYC or KYB verification session Create a verification session for individual (KYC) or business (KYB) verification. Returns a session ID, redirect URL, and QR code. This endpoint requires your API key and **must** only be called from your backend server. Never expose your API key in frontend code. #### Request ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/individual?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Request (with wallet constraint) ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/individual?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" \ -H "Content-Type: application/json" \ -d '{"walletAddress": "0x1234567890abcdef1234567890abcdef12345678"}' ``` #### Response ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "redirectUrl": "https://identity.predicate.io/session/550e8400-e29b-41d4-a716-446655440000", "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSU..." } ``` #### Request ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/business?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Request (with wallet constraint) ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/business?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" \ -H "Content-Type: application/json" \ -d '{"walletAddress": "0x1234567890abcdef1234567890abcdef12345678"}' ``` #### Response ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "redirectUrl": "https://identity.predicate.io/session/550e8400-e29b-41d4-a716-446655440000", "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSU..." } ``` ## Endpoints | Type | Endpoint | | ---------------- | ------------------------------------------------------------------- | | Individual (KYC) | `POST https://api.identity.predicate.io/api/v1/register/individual` | | Business (KYB) | `POST https://api.identity.predicate.io/api/v1/register/business` | ## Query Parameters QR code size in pixels. Must be between 80 and 2000. ## Request Body (Optional) Wallet address that must be used for this session. **Wallet Constraint:** When you include `walletAddress` in the request: * The user can **only** connect and verify with that specific wallet address * If a session (`pending`, `retry`, or `completed`) already exists for that wallet with the same verification type, the existing session is returned (session reuse) * Sessions are only reused within the same check type (KYC sessions for `/register/individual`, KYB for `/register/business`) * Useful for pre-registering known wallet addresses or enforcing wallet constraints ## HTTP Status Codes | Status Code | Description | | ------------- | ------------------------------------------------------------------------------------------ | | `201 Created` | A new session was created | | `200 OK` | An existing session was reused (when `walletAddress` matched an existing reusable session) | ## Response Fields Unique session identifier (UUID) URL where the user completes verification Base64-encoded PNG QR code with data URI prefix ## Authentication ``` Authorization: Bearer YOUR_API_KEY ``` # Get Session Status Source: https://docs.predicate.io/api-reference/v2/endpoint/identity/status Check the current status of a verification session Retrieve the current status of a verification session. Use this for polling-based status checks. #### Request ```bash cURL theme={null} curl -X GET "https://api.identity.predicate.io/api/v1/status/individual/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Request ```bash cURL theme={null} curl -X GET "https://api.identity.predicate.io/api/v1/status/business/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` #### Response (Completed) ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "userId": "user-uuid-here", "completedAt": "2025-01-21T10:30:00Z", "verifiedWallets": [ { "id": "wallet-uuid-1", "address": "0x1234...", "chain": "ethereum" }, { "id": "wallet-uuid-2", "address": "0x5678...", "chain": "polygon" } ], "unverifiedWallets": [] } ``` #### Response (Pending) ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "status": "pending", "verifiedWallets": [], "unverifiedWallets": [ { "id": "wallet-uuid", "address": "0x1234...", "chain": "ethereum" } ] } ``` ## Endpoint ``` GET https://api.identity.predicate.io/api/v1/status/{type}/{sessionId} ``` ## Path Parameters Verification type: `individual` or `business` The session ID from the registration response (UUID format) ## Response Fields The session ID Current status: `pending`, `submitted`, `completed`, `rejected`, or `retry` User ID (present when session has an identity) ISO 8601 timestamp (only present when `completed`) Wallets connected to this session. Populated when `completed`. Wallets connected to this session. Populated when not `completed`. ## Status Values | Status | Description | | ----------- | ----------------------------------------------------- | | `pending` | Session created, user in verification process | | `submitted` | Documents submitted, awaiting manual operator review | | `completed` | Verification successful, user is verified | | `rejected` | Verification permanently failed (fraud, sanctions) | | `retry` | Fixable issue (e.g., blurry document), user can retry | **Important:** A `submitted` status means documents are under manual review. The wallet is **not** verified until status becomes `completed`. ## Authentication ``` Authorization: Bearer YOUR_API_KEY ``` # Verify Wallet Source: https://docs.predicate.io/api-reference/v2/endpoint/identity/verify Check if a wallet address belongs to a verified identity Check if a wallet address belongs to a verified identity. Returns verification status and all associated wallets. This endpoint requires your API key and **must** only be called from your backend server. #### Request ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/verify" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0x1234567890abcdef1234567890abcdef12345678", "type": "individual" }' ``` #### Response (Verified) ```json theme={null} { "address": "0x1234...", "verified": true, "status": "completed", "userId": "uuid-of-verified-identity", "checkType": "kyc", "verifiedAt": "2025-01-21T10:30:00Z", "wallets": [ { "address": "0x1234...", "chain": "ethereum" }, { "address": "0x5678...", "chain": "polygon" } ] } ``` #### Response (Not Verified) ```json theme={null} { "address": "0x1234...", "verified": false } ``` ## Endpoint ``` POST https://api.identity.predicate.io/api/v1/verify ``` ## Request Body The wallet address to check Filter by verification type: `individual` (KYC) or `business` (KYB). Omit to check all types. ## Response Fields The wallet address that was checked Whether the wallet belongs to a verified identity Most recent session status: `pending`, `submitted`, `retry`, `rejected`, `completed`, or null if never seen. The user ID of the identity. Only present if the wallet has an associated user. The verification type: `kyc` or `kyb` (`null` if not verified) ISO 8601 timestamp of when verification completed (`null` if not verified) Wallet addresses verified at compatible levels (empty if not verified) ## Status Values | Status | Description | | ----------- | ----------------------------------------------------- | | `pending` | Session created, user in verification process | | `submitted` | Documents submitted, awaiting manual operator review | | `completed` | Verification successful, user is verified | | `rejected` | Verification permanently failed (fraud, sanctions) | | `retry` | Fixable issue (e.g., blurry document), user can retry | **Important:** A `submitted` status means documents are under manual review. The wallet is **not** verified until status becomes `completed`. **Verification Levels:** The verify endpoint returns verified status for wallets that were verified at your organization's current verification level. If you upgrade your verification level, previously verified wallets will need to re-verify. Wallets verified through other organizations at the same scrutiny level will also appear as verified. ## Authentication ``` Authorization: Bearer YOUR_API_KEY ``` # Common Errors Source: https://docs.predicate.io/v2/applications/common-errors Troubleshoot common integration issues and API errors ## API Validation Errors **Cause**: The provided address doesn't have an associated policy on the specified chain. **Solutions**: * Verify you have registered your contract address in the Predicate dashboard * Confirm you called `setRegistry()` and `setPolicyID()` on your contract * Ensure the contract's Policy ID matches your dashboard configuration * Ensure you're using a supported chain name **Example Error**: ``` request validation failed: failed to get policy ID for address 0xa88274BD794c60B2F6FED3b471044c091Aea04E1 ``` **Cause**: Request is missing required transaction fields. **Solution**: Include all required fields in your request: ```json theme={null} { "from": "0x1234567890123456789012345678901234567890", "to": "0x1234567890123456789012345678901234567890", "chain": "ethereum" } ``` `data` and `msg_value` are optional — only needed when using `PredicateClient` for function-level policy validation. **Example Error**: `Missing required field: from` **Cause**: The specified chain ID is not supported by your configuration. **Solutions**: * Use a supported chain ID (check your dashboard for available chains) * Contact support to add chain support * Verify chain configuration in your environment **Example Error**: `unsupported chain ID 999` ## HTTP Status Errors **Cause**: API key is missing, invalid, or expired. **Solutions**: * Include API key in request header: `x-api-key: YOUR_API_KEY` * Verify API key is active in the Predicate dashboard * Regenerate API key if needed * Check for typos in the API key **Headers Example**: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ https://api.predicate.io/v2/attestation ``` **Cause**: Transaction validation took longer than 25 seconds. **Solutions**: * Retry the request with exponential backoff * Contact support if timeouts persist **Retry Logic Example**: ```typescript theme={null} const retryWithBackoff = async (fn, maxRetries = 3) => { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.status === 408 && i < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000)); continue; } throw error; } } }; ``` **Cause**: Too many requests in a short time period. **Solutions**: * Implement exponential backoff in your retry logic * Review your rate limits in the dashboard * Consider upgrading your plan for higher limits (contact support) ## Integration Errors **Cause**: Attestation signature or format is incorrect for smart contract verification. **Solutions**: * Validate signature encoding is correct (most likely culprit) * Ensure you're using the latest `PredicateClient.sol` * Verify the request to Predicate API matches the encoding on the application Contract **Cause**: The `data` and/or `msg_value` sent to the Predicate API don't match what the contract encodes onchain. The attestation is signed over these values, so any mismatch causes the onchain signature check to fail. This is the most common integration error when using `PredicateClient`. It does **not** apply to `BasicPredicateClient`, which omits `data` and `msg_value` entirely. **Solutions**: * **Switch to `BasicPredicateClient`** if your policy only validates *who* can transact (AML/KYC, allowlist/denylist, geo-restrictions). This eliminates calldata encoding entirely — the API request only needs `from`, `to`, and `chain`. * If you need `PredicateClient`, ensure the `data` field sent to the API is the exact output of `abi.encodeWithSignature` for the **internal** function your contract calls (e.g., `_deposit(uint256)`, not the external function). * Ensure `msg_value` matches the value the transaction will send onchain. Use `"0x0"` or `"0"` if no ETH is sent. # 1. Dashboard Setup Source: https://docs.predicate.io/v2/applications/dashboard-setup Create your project and get your API credentials Predicate is currently invite-only. Contact the Predicate team to request access. Accept your email invitation and navigate to [app.predicate.io](https://app.predicate.io) to create your organization. Create a new Application Compliance project. The onboarding flow will guide you through generating an API key and testing the API. By the end of onboarding, you'll have: * A `verification_hash` that identifies your project, used in API requests or for the onchain integration step * An API key for accessing the Predicate API *** ## Interface-Only Mode After creating your project, you're in interface-only mode. This allows you to evaluate compliance for any wallet address without deploying a smart contract. Your project defaults to **Random Compliance Sampling**, a test policy that returns random compliance results. *** ## Verification Hash Your `verification_hash` is a unique project identifier used in API requests. You'll find it in your project settings and need to include it in all attestation requests. ```bash theme={null} curl -X POST 'https://api.predicate.io/v2/attestation' \ --header 'x-api-key: {YOUR_API_KEY}' \ --header 'Content-Type: application/json' \ --data '{ "verification_hash": "{YOUR_VERIFICATION_HASH}", "from": "0x4675C7e5BaAFBFFbca748158bEcBA61ef3b0a263", "chain": "ethereum" }' ``` The API evaluates the `from` address against your configured policy and returns an attestation with the compliance result. If you encounter errors, see [Common Errors](/v2/applications/common-errors) for troubleshooting. *** ## Next Steps Once you've setup your first project, continue to [Offchain Integration](/v2/applications/offchain-integration) to integrate the Predicate API into your application. # FAQ Source: https://docs.predicate.io/v2/applications/faq Frequently asked questions about Application Compliance The Predicate Registry is the core onchain contract that verifies compliance attestations. It has two primary responsibilities: * **Attester management** — Stores authorized attester addresses that can sign attestations * **Attestation verification** — Validates the authenticity and expiration of attestations Predicate Labs owns and operates the registry. The registry uses the ERC-1967 upgradeable proxy pattern: * **Proxy contract** — The address you interact with, which delegates calls to the implementation * **Implementation contract** — Contains the logic and can be upgraded by the owner * **Two-step ownership** — Uses `Ownable2StepUpgradeable` so ownership transfers require explicit acceptance Keys are rotated on a frequent basis. New keys are registered before old keys are deregistered, ensuring zero downtime during rotation. ECDSA on EVM chains, Ed25519 on Solana. Each attestation includes a unique UUID that is marked as spent after validation. The chain ID is also included in the signed hash to prevent cross-chain replay. # Migrating from V1 Source: https://docs.predicate.io/v2/applications/migration Changes required to upgrade from V1 to V2 ## What's New * **No contract required to start** — test the API and integrate offchain before deploying contracts * **Multi-chain support** — deploy to multiple chains and manage them from a single project * **Offchain policy management** — update policies through the dashboard without onchain transactions * **BasicPredicateClient** — simpler integration for who-based policies (AML/KYC, allowlist/denylist) without calldata encoding ## Changes In V1, `policyID` on your contract selected which policy was enforced. Changing policies required an onchain `setPolicy()` transaction. In V2, the onchain `policyID` is set to a `verification_hash` value during [dashboard setup](/v2/applications/dashboard-setup). Policy selection and updates are managed entirely through the dashboard and enforced immediately. The onchain `policyID` remains unchanged. * Replaces the `ServiceManager` contract with the [Predicate Registry](/v2/applications/quickstart#supported-blockchains) * Introduces `BasicPredicateClient` for simpler "Who"-based policies — no calldata encoding required * Refactors the `PredicateClient` for consistency with the Predicate Registry ### Attestation struct `PredicateMessage` is replaced by `Attestation`: ```solidity theme={null} // V1 import {PredicateMessage} from "predicate-contracts/interfaces/IPredicateClient.sol"; // V2 import {Attestation} from "@predicate/contracts/src/interfaces/IPredicateRegistry.sol"; ``` ### \_authorizeTransaction ```solidity V1 theme={null} _authorizeTransaction(_message, encodedSigAndArgs, msg.sender, msg.value) ``` ```solidity V2 — BasicPredicateClient theme={null} _authorizeTransaction(_attestation, msg.sender) ``` ```solidity V2 — PredicateClient theme={null} _authorizeTransaction(_attestation, encodedSigAndArgs, msg.sender, msg.value) ``` ### Request | | V1 | V2 | | ------------------ | --------------------------------------------- | ------------------------------------ | | Endpoint | `/v1/task` | `/v2/attestation` | | Chain field | `chain_id` (numeric) | `chain` (string, e.g. `"ethereum"`) | | Project identifier | `to` (contract address) | `verification_hash` or `to` | | Required fields | `from`, `to`, `data`, `msg_value`, `chain_id` | `from`, `verification_hash`, `chain` | For onchain enforcement, use `to` (contract address) instead of `verification_hash`. `data` and `msg_value` are only required with `PredicateClient`. ### Response | V1 | V2 | | -------------- | ----------------------------------------------- | | `task_id` | `attestation.uuid` | | `expiry_block` | `attestation.expiration` | | `signers[]` | `attestation.attester` | | `signature[]` | `attestation.signature` | | — | `policy_id`, `policy_name`, `verification_hash` | Using the SDK is optional. V2 introduces the following changes: | V1 | V2 | | ---------------------------------------- | ------------------------------------------------ | | `import from '@predicate/core'` | `import from '@predicate/core/v2'` | | `PredicateRequest` with `chain_id` | `PredicateRequestV2` with `chain` | | `evaluatePolicy()` | `requestAttestation()` | | `signaturesToBytes()` to encode response | Response `attestation` object is passed directly | | `packFunctionArgs()` always required | Only required with `PredicateClient` | *** ## Migration Checklist Create a new project on [app.predicate.io](https://app.predicate.io) and configure your policy. You'll receive a `verification_hash` for your project. Point API calls to `/v2/attestation` with `verification_hash` and `chain` (string). If using the SDK, import from `@predicate/core/v2` and replace `evaluatePolicy()` with `requestAttestation()`. If you must upgrade existing contracts instead of deploying new ones, reach out to the Predicate team to coordinate. Implement [`BasicPredicateClient`](/v2/applications/onchain-enforcement) or [`PredicateClient`](/v2/applications/onchain-enforcement) with the Predicate Registry address. Set your `verification_hash` as the `policyID`. Register your contract addresses in the dashboard to link them to your project. Replace `verification_hash` with `to` (your contract address) in API requests. Pass the `Attestation` struct to your contract functions. # 2. Offchain Integration Source: https://docs.predicate.io/v2/applications/offchain-integration Integrate the Predicate API to evaluate compliance in your application Integrate the Predicate API into your backend to evaluate users against your configured policy. The API returns a compliance result and a signed attestation. Call the Predicate API from your server using your API key. Use `is_compliant` to gate access in your application. *** ## Backend Integration Store your API key on the server and call the Predicate API. Use the `verification_hash` from your project and the user's wallet address. ```typescript theme={null} import express from 'express'; const app = express(); app.use(express.json()); app.post('/api/compliance/check', async (req, res) => { const { userAddress } = req.body; const response = await fetch('https://api.predicate.io/v2/attestation', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.PREDICATE_API_KEY, }, body: JSON.stringify({ verification_hash: process.env.PREDICATE_VERIFICATION_HASH, from: userAddress, chain: 'ethereum', }), }); const result = await response.json(); if (!result.is_compliant) { return res.status(403).json({ error: 'User not compliant', policy: result.policy_name, reason: result.reason, // may include code and message from the policy }); } // User is compliant — proceed with your business logic res.json({ compliant: true }); }); ``` *** ## Request Parameters | Field | Required | Description | | ------------------- | -------- | ------------------------------------------------- | | `verification_hash` | Yes | Your project identifier from the dashboard | | `from` | Yes | The user's wallet address to evaluate | | `chain` | Yes | Chain name (e.g., `ethereum`, `base`, `arbitrum`) | *** ## Response #### Compliant ```json theme={null} { "policy_id": "policy_abc123", "policy_name": "Standard AML Policy", "verification_hash": "x-abc123def456", "is_compliant": true, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 1696640400, "attester": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "signature": "0x1b2c3d4e5f..." } } ``` #### Non-Compliant Some policies include a `reason` object on non-compliant responses with details about why the evaluation failed. Both the `code` and `message` values are fully customizable when authoring a policy. ```json theme={null} { "policy_id": "x-dev-trm-risk-abc123", "policy_name": "TRM Risk Score", "verification_hash": "x-abc123def456", "is_compliant": false, "attestation": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "expiration": 0, "attester": "", "signature": "" }, "reason": { "code": "trm_high_risk", "message": "TRM risk score for this wallet is 10 which exceeds the maximum allowed of 7" } } ``` | Field | Description | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `is_compliant` | Whether the user passed the policy evaluation | | `policy_name` | Name of the policy that was evaluated | | `attestation` | Signed attestation (used for onchain enforcement in Phase 2) | | `reason` | Optional object on non-compliant responses. Contains `code` (machine-readable) and `message` (human-readable) fields defined by the policy. | *** ## Supported Chains | Chain | Value | | --------------- | ----------------- | | Ethereum | `ethereum` | | Base | `base` | | Arbitrum | `arbitrum` | | Hyperliquid | `hyperliquid` | | Plume | `plume` | | BSC | `bsc` | | Plasma | `plasma` | | World | `worldchain` | | Sepolia | `sepolia` | | Base Sepolia | `base_sepolia` | | Solana | `solana` | | MegaETH | `megaeth` | | MegaETH Testnet | `megaeth_testnet` | *** ## Next Steps Your offchain integration is complete. Your application can now evaluate compliance for any user. When you're ready to enforce compliance at the smart contract level, continue to [Onchain Enforcement](/v2/applications/onchain-enforcement). # 3. Onchain Enforcement Source: https://docs.predicate.io/v2/applications/onchain-enforcement Deploy a Predicate-enabled smart contract Onchain enforcement requires users to submit valid attestations to your smart contract. The contract verifies the attestation before executing protected business logic. Inherit from `PredicateClient` and set your `verification_hash` as the `policyID`. Register your contract address in the dashboard to link it to your project. *** ## Installation ```bash Hardhat theme={null} npm i @predicate/contracts ``` ```bash Foundry theme={null} forge install PredicateLabs/predicate-contracts ``` ## Choose Your Client | Client | Use Case | | ---------------------- | ------------------------------------------------------------------------ | | `BasicPredicateClient` | Who-based policies: AML/KYC, allowlist/denylist, geo-restrictions | | `PredicateClient` | Policies that validate function calls, parameters, or value-based limits | Both clients use ERC-7201 namespaced storage for upgrade safety and are audited. ## Example Contract Set your `verification_hash` from the dashboard as the `policyID` when deploying. ```solidity theme={null} // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.28; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BasicPredicateClient} from "@predicate/contracts/src/mixins/BasicPredicateClient.sol"; import {Attestation} from "@predicate/contracts/src/interfaces/IPredicateRegistry.sol"; contract Vault is BasicPredicateClient, Ownable { mapping(address => uint256) public balances; constructor( address _owner, address _registry, string memory _policyID // Use your verification_hash here ) Ownable(_owner) { _initPredicateClient(_registry, _policyID); } function deposit(Attestation calldata _attestation) external payable { require(_authorizeTransaction(_attestation, msg.sender), "Unauthorized"); balances[msg.sender] += msg.value; } function setPolicyID(string memory _policyID) external onlyOwner { _setPolicyID(_policyID); } function setRegistry(address _registry) external onlyOwner { _setRegistry(_registry); } } ``` ```solidity theme={null} // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.28; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {PredicateClient} from "@predicate/contracts/src/mixins/PredicateClient.sol"; import {Attestation} from "@predicate/contracts/src/interfaces/IPredicateRegistry.sol"; contract Vault is PredicateClient, Ownable { mapping(address => uint256) public balances; constructor( address _owner, address _registry, string memory _policyID // Use your verification_hash here ) Ownable(_owner) { _initPredicateClient(_registry, _policyID); } function deposit( uint256 _amount, Attestation calldata _attestation ) external payable { bytes memory encodedSigAndArgs = abi.encodeWithSignature( "_deposit(uint256)", _amount ); require( _authorizeTransaction(_attestation, encodedSigAndArgs, msg.sender, msg.value), "Unauthorized" ); require(msg.value == _amount, "Incorrect ETH amount"); balances[msg.sender] += _amount; } function setPolicyID(string memory _policyID) external onlyOwner { _setPolicyID(_policyID); } function setRegistry(address _registry) external onlyOwner { _setRegistry(_registry); } } ``` ## Constructor Parameters | Parameter | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------- | | `_registry` | Predicate Registry address for your chain. See [Supported Blockchains](/v2/applications/quickstart#supported-blockchains). | | `_policyID` | Your `verification_hash` from the dashboard | *** ## Add Contract to Dashboard After deploying, add your contract address to your project in the dashboard. This links your contract to your policy configuration. ## Solana Program Integration Predicate provides an Anchor-based program for Solana that handles attestation validation onchain. ### Installation ```bash theme={null} git clone https://github.com/PredicateLabs/sol-contracts cd sol-contracts anchor build ``` ### Integration Overview The Solana implementation uses Program Derived Addresses (PDAs) for: * **Policy Accounts**: Policies associated with program addresses * **Attestor Accounts**: Registered attestors who can sign attestations * **Used UUID Accounts**: Replay protection via PDA existence checks ### Example Usage ```rust theme={null} invoke( &validate_attestation( registry_pda, attestor_account, policy_account, target_program, msg_value, encoded_instruction_data, attestor_key, attestation, ), &[/* accounts */], )?; // If validation succeeds, execute business logic ``` The Solana implementation is in active development and has not yet been audited. Use with caution in production environments. ### Resources * [sol-contracts Repository](https://github.com/PredicateLabs/sol-contracts) * [Architecture Documentation](https://github.com/PredicateLabs/sol-contracts/blob/main/ARCHITECTURE.md) *** ## Next Steps Continue to [Onchain Integration](/v2/applications/onchain-integration) to update your offchain code. # 4. Onchain Integration Source: https://docs.predicate.io/v2/applications/onchain-integration Update your offchain code to submit attestations to your contract After deploying your contract and adding it to the dashboard, update your offchain integration to target your contract address and pass attestations onchain. Replace `verification_hash` with `to` (your contract address). Include the attestation struct when calling predicated functions. *** ## Backend Changes Replace `verification_hash` with your contract address in the `to` field. ```typescript theme={null} app.post('/api/predicate/attestation', async (req, res) => { const { userAddress, contractAddress } = req.body; const response = await fetch('https://api.predicate.io/v2/attestation', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.PREDICATE_API_KEY, }, body: JSON.stringify({ to: contractAddress, from: userAddress, chain: 'base', }), }); const result = await response.json(); if (!result.is_compliant) { return res.status(403).json({ error: 'Not compliant' }); } res.json({ attestation: result.attestation }); }); ``` ### Request Parameters | Field | Description | | ------- | ------------------------------------------------- | | `to` | Your deployed contract address | | `from` | The user's wallet address | | `chain` | Chain name (e.g., `base`, `ethereum`, `arbitrum`) | *** ## Frontend Changes Fetch the attestation and pass it to your contract function. ```typescript theme={null} // 1) Fetch attestation from your backend const { attestation } = await fetch('/api/predicate/attestation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userAddress: account, contractAddress: VAULT_ADDRESS, }), }).then(r => r.json()); // 2) Call your contract with the attestation const tx = await vault.deposit( { uuid: attestation.uuid, expiration: attestation.expiration, attester: attestation.attester, signature: attestation.signature, }, { value: depositAmount } ); ``` ### Attestation Struct | Field | Type | Description | | ------------ | --------- | --------------------------------------------- | | `uuid` | `bytes16` | Unique identifier for replay protection | | `expiration` | `uint256` | Unix timestamp when the attestation expires | | `attester` | `address` | Address of the Predicate attester that signed | | `signature` | `bytes` | ECDSA signature over the attestation | ## Backend Changes Update your API calls to target your program address. ```typescript theme={null} app.post('/api/predicate/attestation', async (req, res) => { const { userPubkey, programId, data } = req.body; const response = await fetch('https://api.predicate.io/v2/attestation', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.PREDICATE_API_KEY, }, body: JSON.stringify({ to: programId, from: userPubkey, data, msg_value: '0', chain: 'solana', }), }); const result = await response.json(); if (!result.is_compliant) { return res.status(403).json({ error: 'Not compliant' }); } res.json({ attestation: result.attestation }); }); ``` *** ## Frontend Changes Include an Ed25519 verification instruction before your program instruction, then call your method. ```typescript theme={null} const { attestation } = await fetch('/api/predicate/attestation', { /* ... */ }) .then(r => r.json()); // 1) Verify Ed25519 signature (must precede your program instruction) const ed25519Instruction = Ed25519Program.createInstructionWithPublicKey({ publicKey: attesterPubkey.toBytes(), message: messageHash, signature: signatureBytes, }); // 2) Create your program instruction const clientInstruction = await program.methods .yourMethod(attestation) .accounts({ // ... your accounts predicateRegistry: registryPda, attesterAccount: attesterPda, policyAccount: policyPda, usedUuidAccount: usedUuidPda, instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, }) .instruction(); // 3) Build and send (Ed25519 instruction MUST come first) const transaction = new Transaction(); transaction.add(ed25519Instruction); transaction.add(clientInstruction); await provider.sendAndConfirm(transaction, [userKeypair]); ``` # Overview Source: https://docs.predicate.io/v2/applications/quickstart Enforce compliance policies on users interacting with your application Application Compliance enables financial technology companies to evaluate users against compliance policies before granting access to protected functionality. Predicate handles policy evaluation offchain and returns signed cryptographic attestations which can be passed onchain with a users transaction to permit them. ## Integration Phases Set up your project, configure a policy, and integrate the Predicate API into your application. Policy evaluation works end-to-end without any smart contract deployment. When you're ready to enforce compliance at the smart contract level, deploy a Predicate-enabled contract and update your offchain integration to submit attestations onchain. *** ## Phase 1: Offchain Integration Get Predicate working in your application. Create your project, configure a policy, and generate API credentials. [Go to Dashboard Setup →](/v2/applications/dashboard-setup) Integrate the Predicate API into your backend to evaluate users against your policy. [Go to Offchain Integration →](/v2/applications/offchain-integration) After completing Phase 1, your application can evaluate compliance for any user and take action based on the result. *** ## Phase 2: Onchain Enforcement Enforce compliance at the smart contract level. Deploy a contract that inherits from `PredicateClient` and requires valid attestations. [Go to Onchain Enforcement →](/v2/applications/onchain-enforcement) Modify your API calls to target your contract address and pass attestations to your contract functions. [Go to Onchain Integration →](/v2/applications/onchain-integration) *** ## Supported Blockchains Onchain enforcement is available on any chain where the Predicate Registry has been deployed. | Chain | Address | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Ethereum | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://etherscan.io/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Base | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://basescan.org/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Arbitrum | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://arbiscan.io/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Hyperliquid | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://hyperevmscan.io/address/0xe15a8ca5bd8464283818088c1760d8f23b6a216e) | | Plume | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://explorer.plume.org/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | BSC | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://bscscan.com/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Plasma | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://plasmascan.to/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | World | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://worldscan.org/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Sepolia | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://sepolia.etherscan.io/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Base Sepolia | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://sepolia.basescan.org/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | MegaETH | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://mega.etherscan.io/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | MegaETH Testnet | [`0xe15a8Ca5BD8464283818088c1760d8f23B6a216E`](https://testnet-mega.etherscan.io/address/0xe15a8Ca5BD8464283818088c1760d8f23B6a216E) | | Solana | [`GjXtvmWihnf22Bg48srpzYrs6iGhSUvu1tzsf9L4u9Ck`](https://solscan.io/account/GjXtvmWihnf22Bg48srpzYrs6iGhSUvu1tzsf9L4u9Ck) | # Get Started Source: https://docs.predicate.io/v2/assets/get-started Integrate Asset Compliance into your token ## EVM (ERC20 Tokens) ### Prerequisites Your ERC20 token contract must implement the IFreezable interface. This interface provides freeze functionality with role-based access control. Reference implementation: [Freezable Contract](https://github.com/predicatelabs/predicate-contracts/blob/main/src/Freezable.sol) ### Integration Steps Grant Predicate's freeze manager address the FREEZE\_MANAGER\_ROLE. This allows Predicate to enforce compliance actions on your token contract. The Predicate authorized freezer across all EVM chains is `0x363c256D368277BBFaf6EaF65beE123a7AdbA464` ```bash Foundry (cast) theme={null} # Grant freeze manager role to Predicate cast send \ "grantRole(bytes32,address)" \ $(cast keccak "FREEZE_MANAGER_ROLE") \ 0x363c256D368277BBFaf6EaF65beE123a7AdbA464 \ --rpc-url \ --private-key ``` ```solidity Solidity theme={null} // In your contract's initialization or admin function _grantRole(FREEZE_MANAGER_ROLE, 0x363c256D368277BBFaf6EaF65beE123a7AdbA464); ``` Complete enrollment through the Predicate application. * Request Access: Contact the Predicate team for an invitation. * Add Your Token: Add a new project to the Predicate Application by supplying your Asset details Once enrolled, Predicate will continuously monitor the data sources defined in your policy and enforce actions as addresses are detected. You can return to the dashboard anytime to review enforcement activity. ## Stellar (Soroban) ### Prerequisites Your Soroban token contract must implement the `FungibleBlockList` trait from [OpenZeppelin Stellar Contracts](https://docs.openzeppelin.com/contracts-stellar). This provides blocklist functionality with role-based access control, allowing Predicate to block and unblock addresses on your token. ### Integration Steps Grant Predicate's Stellar account the blocker role on your token contract. This allows Predicate to enforce compliance actions by blocking and unblocking addresses. The Predicate authorized account on Stellar is `GCDJXKIZODN7SISUQYLIP2PDIRAGASCGV7P62ABEJE7M7ISAZVHD6SFL` When deploying your contract, pass Predicate's address as the `blocker` parameter in your constructor, or grant the role via your contract's access control functions: ```rust theme={null} // During contract initialization access_control::grant_role_no_auth( e, &predicate_address, &symbol_short!("blocker"), &admin, ); ``` Complete enrollment through the Predicate application. * Request Access: Contact the Predicate team for an invitation. * Add Your Token: Add a new project to the Predicate Application by supplying your Stellar asset details Once enrolled, Predicate will continuously monitor the data sources defined in your policy and enforce actions as addresses are detected. You can return to the dashboard anytime to review enforcement activity. # Overview Source: https://docs.predicate.io/v2/assets/overview Automated, policy-driven enforcement for regulated tokens on public blockchains ## Integration Steps Your token contract must implement a supported interface that allows our system to freeze, pause, or restrict transfers to high-risk addresses. * **EVM**: Implement the [IFreezable](https://github.com/predicatelabs/predicate-contracts/blob/main/src/interfaces/IFreezable.sol) interface on your ERC20 token * **Stellar (Soroban)**: Implement the `FungibleBlockList` trait from [OpenZeppelin Stellar Contracts](https://docs.openzeppelin.com/contracts-stellar) Our system must be authorized to enforce compliance actions on your token. * **EVM**: Grant Predicate the `FREEZE_MANAGER_ROLE` on your contract * **Stellar (Soroban)**: Grant Predicate the `blocker` role on your contract Requires an invitation from the Predicate team, please contact us to request access Enroll your token contract and set your policy for automated enforcements ### Enforcement Mechanisms All enforcement is configurable. Your policy determines which data sources trigger which actions. **Freezing** applies to addresses on sanctions lists referenced in your policy. When a sanctioned address is detected, Predicate automatically freezes it on your enrolled token contracts. **Pauses** and **Transfer Restrictions** apply to high-risk addresses identified through your compliance criteria. These controls enable you to restrict activity for addresses that require additional review or intervention. ## How It Works 1. **Monitoring**: Predicate continuously monitors the sanctions lists and data sources defined in your policy 2. **Detection**: New entries are identified and queued for onchain enforcement 3. **Enforcement**: Actions are executed automatically on your enrolled token contracts 4. **Visibility**: Full audit trail of all enforcement actions available in the dashboard ## Security * **Secure Key Management**: Enforcement keys are secured in tamper-resistant infrastructure with no human access * **Transaction Resilience**: Failed transactions are monitored and redelivered automatically * **Multi-RPC Failover**: Redundant endpoints ensure continuous operation * **Audit Trail**: Immutable record of all actions for regulatory compliance ## Supported Chains * Ethereum * Base * Hyperliquid * Soneium * Arbitrum * Linea * Stellar # AI Assistance Source: https://docs.predicate.io/v2/essentials/ai Connect Predicate documentation to AI coding assistants via MCP ## MCP Server Integration Connect AI assistants directly to Predicate documentation for real-time search and reference. ```json Cursor theme={null} // Open MCP Settings → New MCP Server { "mcpServers": { "predicate": { "url": "https://docs.predicate.io/mcp" } } } ``` ```bash Claude Code theme={null} claude mcp add --transport http predicate https://docs.predicate.io/mcp ``` ```json Claude Desktop theme={null} // Add to ~/.claude/mcp.json { "mcpServers": { "predicate-docs": { "transport": "sse", "url": "https://docs.predicate.io/mcp", "readOnly": true } } } ``` ```json Windsurf theme={null} // Settings → MCP Servers → Add Server { "name": "predicate-docs", "url": "https://docs.predicate.io/mcp", "transport": "sse", "readOnly": true } ``` ```json VS Code theme={null} // Add to .continue/config.json { "experimental": { "modelContextProtocol": { "servers": [{ "name": "predicate-docs", "url": "https://docs.predicate.io/mcp", "transport": "sse" }] } } } ``` If you can't use MCP servers copy & paste [llms-full.txt](https://docs.predicate.io/llms-full.txt) directly into your AI conversations # Audits Source: https://docs.predicate.io/v2/essentials/audits Security policies and practices ### Audit Details **Auditor:** Open Zeppelin\ **Scope:** Predicate Registry V2 contracts\ **Date:** Jan 28, 2025 ⬇ Download Open Zeppelin Audit Report **Auditor:** Neodyme AG\ **Scope:** Predicate Registry V2 (Solana Programs)\ **Date:** Jan 29, 2026 ⬇ Download Neodyme Audit Report **Auditor:** Cantina\ **Scope:** Predicate Registry V1 (ServiceManager) contracts\ **Date:** May 28, 2025 ⬇ Download Cantina Audit Report *** # Design Source: https://docs.predicate.io/v2/essentials/design How Predicate policy enforcement works ## One Abstraction Predicate provides a single integration that abstracts away all compliance and policy enforcement complexity. Once integrated, you can get back to building what matters most—your core product—while Predicate automatically handles policy enforcement in the background. As your organization scales across different geographies with varying regulations, or as your business requirements evolve, your team can create and modify policies without touching your smart contracts or disrupting your development workflow. We offer two solutions: Light mode system diagram Dark mode system diagram ### Core Components **Web Application**: The server hosting the application business logic that calls the Predicate API before processing each transaction **Predicate API**: Performant RESTful service that evaluates a transaction against a policy and returns a signed compliance attestation for onchain enforcement **Onchain Application**: The smart contract(s) containing your application business logic (e.g. vault) with one or more *predicated* functions **Predicate Registry**: Predicate-owned smart contract used to verify compliance attestations on behalf of the onchain application ### The E2E Attestation Process 1. The app backend sends transaction context to the Predicate API. 2. The Predicate API resolves the policy for the target contract and chain, then evaluates compliance. 3. The API returns `is_compliant` and a signed attestation. 4. The attestation is included in the onchain transaction and verified by the Predicate Registry before execution. Light mode sequence diagram Dark mode sequence diagram Low latency is critical to ensuring a seamless user experience. We’ve heavily optimized the Predicate API to deliver fast responses, with the average round-trip time for an attestation request consistently under 400ms. Predicate provides automated, policy-driven enforcement for regulated tokens. Your policy determines which data sources trigger which actions—from sanctions list monitoring to custom compliance criteria. ### Enforcement Mechanisms **Freezing**: Automatically freeze addresses on sanctions lists referenced in your policy **Pauses & Transfer Restrictions**: Restrict activity for high-risk addresses identified through your compliance criteria ### Core Components **Automated Monitoring**: Continuous monitoring of sanctions lists and data sources defined in your policy **Onchain Enforcement**: Actions are executed automatically on your enrolled token contracts with secure key management and transaction resilience **Audit Trail**: Complete history of all enforcement actions available in the dashboard # Overview Source: https://docs.predicate.io/v2/essentials/overview Programmable, real-time compliance enforcement for financial applications built on blockchains ## What Is Predicate Predicate is programmable policy infrastructure for onchain financial products in regulated markets. It allows developers to enforce custom compliance rules at the smart contract level. Policies can be updated any time, making it easy to adapt as regulations and business requirements change. ## How It Works Predicate is purpose-built for regulated financial applications on blockchains, providing a structured way to define, validate, and enforce both compliance policies and business rules. **Programmable Policy**: Configure your policy with the Predicate team catered towards real-world use cases such as AML checks, rate limits, collateral thresholds, and jurisdictional restrictions. **Onchain Enforcement**: Use our smart contract libraries to enforce policies directly at the smart contract level, ensuring only transactions that adhere to your defined policy can be executed. ## Why We've Built It Predicate is founded by a team with deep expertise across regulatory compliance, blockchain infrastructure, and financial services. We understand the burden that evolving regulations and internal business requirements place on engineering teams building in the blockchain ecosystem and aim to solve for it. We believe compliance shouldn't be a bottleneck to innovation. Our platform offers a unified, programmatic layer for policy enforcement so you can launch faster, operate across borders, and stay ahead of regulatory shifts without compromising on control or security. ## Common Use Cases * **AML/CFT Compliance**: Automated sanctions screening and anti-money laundering checks * **KYC/KYB**: Seamless integration with identity verification providers * **Rate Limiting**: Configurable transaction volume and frequency controls * **Collateral Requirements**: Dynamic collateral validation for lending protocols * **Geofencing**: Jurisdiction-based access controls and regional transaction restrictions ## Key Terms A discrete condition within a policy that evaluates a specific requirement (e.g., AML check, minimum collateral). A rule or set of rules make up a policy, and the overall compliance status is determined by evaluating all rule outcomes. A declarative, programmable set of rules that determines whether a transaction meets compliance or business logic criteria. Policies are authored via the Predicate Dashboard and can be updated dynamically without requiring onchain redeployments. The structured result of a policy evaluation which includes relevant execution context (e.g., user wallet, function arguments, value transferred). The statement is signed to form an attestation. A signed data structure generated by Predicate that certifies whether a transaction complies with a configured policy. An attestation combines a statement—the result and relevant context of the policy evaluation—with a signature that binds the statement to a specific attester. This pairing provides cryptographic proof that the evaluation originated from an authorized entity. An entity responsible for evaluating policies and signing the resulting attestation. Currently, the Predicate API acts as the sole attester, identified by its blockchain address. An onchain smart contract/program that validates Predicate attestations, enforces policy-to-client bindings, and manages authorized attesters. It prevents replay attacks by tracking spent UUIDs and ensures attestations are only accepted from registered attesters. # Quickstart Source: https://docs.predicate.io/v2/essentials/quickstart Choose your integration path ## Integration Paths Predicate offers two compliance solutions depending on your needs: Enforce custom policies on smart contract functions. Ideal for DeFi protocols such as lending platforms, bridges, DEXs, and any applications requiring transaction-level compliance. Automated OFAC sanctions enforcement for ERC20 tokens. Predicate monitors and freezes sanctioned addresses per regulatory requirements. Ideal for any regulated ERC20. # Integration Guide Source: https://docs.predicate.io/v2/identity/integration Step-by-step guide to integrate Predicate Identity verification All endpoints require an API key as a Bearer `Authorization` header. API keys must only be used in backend server code. ## Integration Steps Your backend calls the Predicate API to create a KYC or KYB session Show the QR code or redirect URL to the user in your application Use SSE (recommended) or polling to track verification progress Check if a wallet belongs to a verified identity before transactions ## Step 1: Create a Verification Session Your backend creates a session by calling the registration endpoint. The response includes a QR code and redirect URL for the user. ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/individual?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/register/business?size=300" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" ``` **Request Body (Optional):** ```json theme={null} { "walletAddress": "0x1234567890abcdef..." } ``` **Wallet Constraint:** When you include `walletAddress` in the request: * The user can **only** connect and verify with that specific wallet address * If a session (`pending`, `retry`, or `completed`) already exists for that wallet with the same verification type, the existing session is returned (session reuse) * Sessions are only reused within the same check type (KYC sessions for `/register/individual`, KYB for `/register/business`) * Useful for pre-registering known wallet addresses or enforcing wallet constraints **HTTP Status Codes:** * `201 Created` - A new session was created * `200 OK` - An existing session was reused (when `walletAddress` matched an existing reusable session) **Response:** ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "redirectUrl": "https://identity.predicate.io/session/550e8400-e29b-41d4-a716-446655440000", "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSU..." } ``` ```javascript theme={null} const express = require('express'); const router = express.Router(); const KYC_API_KEY = process.env.KYC_API_KEY; router.post('/kyc/create-session', async (req, res) => { const { type, walletAddress } = req.body; // 'individual' or 'business', optional wallet const endpoint = type === 'business' ? 'business' : 'individual'; const url = `https://api.identity.predicate.io/api/v1/register/${endpoint}`; // Build request options const options = { method: 'POST', headers: { 'Authorization': `Bearer ${KYC_API_KEY}` } }; // Include wallet address if provided (restricts session to this wallet) if (walletAddress) { options.headers['Content-Type'] = 'application/json'; options.body = JSON.stringify({ walletAddress }); } try { const response = await fetch(url, options); if (!response.ok) { const error = await response.json(); throw new Error(error.message || `API error: ${response.status}`); } const data = await response.json(); res.json(data); } catch (error) { console.error('Session creation error:', error); res.status(500).json({ error: 'Failed to create verification session' }); } }); module.exports = router; ``` ## Step 2: Display the QR Code and Link Display the base64-encoded QR code from the response directly in an `` tag. ```html theme={null}

Complete Your Verification

Scan the QR code or click the link below to start:

Verify Identity

Verify Your Identity

``` **What happens on the verification portal:** * User lands on the verification page * User connects their wallet(s) - up to 20 per session, across different chains * User completes identity verification * Session status updates are sent via SSE ## Step 3: Monitor Session Status Once a user starts verification, monitor their progress using SSE (recommended) or polling. **Session Status Values:** | Status | Description | | ----------- | ----------------------------------------------------- | | `pending` | Session created, user in verification process | | `submitted` | Documents submitted, awaiting manual operator review | | `completed` | Verification successful, user is verified | | `rejected` | Verification permanently failed (fraud, sanctions) | | `retry` | Fixable issue (e.g., blurry document), user can retry | **Important:** A `submitted` status means documents are under manual review. The wallet is **not** verified until status becomes `completed`. Server-Sent Events provide instant notifications when verification status changes. The SSE endpoint requires your API key. Since browsers' `EventSource` API doesn't support custom headers, you must create a backend proxy. **Proxy Timeout:** Ensure your backend proxy does not impose a response time limit on the SSE connection. Some frameworks (e.g., Next.js API routes) have default timeouts that will prematurely close the stream. Use a standalone server or configure your framework to allow long-lived connections. ```javascript theme={null} router.get('/kyc/stream/:type/:sessionId', async (req, res) => { const { type, sessionId } = req.params; if (type !== 'individual' && type !== 'business') { return res.status(400).json({ error: 'Type must be individual or business' }); } // Validate UUID format const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(sessionId)) { return res.status(400).json({ error: 'Invalid session ID format' }); } // Set SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); const kycUrl = `https://api.identity.predicate.io/api/v1/status/realtime/${type}/${sessionId}`; try { const response = await fetch(kycUrl, { headers: { 'Authorization': `Bearer ${KYC_API_KEY}` } }); if (!response.ok) { throw new Error(`KYC API error: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); req.on('close', () => reader.cancel()); while (true) { const { done, value } = await reader.read(); if (done) break; res.write(decoder.decode(value, { stream: true })); } } catch (error) { console.error('SSE streaming error:', error); res.write(`event: error\ndata: ${JSON.stringify({ error: 'Stream failed' })}\n\n`); } }); ``` ```javascript theme={null} class VerificationMonitor { constructor(sessionId, type) { this.sessionId = sessionId; this.type = type; this.eventSource = null; } start(onStatusUpdate, onError) { // Connect to YOUR backend, not directly to KYC API const url = `/kyc/stream/${this.type}/${this.sessionId}`; this.eventSource = new EventSource(url); this.eventSource.onmessage = (event) => { const status = JSON.parse(event.data); onStatusUpdate(status); if (status.status === 'completed' || status.status === 'rejected') { this.stop(); } }; this.eventSource.onerror = () => { onError('Connection lost'); this.stop(); }; } stop() { if (this.eventSource) { this.eventSource.close(); this.eventSource = null; } } } function startMonitoring(sessionId, type) { const monitor = new VerificationMonitor(sessionId, type); monitor.start( (status) => updateVerificationUI(status), (error) => console.error('Monitoring error:', error) ); return monitor; } ``` **SSE Event Format:** ```json theme={null} { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "userId": "user-uuid-here", "completedAt": "2025-01-21T10:30:00Z", "verifiedWallets": [ { "id": "wallet-uuid-1", "address": "0x1234...", "chain": "ethereum" }, { "id": "wallet-uuid-2", "address": "0x5678...", "chain": "polygon" } ], "unverifiedWallets": [] } ``` **Wallet Arrays:** | Field | Description | | ------------------- | ------------------------------------------------------------------------------------- | | `verifiedWallets` | Wallets connected to this session. Only populated when session status is `completed`. | | `unverifiedWallets` | Wallets connected to this session. Populated when session is not yet `completed`. | If SSE isn't suitable for your infrastructure, poll the status endpoint at regular intervals. ```javascript theme={null} router.get('/kyc/status/:type/:sessionId', async (req, res) => { const { type, sessionId } = req.params; if (type !== 'individual' && type !== 'business') { return res.status(400).json({ error: 'Type must be individual or business' }); } const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(sessionId)) { return res.status(400).json({ error: 'Invalid session ID format' }); } const kycUrl = `https://api.identity.predicate.io/api/v1/status/${type}/${sessionId}`; try { const response = await fetch(kycUrl, { headers: { 'Authorization': `Bearer ${KYC_API_KEY}` } }); if (!response.ok) { throw new Error(`KYC API error: ${response.status}`); } const data = await response.json(); res.json(data); } catch (error) { console.error('Status check error:', error); res.status(500).json({ error: 'Failed to check verification status' }); } }); ``` ```javascript theme={null} class VerificationPoller { constructor(sessionId, type, interval = 3000) { this.sessionId = sessionId; this.type = type; this.interval = interval; this.isPolling = false; } start(onStatusUpdate, onError) { this.onStatusUpdate = onStatusUpdate; this.onError = onError; this.isPolling = true; this.poll(); } async poll() { if (!this.isPolling) return; try { const response = await fetch(`/kyc/status/${this.type}/${this.sessionId}`); const status = await response.json(); this.onStatusUpdate(status); if (status.status === 'completed' || status.status === 'rejected') { this.stop(); return; } this.pollTimer = setTimeout(() => this.poll(), this.interval); } catch (error) { this.onError(error); this.pollTimer = setTimeout(() => this.poll(), this.interval); } } stop() { this.isPolling = false; if (this.pollTimer) clearTimeout(this.pollTimer); } } ``` ## Verifying Wallet Addresses Once a user has completed verification, check if any wallet address belongs to a verified identity. ```bash cURL theme={null} curl -X POST "https://api.identity.predicate.io/api/v1/verify" \ -H "Authorization: Bearer $PREDICATE_KYC_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0x1234567890abcdef...", "type": "individual" }' ``` **Response (verified):** ```json theme={null} { "address": "0x1234...", "verified": true, "status": "completed", "userId": "uuid-of-verified-identity", "checkType": "kyc", "verifiedAt": "2025-01-21T10:30:00Z", "wallets": [ { "address": "0x1234...", "chain": "ethereum" }, { "address": "0x5678...", "chain": "polygon" } ] } ``` **Response (not verified):** ```json theme={null} { "address": "0x1234...", "verified": false } ``` **Verification Levels:** The verify endpoint returns verified status for wallets that were verified at your organization's current verification level. If you upgrade your verification level, previously verified wallets will need to re-verify. Wallets verified through other organizations at the same scrutiny level will also appear as verified. ## Best Practices ### Security * **Never expose API keys in frontend code** - All authenticated operations must happen on your backend * **Use environment variables** - Store API keys in `.env` files or secure secret management systems * **Validate session IDs** - Always validate UUID format before making API calls * **Verify session ownership** - The API ensures sessions can only be accessed by the customer that created them ### User Experience * **Show clear progress indicators** - Update the UI as session status changes * **Handle all statuses** - Display appropriate messages for pending, submitted, completed, rejected, and retry * **Explain submitted status to users** - When status is `submitted`, inform users that their documents are under review and they'll be notified when a decision is made. `submitted` does NOT mean approved. * **Provide both QR and link** - Some users prefer clicking, others prefer scanning * **Consider timeouts** - Sessions don't expire, but you may want to implement client-side timeouts ### Performance * **Use SSE over polling** - Real-time updates are more efficient and user-friendly * **Implement reconnection logic** - Handle network interruptions gracefully * **Close connections when done** - Stop monitoring once verification completes or fails ## Complete Integration Example Here's a full example tying everything together: ```html theme={null} Identity Verification

Verify Your Identity

``` ## API Reference | Method | Endpoint | Purpose | Auth | | ------ | -------------------------------------------- | ----------------------------- | ------- | | `POST` | `/api/v1/register/individual[?size=N]` | Create individual KYC session | API Key | | `POST` | `/api/v1/register/business[?size=N]` | Create business KYB session | API Key | | `POST` | `/api/v1/verify` | Check if wallet is verified | API Key | | `GET` | `/api/v1/status/{type}/{sessionId}` | Get session status (one-shot) | API Key | | `GET` | `/api/v1/status/realtime/{type}/{sessionId}` | Stream session updates (SSE) | API Key | **Query Parameters for Registration Endpoints:** * `size` (optional): QR code size in pixels, between 80 and 2000. Default: 300. All API endpoints require a Bearer token: ``` Authorization: Bearer YOUR_API_KEY ``` # Overview Source: https://docs.predicate.io/v2/identity/overview Wallet identity verification for compliant applications Light mode identity diagram Dark mode identity diagram # For Financial Applications Predicate Identity is a verification service that enables compliant wallet-based identity verification for Web3 applications. Users complete KYC (Know Your Customer) or KYB (Know Your Business) verification through our hosted portal at `identity.predicate.io`, and you can check their verification status in real-time. Once verified, these identities can be enforced directly on your smart contracts through the Predicate API integration, ensuring only compliant users can execute transactions. ## How it Works 1. **Create a verification session** - Your backend calls our API to create a session 2. **Display the verification link** - Show the QR code or link to the user 3. **User completes verification** - They connect wallets and verify their identity on our portal 4. **Monitor session status** - Use SSE or polling to know when they're done 5. **Verify wallets** - Check if any wallet address belongs to a verified identity ## Use Cases * **Token Sale**: Confirm buyer eligibility and jurisdiction * **Institutional Borrowing and Lending**: Verify borrower identity before loan approval * **Swaps**: Ensure counterparty compliance requirements * **High-Value Transactions**: Additional verification for large transfers ## Benefits * **Abstraction**: All commercial agreements and integrations are handled by Predicate * **Onchain Enforcement**: Verify KYC/KYB status along with other rules directly onchain without revealing user information * **Unified Access with Wide Market Coverage**: Access multiple KYC/KYB providers through a single integration * **Real-time Updates**: Know immediately when verification is complete * **Wallet Aggregation**: Multiple wallets can be linked to a single verified identity ## Next Steps Step-by-step guide to integrate identity verification Explore the Identity API endpoints