Smart Triggers
Verifiable events initiating Smart Workflow execution
Triggers can be blockchain events (contract logs, transaction states), API requests (workflow_dispatch), or time-based schedules (cron). Each type has different observation and verification patterns suited to its source.
Examples
Configuration
Trigger workflow manually via HTTP API request. Useful for on-demand execution, manual operations, or external system integrations.
Workflow Configuration
on:
workflow_dispatch:Trigger
HTTP POST request received at /workflows/ping/dispatches.
HTTP API Request
POST /workflows/0x4f3a7c9e2b5d8a1c4e7f0b3d6a9c2e5f8b1d4a7c0e3b6d9f2a5c8e1b4d7a0c3/dispatches
{
"authority_signature": "0x4a7b9c3d8e2f1a5c6d9e3b7f2a5d8c1e4b7a9d3c6e2f5a8b1d4c7e9f3a6b2d5c",
"authority_pubkey": "0x892b3c5e8d1f4a7c9e2b6d8a3f5c7e9b4d6a8c1e3b5d7a9c2e4f6b8d1a3c5e7",
"payer_signature": "0x2f8a9d3c6e5b1f4a7c0e2b9d5a8c1e4f7b0a3d6c9e2f5a8b1d4c7e0f3a6b9d2",
"payer_pubkey": "0x6d9a2c5e8f1b4d7a0c3e6b9d2f5a8c1e4b7d0a3c6e9f2b5d8a1c4e7f0b3d6a9",
"timestamp": 1730485532000,
"nonce": "0x7c3e9f2a5d8b1c4e7f0a3d6b9c2e5f8a1b4d7a0c3e6f9b2d5a8c1e4f7b0d3a6"
}Workflow Hash:0x4f3a7c9e2b5d8a1c4e7f0b3d6a9c2e5f8b1d4a7c0e3b6d9f2a5c8e1b4d7a0c3Name:pingAuthority:0x892b3c5e8d1f4a7c9e2b6d8a3f5c7e9b4d6a8c1e3b5d7a9c2e4f6b8d1a3c5e7Payer:0x6d9a2c5e8f1b4d7a0c3e6b9d2f5a8c1e4b7d0a3c6e9f2b5d8a1c4e7f0b3d6a9Timestamp:1730485532000Nonce:0x7c3e9f2a5d8b1c4e7f0a3d6b9c2e5f8a1b4d7a0c3e6f9b2d5a8c1e4f7b0d3a6
Observation
Validator verifies authority and payer, creates trigger attestation.
Detection
Observation Code
// Validator's HTTP RPC handler
rpc_server.post("/workflows/:workflow_hash/dispatches", (req) => {
const workflow_hash = req.params.workflow_hash; // Full 32-byte hash
const {
authority_signature, authority_pubkey,
payer_signature, payer_pubkey,
timestamp, nonce
} = req.body;
const workflow_config = get_workflow_config(workflow_hash);
const now = Date.now();
// Check timestamp is within acceptable range
const MIN_TIMESTAMP = now - 5 * 60 * 1000; // 5 min past
const MAX_TIMESTAMP = now + 24 * 60 * 60 * 1000; // 24 hrs future
if (timestamp < MIN_TIMESTAMP || timestamp > MAX_TIMESTAMP) {
return error("Timestamp out of range");
}
// Verify authority signature (permission)
const auth_message = hash(workflow_hash + timestamp + nonce);
if (!verify(auth_message, authority_signature, authority_pubkey)) {
return error("Invalid authority signature");
}
// Check authority is authorized
if (!workflow_config.authorized_keys.includes(authority_pubkey)) {
return error("Authority not authorized");
}
// Verify payer signature (payment commitment)
const payer_message = hash(workflow_hash + timestamp + nonce + authority_pubkey);
if (!verify(payer_message, payer_signature, payer_pubkey)) {
return error("Invalid payer signature");
}
// Check payer has sufficient balance (or deduct fees)
if (!has_sufficient_balance(payer_pubkey, workflow_config.fee)) {
return error("Insufficient payer balance");
}
// Create trigger with authority and payer proof
const trigger = {
workflow_hash,
topic: "trigger.workflow_dispatch",
data: {
workflow_hash,
timestamp,
nonce,
authority_signature,
authority_pubkey,
payer_signature,
payer_pubkey
}
};
// Validator signs and broadcasts
broadcast(sign_message({
message_type: "trigger",
timestamp_ms: now,
payload: trigger
}));
});Attestation
Trigger Attestation
{
"content": {
"message_type": "trigger",
"timestamp_ms": 1730485532000,
"payload": {
"workflow_hash": "0x4f3a7c9e2b5d8a1c4e7f0b3d6a9c2e5f8b1d4a7c0e3b6d9f2a5c8e1b4d7a0c3",
"topic": "trigger.workflow_dispatch",
"data": {
"workflow_hash": "0x4f3a7c9e2b5d8a1c4e7f0b3d6a9c2e5f8b1d4a7c0e3b6d9f2a5c8e1b4d7a0c3",
"timestamp": 1730485532000,
"nonce": "0x7c3e9f2a5d8b1c4e7f0a3d6b9c2e5f8a1b4d7a0c3e6f9b2d5a8c1e4f7b0d3a6",
"authority_signature": "0x4a7b9c3d8e2f1a5c6d9e3b7f2a5d8c1e4b7a9d3c6e2f5a8b1d4c7e9f3a6b2d5c",
"authority_pubkey": "0x892b3c5e8d1f4a7c9e2b6d8a3f5c7e9b4d6a8c1e3b5d7a9c2e4f6b8d1a3c5e7",
"payer_signature": "0x2f8a9d3c6e5b1f4a7c0e2b9d5a8c1e4f7b0a3d6c9e2f5a8b1d4c7e0f3a6b9d2",
"payer_pubkey": "0x6d9a2c5e8f1b4d7a0c3e6b9d2f5a8c1e4b7d0a3c6e9f2b5d8a1c4e7f0b3d6a9"
}
}
},
"signature": "0x8a3f9d7c2e5b1f4a6d8c3e9b7a5f2d4c8e1b6a9d3c7e2f5a8b4d1c6e9f3a7b2d",
"signer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2"
}Verification
Validators verify authority and payer authorization, all three signatures valid.
Verification Protocol
1. Check Topic
topic == "trigger.workflow_dispatch"
2. Check Workflow Exists
workflow_exists(workflow_name)
3. Verify Timestamp Range
now - 5min < timestamp < now + 24hrs
4. Verify Authority Signature
verify(hash(workflow_hash || timestamp || nonce), authority_signature, authority_pubkey)
5. Check Authority Authorized
workflow.authorized_keys.contains(authority_pubkey)
6. Verify Payer Signature
verify(hash(workflow_hash || timestamp || nonce || authority_pubkey), payer_signature, payer_pubkey)
7. Check Payer Has Balance
has_sufficient_balance(payer_pubkey, workflow.fee)
8. Verify Validator Signature
verify(message.content.hash(), signature, signer)
Challenge & Fraud Proof
// Challenge if authority unauthorized or signatures invalid
fn verify_and_challenge(
signed_message: SignedMessage
) -> Result<()> {
let trigger = signed_message.content.payload;
let data = trigger.data;
// Check topic first (cheap string comparison)
if trigger.topic != "trigger.workflow_dispatch" {
return submit_fraud_proof(InvalidTopic);
}
// Check workflow exists (cheap hashmap lookup)
let workflow = get_workflow(&trigger.workflow_hash)
.ok_or(submit_fraud_proof(WorkflowNotFound))?;
// Check timestamp is within range (cheap comparison)
let now = current_timestamp_ms();
let min_ts = now - 5 * 60 * 1000; // 5 min past
let max_ts = now + 24 * 60 * 60 * 1000; // 24 hrs future
if data.timestamp < min_ts || data.timestamp > max_ts {
return submit_fraud_proof(TimestampOutOfRange);
}
// Verify authority signature (expensive crypto)
let auth_message = hash(&trigger.workflow_hash, &data.timestamp, &data.nonce);
if !verify(
&auth_message,
&data.authority_signature,
&data.authority_pubkey
) {
return submit_fraud_proof(InvalidAuthoritySignature);
}
// Check authority authorized (cheap array lookup)
if !workflow.authorized_keys.contains(&data.authority_pubkey) {
return submit_fraud_proof(UnauthorizedAuthority);
}
// Verify payer signature (expensive crypto)
let payer_message = hash(
&trigger.workflow_hash,
&data.timestamp,
&data.nonce,
&data.authority_pubkey
);
if !verify(
&payer_message,
&data.payer_signature,
&data.payer_pubkey
) {
return submit_fraud_proof(InvalidPayerSignature);
}
// Check payer has balance (cheap balance lookup)
if !has_sufficient_balance(&data.payer_pubkey, workflow.fee) {
return submit_fraud_proof(InsufficientPayerBalance);
}
// Verify validator signature last (expensive crypto)
if !verify_signature(
signed_message.content.hash(),
&signed_message.signature,
&signed_message.signer
) {
return submit_fraud_proof(InvalidValidatorSignature);
}
Ok(())
}Consensus
The network reaches agreement that the trigger is valid.
validator_1→✓ ATTESTED
validator_2→✓ ATTESTED
validator_3→✓ ATTESTED
validator_4→✓ ATTESTED
validator_5→○ OFFLINE
validator_6→✗ CHALLENGED→ SLASHED
✓ Quorum Reached
4/6 validators attested correctly
Threshold: ≥4 required (2/3 of 6)
→ Trigger accepted, workflow begins
✓Honest attestors (4): Proceed with workflow execution
○Offline (1): No response within timeout, no penalty
✗Challenged (1): Fraud proof submitted, challenger rewarded, attestor slashed