Proofs

A Sepolia PortfolioSignal is an observation. Affest does not rebalance from a log, a database row, or an MCP reply.

The event

PortfolioSignalEmitter.emitSignal on Sepolia emits:

event PortfolioSignal(
  bytes32 indexed signalId,
  address indexed user,
  address indexed asset,
  uint256 amount,
  uint8 signalType
);

The source contract rejects zero assets, zero amounts, and duplicate signal IDs. The worker discovers the log and reads the source receipt before asking Attestcoin for a proof. Sepolia is Attestcoin chain key 1.

Proof lifecycle

  1. The Sepolia source port finds the log and confirms receipt status 0x1.
  2. The attestation port checks the official attested-height endpoint.
  3. Once the block is attested, @gluwa/usc-sdk@0.18.0 builds a proof.
  4. TypeScript validates the proof-builder shape: txBytes, continuity proof, and Merkle proof.
  5. The executor supplies that proof to AffestAttestationVerifier.
  6. The verifier calls INativeQueryVerifier.verifyAndEmit on the BlockProver precompile at 0x0000000000000000000000000000000000000FD2, decodes the returned transaction, and requires receipt status 1.
  7. It then checks emitter, event signature, log index, user, asset, minimum amount, and signal type.

Only after those checks can AffestStrategyManager record an execution against vault limits.

Replay

The verifier derives a query ID from (chainKey, blockHeight, txIndex) and a processed-event key from (queryId, receiptLogIndex). A second submission of the same source receipt log reverts with EventAlreadyProcessed.

What you will see

Rebalance now on the strategy page does not discretionary-swap. It tells you to wait for a verified proof. Pause and resume are on-chain and do not need a proof. Proof status on the strategy page tracks the source observation, not a fake fill.

Deployed emitter and verifier addresses are on Chains and contracts.