Skip to main content
When a user wants to settle their funds onchain via a batch swap, they submit an intent to the operator. Arkade intents are valid-but-unmineable Bitcoin transactions that encode an ownership proof of the inputs a user wants to redeem - whether it’s offchain VTXOs, an onchain UTXO or recoverable coins (eg. expired coins or in form of an Arkade Note) - and define the outputs they wish to receive. Arkade intents are based on BIP322, a standardized Bitcoin message signing protocol. The system was introduced with the v0.7.0 arkd release.
The usage of BIP322 enables users to choose between renewing their expiring offchain funds themselves or delegating the renewal of such VTXOs. The delegated intent workflow can be found under Intent Delegation

Intent Structure

This section defines the intent schema that expresses what is being spent and where it goes. It covers the top-level fields, the Receiver format (onchain address vs. offchain pubkey), and the IntentMessage that captures execution parameters and the intent’s validity window.
The Intent domain is a user-submitted presigned Bitcoin transaction that:
  • Contains an IntentId(UUID)
  • Specifies the Inputs(VTXOs, UTXOs, or expired coins)
  • Specifies the Outputs(via Receivers). Either:
    • onchain (via OnchainAddress)
    • offchain (via PubKeyto create new VTXOs)
  • Contains a Proofof ownership of those funds via a bip322signature
  • Contains a Message (bip322 message) with intent details
type Intent struct {
    Id        string
    Inputs    []Vtxo
    Receivers []Receiver
    Proof     string
    Message   string
}
Receivers is defined via an Amount, an OnchainAddressand a PubKey(of which at least one must be present during Intent submission. Those are used to construct Arkade transaction outputs or direct onchain payments. 
type Receiver struct {
    Amount         uint64
    OnchainAddress string // onchain
    PubKey         string // offchain
}
IntentMessage contains intent details in JSON structure:
  • The Typeindicates if the intent is for renewal or ownership proof to delete another
  • InputTapTreesis the revealed Taproot tree of all inputs of the intent. Revelation occurs like witness data in Bitcoin
  • OnchainOutputIndexes: Indicates which outputs become UTXOs; others are VTXOs
  • ValidAt: Time (seconds) when the intent becomes valid; 0 = valid right away
  • ExpireAt: Time (seconds) when the intent expires; 0 = no expiry
  • CosignersPublicKeys: Public keys signing the VTXO tree; typically one, but can be more to support flexible user needs
type IntentMessage struct {
    BaseIntentMessage
    // InputTapTrees is the list of taproot trees associated with the spent inputs
    // the index of the taproot tree in the list corresponds to the index of the input + 1
    // (we ignore the first bip322 input, as it is duplicate of the second one)
    InputTapTrees []string `json:"input_tap_trees"`
    // OnchainOutputIndexes specifies what are the outputs in the proof tx
    // that should be considered as onchain by the Ark operator
    OnchainOutputIndexes []int `json:"onchain_output_indexes"`
    // ValidAt is the timestamp (in seconds) at which the proof should be considered valid
    // if set to 0, the proof will be considered valid indefinitely or until ExpireAt is reached
    ValidAt int64 `json:"valid_at"`
    // ExpireAt is the timestamp (in seconds) at which the proof should be considered invalid
    // if set to 0, the proof will be considered valid indefinitely
    ExpireAt int64 `json:"expire_at"`
    // CosignersPublicKeys contains the public keys of the cosigners
    // if the outputs are not registered in the proof or all the outputs are onchain, this field is not required
    // it is required only if one of the outputs is offchain
    CosignersPublicKeys []string `json:"cosigners_public_keys"`
}

Intent Lifecycle 

The Arkade event stream (GetEventStream) is a server-side streaming RPC method in the ArkService that provides real-time batch processing coordination events to clients including batch start, finalization, and failure notifications. The server uses this stream to indicate the next required action and corresponding API call. The full Intent lifecycle is as follows:
1

Create a BIP322 Signature

Intents use BIP322 message signing protocol for proving ownership of coins.
 export function create(
  message: string,
  inputs: TransactionInput[],
  outputs: TransactionOutput[] = []
): FullProof {
  if (inputs.length == 0) throw ErrMissingInputs;
  if (!validateInputs(inputs)) throw ErrMissingData;
  if (!validateOutputs(outputs)) throw ErrMissingData;

  // create the initial transaction to spend
  const toSpend = craftToSpendTx(message, inputs[0].witnessUtxo.script);

  // create the transaction to sign
  return craftToSignTx(toSpend, inputs, outputs);
}
Source: TS-SDK
The create function takes a string message corresponding to the associated action described below (“Register” or “Delete”).
2

Register an Intent

The core of intent registration is the RegisterIntentRequest which contains a Bip322Signaturefield and an intent message:
message RegisterIntentRequest {
// BIP322 signature embeds the outpoints to be spent and new ones to be created, as well as the
// the proof of funds.
Bip322Signature intent = 1;
}
message RegisterIntentResponse {
string intent_id = 1;
}
The intent message structure includes:
  • InputTapTrees - Taproot trees for spent inputs
  • OnchainOutputIndexes - Which outputs should be considered onchain
  • ValidAt/ExpireAt - Timestamp validity windows
  • CosignersPublicKeys - Required for offchain outputs
Messages are JSON string encoded with a static format, making the order of the fields relevant
   const message = {
       type: "register",
       input_tap_trees: inputTapTrees,
       onchain_output_indexes: onchainOutputsIndexes,
       valid_at: nowSeconds,
       expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
       cosigners_public_keys: cosignerPubKeys,
   };
Source: Typescript SDK
The server then responds with a RegisterIntentResponsecontaining an intent_idstring for tracking.
3

Confirm Registration

After receiving a BatchStartedEventcontaining their intent ID hash, clients must call ConfirmRegistrationwith a ConfirmRegistrationRequestcontaining the intent_idto confirm participation. The server responds with an empty ConfirmRegistrationResponse.
4

Delete Intent

The DeleteIntentmethod accepts a DeleteIntentRequestwith a Bip322Signatureproof that demonstrates ownership of any input VTXOs from the original intent. The server responds with an empty DeleteIntentResponseupon successful deletion.
const message = {
  type: "delete",
  expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
}
Source: Typescript SDK

Recovery Mechanisms

The intent system provides additional options for edge cases like recoverable VTXOs:
I