arkadec turns that source into a JSON artifact with two kinds of script. Arkade Script covenants run in the Emulator, and tapscript leaves run in Bitcoin, for example the unilateral exit.
The playground runs the compiler in the browser. You do not install anything.
A first contract
Calculator is an example, not a real vault. It has no output constraint and no exit leaf. Compile it to read the output. Do not fund its address.
Calculator.ark
a and b, which you fix when you instantiate the contract. Each function is one way to spend the output, and result is a witness argument from the spender. The spend succeeds only if each require passes.
A function without a modifier is a covenant: it compiles to Arkade Script, and the Emulator runs that script before it signs.
<a> and <b> are placeholders. Instantiation replaces them with the constructor arguments:
Calculator.asm
OP_MUL and OP_DIV. In Arkade Script, numbers are BigNums of up to 520 bytes, while Bitcoin Script operands have at most 4 bytes. OP_DIV fails the spend if b is 0.
int also has %, <<, and >>. Covenants also have abs, min, max, within, and modExp.
Reading the transaction
A covenant can read the transaction that spends it, which Bitcoin Script cannot do. Abytes32 payout is the 32-byte witness program of the destination, not the full scriptPubKey. Payout sends the whole output to one script and nothing else:
Payout.ark
require is one line:
Payout.asm
OP_INSPECTOUTPUTSCRIPTPUBKEY followed by OP_DROP. That opcode pushes the witness program and the segwit version, and OP_DROP removes the version.
A covenant can also read the transaction (tx.version, tx.locktime, tx.weight, tx.id), any input (tx.inputs), Arkade assets (assets.lookup, tx.assetGroups), and packets (tx.packet).
A tapscript function cannot read the transaction. tx.locktime is the nLockTime of the transaction, not a clock, so for time, use checkTime, which compares a timestamp with the Unix time of the Emulator.
Spend paths
The address of a contract is a Taproot output. Each function becomes a leaf of its script tree. The form of the function chooses the leaf:
The synthesized leaf of a covenant
f needs the signature of the operator and the signature of the tweaked Emulator key:
tapscript functions with its name form one spend group. A spend group is one entry of functions in the artifact. <EMULATOR_KEY:f> is the Emulator key tweaked with the script of the covenant (Key tweak).
The tapscript modifier
tapscript marks a function as Bitcoin Script. Bitcoin checks it, and the Emulator does not take part. Use it for the unilateral exit: a CSV delay, then the signature of the owner.
A tapscript body has require statements in a fixed order. The order is an optional hash condition, an optional timelock (older for CSV, after for CLTV), and then exactly one checkSig or checkMultisig. arkd accepts only N-of-N leaves.
OwnedPayout adds a signature of the owner and an exit leaf to Payout. The exit leaf waits for serverExitDelay, the exit delay of the operator (CSV delays).
OwnedPayout.ark
pay appears twice. The covenant limits the output, and the tapscript with the same name replaces the synthesized leaf, so pay now needs the signature of the owner and the signatures of the operator and the Emulator.
unilateral has no covenant with the same name, so it is a standalone leaf and the Emulator does not take part.
Reserved names
The virtual output model needs three names for Arkade infrastructure, and the compiler reserves them. A constructor parameter or a tapscript input with one of these names fails withcollides with a reserved arkd name.
The compiler accepts a contract with no exit leaf, and it does not warn.
Artifact
The compiler makes the artifact, a JSON file. It has what a client needs to find the addresses of a contract and to build its spends. The client does not need the compiler or the source. The artifact does two jobs. Like a Solidity ABI, it lists the constructor inputs and the arguments of each function. Like an output descriptor, it holds script templates. A wallet fills the templates with the constructor arguments. This gives the address and the Taproot tree. A wallet depends on the artifact, not onarkadec. The SDK function programFromArtifact loads it (Create a Vault).
savings_vault.json is the artifact of the savings vault. The block omits source and updatedAt:
savings_vault.json
constructorInputs lists the constructor parameters in declaration order. Each entry in functions is one spend group. arkade is the Arkade Script of a covenant, as a template. Each leaf has an asm template and a witness list. The list has the values that the spender supplies.
<SERVER_EXIT_DELAY> in the exit leaf is not a constructor input. The client binds it (Reserved names).
A witness item with injected set to true is a signature that infrastructure supplies: the operator or the Emulator. The wallet does not supply it.
fingerprint is the SHA-256 of the artifact without fingerprint and updatedAt. It identifies the content, but it does not prove where the artifact came from, and updatedAt changes at each compile.
Keep the artifact that you used for your addresses. Clients build the Taproot tree in the order of the artifact. A different leaf order can change the output key and the address. Recompile a source bundle only with the same compiler version. The compiler README describes each field.
Install and run
You need Rust 1.85 or newer.-o. Without it, arkadec writes <stem>.json in the current directory. arkadec prints warnings to stderr and exits with status 0. It exits with a non-zero status on a type or validation error. --no-optimize skips constant folding, static branch removal, and assembly optimization. arkade-bindgen makes typed TypeScript or Go stubs from an artifact.
Placeholders
Write an asset id in a constructor argument in reverse byte order: it is the reverse of the display order. The language also has arrays, structs, loops, Arkade assets, and
new for recursive contract instantiation. The compiler README is the reference. The examples/ folder has complete contracts.