1
Deploy your contract
Inherit from
PredicateClient and set your verification_hash as the policyID.2
Add contract to dashboard
Register your contract address in the dashboard to link it to your project.
- EVM
- SVM
- Stellar
Installation
npm i @predicate/contracts
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 yourverification_hash from the dashboard as the policyID when deploying.- BasicPredicateClient
- PredicateClient
// 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);
}
}
// 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. |
_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
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
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
Resources
Installation
Predicate provides two Soroban crates:predicate-client (the SDK your contract embeds) and predicate-registry (the deployed registry, used in tests). Add the client to your contract’s Cargo.toml:[dependencies]
soroban-sdk = "23.5.3"
predicate-client = { git = "https://github.com/PredicateLabs/predicate-contracts", branch = "main" }
[dev-dependencies]
predicate-registry = { git = "https://github.com/PredicateLabs/predicate-contracts", branch = "main" }
Example Contract
Store the registry address, yourpolicy_id (your verification_hash from the dashboard), and the Stellar network passphrase at construction. Call predicate_client::authorize_transaction inside any protected function before executing business logic.#![no_std]
use soroban_sdk::{
contract, contractimpl, symbol_short, Address, Bytes, Env, IntoVal, String, Symbol, Val, Vec,
};
use predicate_client::Attestation;
const ADMIN: Symbol = symbol_short!("ADMIN");
const REGISTRY: Symbol = symbol_short!("REGISTRY");
const POLICY: Symbol = symbol_short!("POLICY");
const NETWORK: Symbol = symbol_short!("NETWORK");
#[contract]
pub struct Vault;
#[contractimpl]
impl Vault {
pub fn __constructor(
e: &Env,
admin: Address,
registry: Address,
policy_id: String, // Use your verification_hash here
network: String, // Stellar network passphrase
) {
e.storage().instance().set(&ADMIN, &admin);
e.storage().instance().set(®ISTRY, ®istry);
e.storage().instance().set(&POLICY, &policy_id);
e.storage().instance().set(&NETWORK, &network);
}
/// Register your policy with the registry once, after deployment.
pub fn register_policy(e: &Env) {
let admin: Address = e.storage().instance().get(&ADMIN).unwrap();
admin.require_auth();
let registry: Address = e.storage().instance().get(®ISTRY).unwrap();
let policy_id: String = e.storage().instance().get(&POLICY).unwrap();
let args: Vec<Val> = soroban_sdk::vec![
e,
e.current_contract_address().into_val(e),
policy_id.into_val(e),
];
e.invoke_contract::<()>(®istry, &Symbol::new(e, "set_policy_id"), args);
}
pub fn deposit(e: &Env, from: Address, amount: i128, attestation: Attestation) {
from.require_auth();
let registry: Address = e.storage().instance().get(®ISTRY).unwrap();
let policy: String = e.storage().instance().get(&POLICY).unwrap();
let network: String = e.storage().instance().get(&NETWORK).unwrap();
// Encoded function selector + args (or a hash of it)
let encoded_call = Bytes::from_slice(
e,
&e.crypto()
.sha256(&Bytes::from_slice(e, b"deposit(address,i128)"))
.to_array(),
);
// Reverts if the attestation is invalid, expired, replayed, or non-compliant
predicate_client::authorize_transaction(
e,
®istry,
&attestation,
&encoded_call,
&from,
amount,
&e.current_contract_address(),
&policy,
&network,
);
// ... protected business logic
}
}
Attestation Type
The attestation returned by the API maps to the on-chainAttestation type from predicate-client:#[contracttype]
pub struct Attestation {
pub uuid: String, // Unique identifier for replay protection
pub expiration: u64, // Ledger timestamp when the attestation expires
pub attester: BytesN<32>, // Ed25519 public key of the Predicate attester
pub signature: BytesN<64>, // Ed25519 signature over the statement hash
}
Constructor Parameters
| Parameter | Description |
|---|---|
registry | Predicate Registry contract on Stellar. See Supported Blockchains. |
policy_id | Your verification_hash from the dashboard |
network | Stellar network passphrase (e.g. Public Global Stellar Network ; September 2015), used for signature domain separation |
Attestations are Ed25519-signed over a SHA-256 hash of the network passphrase and the transaction statement.
authorize_transaction invokes validate_attestation on the registry, which checks expiration, UUID replay protection, that the attester is registered, and the signature — binding the signature to the calling contract to prevent cross-contract replay.Add Contract to Dashboard
After deploying and callingregister_policy, add your contract address to your project in the dashboard. This links your contract to your policy configuration.