Skip to main content
AttestationBeacon is the oracle output contract. Copy it below. Asset as Contract ID explains the asset. A sample is a price and a time that the signers sign. A fixing is the price that the output stores in a slot. The slot key names the slot. The binding on this page uses one key. Multi-Oracle Aggregation is the same contract with more signers, and the consumers that take a median.

Source

Copy the contract without changes. A change to the code changes the address of the output. Compile the file with arkadec (Compiler). Constructor lists the arguments that you bind.
contracts/attestation_beacon.ark @ 9fc10ef
The file is attestation_beacon.ark in arkade-options 9fc10ef.

Constructor

The ticker is not a parameter. The contract fixes it as BTCUSD (0x425443555344).

Binding

The SDK flattens arrays. The constructor arguments are signers.0 to signers.4. The call arguments are sigs.0 to sigs.4. The artifact also declares server. The SDK client binds server to the operator key.

State packet

The packet has type 32 and 329 bytes. Integers use num2bin: little-endian sign-magnitude. The genesis state is round 0 with eight zero slots. attest stores the price in the value as a 32-byte number. A consumer reads the first 8 bytes of the value to get the price.

Functions

attest spends the output at input 0. The sample digest is the SHA-256 hash of the asset id, the ticker, the price (8 bytes), and the time (8 bytes). Slot i of sigs belongs to signer i. A slot below signersN holds a valid signature or an empty signature. Any other value fails the spend. The spend needs at least threshold valid signatures. The next packet has the round plus 1. Slot 0 holds the slot key and the price. Slots 1 to 7 hold the old slots 0 to 6. The slot key must not be in a current slot. read keeps the packet of the output the same. It checks that output 0 gains readFee. It has no signature check. Output 0 carries the oracle output’s own script, because it is the oracle’s continuation. A contract that also continues on output 0, like the pool templates (Lending, Automated Market Maker), cannot spend in the same transaction as a read: one output 0 cannot carry two scripts. A vault that settles without continuing can share the transaction and pays readFee from its locked funds. Any other reader spends the oracle in a separate transaction. migrate spends the output at input 0. adminPk signs the SHA-256 hash of the domain, the text migrate, the asset id, the group index (4 bytes), the round (8 bytes), and next. The round comes from the current packet. Output 0 pays the 32-byte program next with the unit and the same packet. unilateral is the exit if the operator is unavailable. It is a tapscript leaf that needs the CSV exit and a signature from adminPk. The emulator runs each function except unilateral.

Set up one key

One key means one signer (signersN), a threshold of 1, and an admin key (adminPk) that equals the signer key. The contract checks only the first signersN signer slots. Repeat the signer key in the other four slots. Build exit as a BIP68 sequence (CSV sequences). Use the issuance txid in reverse byte order for ctrlTxid (Asset as Contract ID).
oracle.ts
To deploy, spend the issued output. Output 0 pays to current. It holds the unit and a genesis state packet (round 0, eight zero slots). The function genesisOutputs in protocol/beacon.ts builds the outputs. The wallet submits them as an offchain transaction. The deployed oracle uses keyLag 60. The emulator clock must be 60 seconds past the slot key before attest writes it. A negative keyLag removes that clock gate: attest then checks only that the sample time is not later than the slot key plus keyLag, so a fixing can land at any time. Deploy a negative lag only when the signers’ own process is the liveness guarantee, because the emulator no longer stops an early fixing. The deployed oracle uses readFee 1000, which is more than the dust limit of 330 sats.

The output

Each cooperative spend creates the output again on output 0 with its identity unit. Only migrate changes the script.

Publish a price

Anyone who has the signatures of the quorum can write a sample into the output. With one key, the quorum is one signature. The output is the only input. It returns on output 0 with the new state:
The signer signs a digest that contains the asset of the oracle. num2bin writes a number as little-endian sign-magnitude bytes. The top bit of the last byte must stay clear.
protocol/beacon.ts @ 9fc10ef
The signer signs the digest with a BIP340 Schnorr signature:
The contract checks only that the slot key is greater than 1800. The consumer decides what the slot key means. OptionVault uses its expiry time in unix seconds. attest verifies the signature, the clock, and the change of state. The excerpt omits the check of the signer set and the checks of the packet size and version:
contracts/attestation_beacon.ark @ 9fc10ef
With one signer, the loop verifies the first signature against the first signer key. It ignores the other four slots. The contract limits time from above only, and the slot does not store time. See Limits. The function buildAttest builds the transaction that oracle/service.ts sends:
protocol/cospend.ts @ 9fc10ef

Consume the price

A consumer spends the oracle output as an input of its own transaction. read has no signature check, so anyone can build this transaction. In OptionVault, the consumer is input 0 and the oracle output is input 1. read takes the index of its own input. The payouts come from the output of the consumer, minus readFee:
read keeps the state and charges the fee:
contracts/attestation_beacon.ark @ 9fc10ef
The consumer finds the oracle with the asset id. It reads the state packet of input 1. It takes the price from the slot whose key is its expiry:
contracts/option_vault.ark @ 9fc10ef
The function buildSettle builds the settlement transaction. The builder of the SDK spends one covenant output. cospend.ts assembles two:
protocol/cospend.ts @ 9fc10ef

Replace the key

The admin replaces the script of the output, for example with a script that has a new signer key. The contracts that read the output do not change, because they look for the same asset id.
migrate takes the 32-byte program of the new script and a signature from adminPk:
contracts/attestation_beacon.ark @ 9fc10ef
To replace the signer key, do these steps:
  1. Bind the new script with the same arguments and the new key in signers. The 32-byte program of this script is next.
  2. Read round from bytes 1 to 8 of the state packet of the output.
  3. Compute the digest msg as the function migrate computes it. Sign the digest with the key of adminPk. The signature is opSig.
  4. Spend the output at input 0 with the function migrate. Pass next and opSig.
  5. Pay output 0 to the new script with the unit and at least the input value. Keep the state packet the same.
arkade-options has no builder for migrate and no test that spends it. Steps 2 to 5 follow the contract. Use buildAttest as a template for the transaction.

Exit

contracts/attestation_beacon.ark @ 9fc10ef
If the operator is unavailable, adminPk alone can claim the output onchain: unroll it, wait for the exit delay, and sign. The unit then leaves Arkade, and contracts that read this oracle can no longer settle against it.

Limits

Source files at 9fc10ef: attestation_beacon.ark, option_vault.ark, protocol/beacon.ts, protocol/cospend.ts, and the reference service oracle/service.ts.