1
Update API requests
Replace
verification_hash with to (your contract address).2
Pass attestations to your contract
Include the attestation struct when calling predicated functions.
- EVM
- SVM
- Stellar
Backend Changes
Replaceverification_hash with your contract address in the to field.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.// 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.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.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]);
Backend Changes
Target your Soroban contract address and setchain to stellar.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: 'stellar',
}),
});
const result = await response.json();
if (!result.is_compliant) {
return res.status(403).json({ error: 'Not compliant' });
}
res.json({ attestation: result.attestation });
});
Client Changes
Pass the returned attestation into your Soroban contract invocation. Its fields map directly to the on-chainAttestation type.stellar contract invoke \
--id $CONTRACT_ID \
--source $IDENTITY \
--network public \
--send=yes \
-- \
deposit \
--from $USER_ADDRESS \
--amount 250 \
--attestation '{
"uuid": "<attestation.uuid>",
"expiration": <attestation.expiration>,
"attester": "<attestation.attester>",
"signature": "<attestation.signature>"
}'
Attestation Fields
| Field | Type | Description |
|---|---|---|
uuid | String | Unique identifier for replay protection |
expiration | u64 | Ledger timestamp when the attestation expires |
attester | BytesN<32> | Ed25519 public key of the Predicate attester |
signature | BytesN<64> | Ed25519 signature over the statement hash |
