Before You Start
Determine Your Policy: Work with the Predicate team to define the policy that your application will enforce.
This includes risk management categories and configurations, as well as fallback mechanisms.
API Access: You’ll need to obtain an API key in order to receive predicate attestations for mainnet,
please contact us directly for production keys.
Smart Contract Integration
We will be using a sample bridge contract as example integration.
It inherits the PredicateClient
mixin, which offers an abstraction for authorizing transactions based on the signatures
received from the Predicate API.
In this example the dispatch
is a public function which is “predicating” an internal private function called _dispatch
which handles
the actual business logic.
import {PredicateClient} from "@predicate/mixins/PredicateClient.sol";
contract Bridge is PredicateClient {
function dispatch(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes calldata messageBody,
bytes calldata metadata,
PredicateMessage calldata predicateMessage
) public payable virtual returns (bytes32) {
// Require transaction authorization via the Predicate client
bytes memory encodedSigAndArgs = abi.encodeWithSignature("_dispatch(uint32,bytes32,bytes,bytes)", receiver, amount);
require(_authorizeTransaction(predicateMessage, encodedSigAndArgs), "MetaCoin: unauthorized transaction");
return _dispatch(destinationDomain, recipientAddress, messageBody, metadata);
}
}
Front End Integration
As seen above, all transactions must be accompanied by a PredicateMessage
which contains the signatures issued by the Predicate API.
In order to obtain these signatures, you will need to call the Predicate API with the transaction details.
We’ve provided an SDK to make this process easier.
import {PredicateClient, PredicateRequest, packFunctionArgs, signaturesToBytes} from '@predicate/core';
import { ethers } from 'ethers';
// Type definitions for better type safety
interface DispatchParams {
destinationDomain: number;
recipientAddress: string;
messageBody: string;
metadata: string;
}
interface PredicateMessage {
taskId: string;
expireByBlockNumber: number;
signerAddresses: string[];
signatures: string[];
}
type DispatchFunctionArgs = [
destinationDomain: number,
recipientAddress: string,
messageBody: string,
metadata: string
];
const predicateClient = new PredicateClient({
apiUrl: 'https://api.predicate.io/',
apiKey: process.env.PREDICATE_API_KEY!
});
if (!process.env.PREDICATE_API_KEY) {
console.error("Error: PREDICATE_API_KEY is not set.");
process.exit(1);
}
// Define the ABI for the Bridge contract's dispatch function
const contractABI = [
"function dispatch(uint32 destinationDomain, bytes32 recipientAddress, bytes messageBody, bytes metadata, tuple(string, uint256, address[], bytes[]) predicateMessage) payable returns (bytes32)"
] as const;
// Structured parameters with proper typing
const dispatchParams: DispatchParams = {
destinationDomain: 1,
recipientAddress: '0x38f6001e8ac11240f903CBa56aFF72A1425ae371000000000000000000000000',
messageBody: '0x1234',
metadata: '0x5678'
} as const;
// Convert to function args array with proper typing
const functionArgs: DispatchFunctionArgs = [
dispatchParams.destinationDomain,
dispatchParams.recipientAddress,
dispatchParams.messageBody,
dispatchParams.metadata
];
const provider = new ethers.JsonRpcProvider(process.env.RPC);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || "", provider);
const contract = new ethers.Contract("0x034f711BbdFC89f46a485dD2b5E12Af00Ee7AB2a", contractABI, wallet);
async function main() {
const contractAddress = await contract.getAddress();
// IMPORTANT: encode the private function that is invoked by the Predicate function
const data = packFunctionArgs("_dispatch(uint32,bytes32,bytes,bytes)", functionArgs);
const request: PredicateRequest = {
from: wallet.address,
to: contractAddress,
data: data,
msg_value: '0'
};
const evaluationResult = await predicateClient.evaluatePolicy(request);
console.log("Policy evaluation result:", evaluationResult);
if (!evaluationResult.is_compliant) {
console.error("Policy evaluation failed - transaction not compliant");
return;
}
const predicateMessage: PredicateMessage = signaturesToBytes(evaluationResult);
console.log("Submitting transaction with predicate message:", predicateMessage);
const tx = await contract.dispatch(
dispatchParams.destinationDomain,
dispatchParams.recipientAddress,
dispatchParams.messageBody,
dispatchParams.metadata,
[
predicateMessage.taskId,
predicateMessage.expireByBlockNumber,
predicateMessage.signerAddresses,
predicateMessage.signatures
]
);
const receipt = await tx.wait();
console.log("Transaction receipt:", receipt);
}
main().catch((error) => {
console.error("Error evaluating policy:", error);
process.exit(1);
});