Status: the sandbox runs today live. The SDK and API on this page are planned planned.
Docs
Enforce a ruling
Your contract checks the jurors' signatures itself. A ruling only counts on chain if it verifies there.
withdraw() → requestRuling(asc, action, jury) → jury votes → verifyRuling → allow: runs, else: revert
| Chain | Call | Status |
|---|---|---|
| Solana | verify_ruling(&asc, &action, &instructions) -> Outcome. The juror signatures ride in the same transaction (Ed25519 precompile). It fails if the ASC id, action hash, juror keys or quorum don't match. | planned |
| EVM | requestRuling(ascId, action, jury) emits an event; then verifyRuling(requestId, attestations) returns the outcome. Two steps, because a contract can't wait for models. | planned |
Solana
Your backend calls asc.requestRuling, then sends the payout with ruling.attestations in the same transaction. The program checks them.
withdraw.anchor.rsRustillustrative
// The on-chain side. The payout fails unless a quorum of listed jurors signed
// "allow" for this exact withdrawal under this exact ASC.
// Illustrative, Anchor-style. Not a deployed program.
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let action = Action::withdrawal(ctx.accounts.to.key(), amount);
// Juror signatures ride in this transaction (Ed25519 precompile).
// verify_ruling checks the ASC id, action hash, juror keys and quorum.
verify_ruling(&ctx.accounts.asc, &action, &ctx.accounts.instructions)?.require(Outcome::Allow)?;
// Hard caps still apply in code, whatever the jury said.
require!(amount <= ctx.accounts.asc.max_single, AscError::OverCap);
transfer(ctx, amount)
}
Full sample: Exchange withdrawal checks.
EVM
Step 1 asks the jury. Step 2 executes with the jurors' signed votes.
IAsc.solSolidityplanned
// SPDX-License-Identifier: MIT
// The on-chain side of an ASC on EVM chains. Planned interface, illustrative.
// EVM calls can't wait for models, so it's two steps: requestRuling, then verifyRuling.
pragma solidity ^0.8.24;
interface IAsc {
enum Tier { Fast, Standard, Frontier }
enum Outcome { None, Allow, Deny, Hold, Partial, Release, Stop }
struct Jury { uint8 size; uint8 quorum; Tier tier; }
/// Ask the jury to rule on `action` under the published ASC `ascId`.
/// Emits RulingRequested; the jury answers off chain with signed votes.
function requestRuling(bytes32 ascId, bytes calldata action, Jury calldata jury)
external returns (bytes32 requestId);
/// Check the jurors' signed votes for `requestId` and return the outcome
/// they reached. Reverts if signatures, juror keys, ASC id or quorum don't check out.
function verifyRuling(bytes32 requestId, bytes calldata attestations)
external view returns (Outcome);
event RulingRequested(bytes32 indexed requestId, bytes32 indexed ascId, bytes32 actionHash, Jury jury);
}
ConstitutionGuard.solSolidityillustrative
// SPDX-License-Identifier: MIT
// A Governor that won't execute a passed proposal until the constitution's jury allows it.
// Two steps, because an EVM call can't wait for models: request, then execute with signatures.
// Illustrative. Not audited or deployed.
pragma solidity ^0.8.24;
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {IAsc} from "@verafy/asc-contracts/IAsc.sol";
abstract contract ConstitutionGuard is Governor {
IAsc public immutable asc;
bytes32 public immutable CONSTITUTION; // ASC id of constitution.asc.md
mapping(uint256 => bytes32) public rulingOf; // proposalId => ruling request id
constructor(IAsc _asc, bytes32 constitution) { asc = _asc; CONSTITUTION = constitution; }
/// Step 1: anyone can ask the jury once a proposal has passed.
function requestRuling(uint256 proposalId) external {
require(state(proposalId) == ProposalState.Succeeded, "not passed");
rulingOf[proposalId] = asc.requestRuling(
CONSTITUTION,
abi.encode(proposalId, block.chainid),
IAsc.Jury({size: 7, quorum: 5, tier: IAsc.Tier.Frontier})
);
}
/// Step 2: execution carries the jurors' signed rulings.
function executeWithRuling(
address[] memory targets, uint256[] memory values, bytes[] memory calldatas,
bytes32 descriptionHash, bytes calldata attestations
) external payable returns (uint256 id) {
id = hashProposal(targets, values, calldatas, descriptionHash);
require(asc.verifyRuling(rulingOf[id], attestations) == IAsc.Outcome.Allow, "constitution: blocked");
return execute(targets, values, calldatas, descriptionHash);
}
// Plain execute() is closed, so nothing skips the check.
function _executeOperations(uint256 id, address[] memory t, uint256[] memory v, bytes[] memory c, bytes32 d)
internal override
{
require(rulingOf[id] != bytes32(0), "constitution: request a ruling first");
super._executeOperations(id, t, v, c, d);
}
}
Full sample: A DAO constitution that can block a vote.
Keep hard caps in your code
Hard caps stay in the contract's own code. Never let a ruling raise a cap. A ruling only chooses among the outcomes your ASC lists, and the cap check runs whatever the jury said.