conceptualv0.1 · skeleton docs
HookReceipt
What comes back, including when things go wrong.
Schema
hookreceipt.tsconceptual
type HookReceipt = {
callId: string;
status: "success" | "failed" | "partial";
destinationTx?: string; // EVM tx that executed (or rejected) the call
outputHash?: string; // H(hook output); absent when nothing was produced
amountSpent?: string; // decimal string, hook's spend currency
timestamp: number; // unix seconds, destination block time
};Status semantics
| Status | Meaning | Spend |
|---|---|---|
success | The action completed. outputHash commits to the result. | Actual amount used, ≤ maxSpend |
partial | Some sub-operations succeeded (e.g. 2 of 3 swarm jobs accepted). | Only for the accepted parts |
failed | The hook reverted, or the gateway refused the call after delivery (e.g. expired). | 0 |
A failed receipt is still a receipt. It is how the gateway tells Solana that nothing happened, so the app can refund, retry with a new call, or surface the error.
When there is no receipt
Two cases produce no receipt for a given delivery:
- Duplicate delivery (
WH-302). The first delivery already produced the receipt for that call ID, and a second receipt could overwrite it. - Not delivered yet (
WH-202). The call is still in transit. It will end in a receipt (success, failure, or expiry).
Verifying a receipt
On Solana, the router only records receipts carried by a VAA from the registered gateway emitter on the call's destination chain, for a call ID it has pending. Your program reads the receipt account; it does not need to verify signatures itself.
receipt.rsconceptual
// Reading a recorded receipt from your program (account layout is phase 2).
let receipt = HookReceiptAccount::load(&ctx.accounts.receipt)?;
require!(receipt.call_id == expected_call_id, MyError::WrongCall);
match receipt.status {
Status::Success => settle(&receipt.output_hash, receipt.amount_spent)?,
Status::Partial => settle_partial(&receipt)?,
Status::Failed => refund(ctx)?,
}See Returning receipts for the return path.