Background
On CKB, intentions can be expressed in two ways:
- Using Intent Cell to carry assets and information, and pass them to applications.
- Using OTX to carry assets and information, and pass them to applications.
Each method has its advantages and disadvantages:
- Intent Cell requires two-step transactions to pass information, while OTX operates within a single batch. Thus, Intent Cell has longer delays and must handle failure results.
- Intent Cell requires additional CKB, whereas OTX does not.
- OTX requires users to switch to a new Lock, while Intent Cell does not.
- In multi-party applications, if different parties use their respective aggregators, Intent Cell can be combined asynchronously, while OTX can only be combined synchronously, or OTX and Intent Cell must be combined; otherwise, multiple parties must share one aggregator to achieve synchronous combination of OTX.
This solution adopts Type Script to carry intent for the following reasons:
- Type can analyze the transaction that created the intent cell, whereas lock cannot, which helps in implementing some permission management tasks.
- Type mandates that the transaction creation only places the hash pre-image in Witness, with script args containing only the hash of intent data, while lock must place all data in args. Since args occupy on-chain state, it complicates expressing complex intents and the varying sizes of different intents create substantial difficulties for application development and SDK integration.
DEMO: ckb-intent-scripts (github.com)
This scheme also utilizes two scripts, input-type-proxy-lock
and always-success
. For reference, please see: ckb-ecofund/ckb-proxy-locks (github.com).
Intent_type:
code_hash: Intent-type code_hash
hash_type: Intent-type hash_type
args: script_hash(20Bytes)|intent_data_hash(20Bytes)
Capacity: 32 + 1 + 40 = 73.
In args, script_hash represents the script that will process the intent, and intent_data_hash represents the hash of the corresponding intent data.
In this plan, apart from type, other cell fields are not used, the length of its data is not specified, and its lock is recommended to be an always_success_script with args length of 0. Thus, the total space for an intent_cell is 8 (capacity) + 33 (lock) + 73 (type) = 114 CKB.
The Intent-type can carry assets as intent input through the input_type_proxy_lock:
Intent-type-proxy-lock
code_hash: input_type_proxy_lock
hash_type: input_type_proxy_lock hash_type
args: Intent_type_hash
When creating an intent_cell, the user also transfers the asset cell needed for the intent to the corresponding proxy_lock, so the corresponding proxy_lock will be unlocked along with the intent_cell. The size of the input_proxy_lock is 65 CKB.
If there are requirements that Input needs to place more CKB as Capacity, such as if the output’s lock_script is too long and needs to reserve Capacity, it is advisable to place all CKB in the Intent Cell.
Moreover, the following structure-defined IntentData is placed at the specified Witness position:
import blockchain;
struct ScriptAttr {
location: byte,
script_hash: Byte32,
}
vector ScriptAttrVec <ScriptAttr>;
table AnotherIntent {
script_hash: Byte32,
intent_data: Bytes,
}
union IntentTarget {
Script,
AnotherIntent,
}
vector IntentTargetVec <IntentTarget>;
table IntentData {
location: byte,
owner: ScriptAttr,
expire_since: Uint64,
authorizer: ScriptAttrVec,
targets: IntentTargetVec,
input_data: Bytes,
}
The corresponding Rust code is as follows:
pub enum ScriptLocation {
#[default]
InputLock,
InputType,
OutputLock,
}
#[derive(Default, Debug)]
pub struct ScriptAttr {
pub loc: ScriptLocation,
pub script_hash: [u8; 32],
}
#[derive(Default, Debug)]
pub struct AnotherIntent {
pub script_hash: [u8; 32],
pub intent_data: IntentData,
}
#[derive(Debug)]
pub enum IntentTarget {
Script(packed::Script),
AnotherIntent(AnotherIntent),
}
impl Default for IntentTarget {
fn default() -> Self {
Self::Script(packed::Script::default())
}
}
#[derive(Default, Debug)]
pub struct IntentData {
pub location: ScriptLocation,
pub owner: ScriptAttr,
pub expire_since: u64, // After this since condition is met, the owner can unlock the intent type.
pub authorizers: Vec<ScriptAttr>,
pub targets: Vec<IntentTarget>,
pub input_data: Vec<u8>,
}
Example transaction:
Intent creation tx:
inputs:
asset_x cell
asset_y cell
capacity cell
output:
intent_cell with intent_type
asset_x cell with proxy_lock
asset_y cell with proxy_lock
witnesses:
intent_data
Checks when intent_cell is in output:
- The witness.output_type at the corresponding position has input_data, whose hash matches the intent_data_hash specified in args.
- Parses input_data,
verifies all targets, ensuring all AnotherIntent’s intent_data can be decoded into IntentData, and performs recursive parsing. - Ensures all authorizers in the intent_data exist in the transaction, whether in inputs or outputs, through lock or type.
Checks when intent_cell is in input:
- The witness.input_type at the corresponding position has input_data, whose hash matches the intent_data_hash specified in args.
- There exists a script in the transaction, whose script_hash satisfies the script_hash specified in type args, and its location aligns with the location specified in intent_data.
- After meeting the expire_since condition, the owner can unlock the intent_cell and retrieve all assets.
When the called Script verifies whether the intent is satisfied, it can use the following:
- Authorizer: All authorizers have already authorized this intent, so when a authorizer exists, it can be used for permission management.
- Target: Script can freely use the target; generally, if the Target is a Script, then the intent’s output will transfer to the corresponding lock; if the target is Another Intent, then the transaction’s output will be another Intent_cell controlled by proxy_lock.
- Input_data: Script can analyze and process its own business.
- Asset_cell: Script can scan the asset Cell accompanying the intent and process it accordingly.
The above checks are just suggestions for Script to verify if the intent is satisfied. For instance, when an another intent target cannot fully determine parameters during intent construction, Script can impose some limitations in the input_data and dynamically output to a constrained new Intent cell during aggregator transaction construction.
Through this model, one can achieve on CKB:
- A general function calling simulation mode.
- By outputting to another Intent, multi-layered function calls are realized. However, the whole process is entirely asynchronous, and even if subsequent calls fail, the previous call will not roll back. Through the Intent chain, any asynchronous calls can be implemented.
Here’s the translation of the technical document:
Intent Lock
In some cases, using Intent Lock can save space and simplify usage, especially when the intent doesn’t involve authentication and only carries one Cell.
Intent_lock:
code_hash: Intent-type code_hash
hash_type: Intent-type hash_type
args: receiver_script_hash(32Bytes)|receiver_location(1byte)|owner_script_hash(32)|owner_location(1byte)|expire_since(8byte)|input_data
The checks for intent_lock are as follows:
- There exists a script in the transaction whose script_hash matches the receiver_script_hash specified in the args, and its location complies with the location requirement.
- After the expire_since condition is met, the owner can unlock the intent_cell and retrieve all assets.
For the first cell carried by the intent, its lock is intent-lock, while the locks for other cells are input-lock-proxy-lock, which determines whether to unlock by checking if the intent-lock has been unlocked.
Intent-lock-proxy-lock
code_hash: input_lock_proxy_lock
hash_type: input_lock_proxy_lock hash_type
args: Intent_lock_hash