Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Please read the introduction of Hooks in this blog.
While working on Hooks we published a number of blogs on our progress, insights & Hooks concepts. You can read all about that in our blogs on Dev.to
Guards are needed to perform loops in a Hook.
Hooks are deliberately not Turing Complete. This means arbitrary looping is forbidden. Instead you must guard your loops against a hard "maximum iteration" boundary.
A guard is a marker placed in your code at the top of each loop. The marker informs the Xahau what the upper bound of your loop will be in every possible scenario. Thus if your loop usually executes twice but sometimes executes 500 times, then your guard will say 500.
Guards are used by the Xahau to determine the worst case execution time (in instructions) of your Hook before execution. This is the basis for the fee the Xahau charges for the execution of a Hook and makes execution times predictable and controllable.
🚧 Tip
Existing developers migrating from other smart contract platforms may find guards to be annoying at first but once you get used to them they are no harder to use than a normal for-loop.
The guard function tells the ledger the maximum number of iterations a loop will make. Specifically the function takes two arguments:
The first argument id
is the identifier for this guard. This is a unique constant chosen by the developer, typically the line number in the source file is used.
The second argument maxiter
is a promise the developer makes to the ledger that this guard will not be hit more than maxiter
times during the execution of the Hook. If the guard call is executed more than this many times the Hook will automatically rollback with a GUARD_VIOLATION
(Hook API return codes). Because the guard will be hit before the loop condition is checked, it is important to add one to the total number of expected iterations. (Note: The GUARD() macro already adds one).
❗️ Pay Attention
Guards must be set using numerical literals. You cannot use a variable or runtime value in a Guard.
Consider the following for-loop in C:
In C, the comma operator executes each expression in a list of expressions (e.g. A, B, C
) and returns the last expression (e.g. C
). Thus the condition above is still i < 3
, but the guard is called before the condition is checked. This is the only way to satisify the guard rule when using a for-loop in C.
👍 The Guard Rule
A call to
_g
(the guard function) is must be the first branch instruction after a loop instruction.
Below appears the webassembly output when the above is compiled. Note the guard function being called at the start of the loop. The only instructions allowed before this call are non-branch instructions (typically manipulating constants.)
When using nested loops the maxiter
argument must reflect the total number of times the guard will be hit. This means you must multiply the nestings together.
Consider the example below:
Notice the inner-loop's guard is set to 15. You must multiply the loops together to compute the maximum number of times an inner guard will be hit during Hook execution.
Calls to non-Hook API functions are disallowed in the Hooks ammendment. All user code must fit within the two allowed Hook functions cbak
and hook
.
❗️ Warning
Failure to use guards correctly will cause an attempted
SetHook
transaction to be rejected.
Hooks add smart contract functionality to Xahau: 'layer one' custom code to influence the behaviour and flow of transactions. Hooks are small, efficient pieces of code being defined on an Xahau account, allowing logic to be executed before and/or after Xahau transactions.
Xahau is known and is being appreciated for its transaction throughput, speed and the low fees. Combined with available advanced transaction types like multi sign, escrows, payment channels and even a decentralized exchange (all on ledger, out of the box, without requiring smart contracts) the Xahau has a lot to offer businesses and (creative) developers.
Welcome to Hooks 👋
Hooks are small, efficient web assembly modules designed specifically for Xahau. Hooks can be written in any language (compilable to WebAssembly) and most business logic and most smart contract concepts can be implemented in a hook. Typically Hooks are written in C.
Hooks are set onto an Xahau account using a SetHook
transaction. Once installed on an account, a hook can:
Block or allow incoming and outgoing transactions on the account,
Modify and maintain internal state and logic specific to the hook on that account, and
Emit new transactions on behalf of the account.
This Hooks documentation and the Hooks API use a set of unfamiliar terms. Use the lookup table below if you find yourself lost.
Hook
Originating Transaction
The transaction that triggered the Hook to fire. This could be either a transaction sent out of or into an account with a Hook set on it.
Originating Account
The account that sent an Originating Transaction.
Hook Account
Installer
The account which is currently installing a Hook using the SetHook transaction.
Emitted Transaction
State
SetHook
Guards
Grants
Special permission a Hook Installer can give another account or a specific Hook (regardless of where it is installed) to modify Hook State on the Installer's account.
Namespace
A unique 32 byte code delineating one set of state keys from another. The same state key may be used by two different Hooks without interfering with each-other if the namespaces set on the Hooks are different.
Parameters
Install-time parameters that can be optionally set on a Hook.
Reference Counting
An unowned object on the ledger may be reference counted, meaning it is deleted when the final account which referenced (used) it removes their reference to it.
XFL or Floating Point
Serialized Objects (STO)
Slots and Keylets
Trace
Chain multiple hooks together to do more useful tasks
👍 Hook Design Philosophy
Each Hook should do one thing, and do it really well.
In the early days of Hooks it was only possible to install one Hook per account. This meant users were forced to produce omnibus Hooks if they wanted to do more than one thing: for example offset carbon and firewall at the same time.
This was counter to the Hook Design Philosophy, so Hook Chaining was introduced.
A Hook Chain is a sequence of up to 10 Hooks installed on an Xahau account.
A Hook Chain executes successfully when every Hook in the chain has been individually executed and subsequently calls accept.
Each chain's execution starts at chain position 0 and ends at chain position 9. If a position is blank (because it was never filled or because the hook that was installed there has been removed) then that position is skipped and treated as successful.
In order for a transaction to succeed, both ends of the transaction (sending side and receiving side) must have executed successfully. This means if there is a Hook Chain installed on both sides, then both Hook Chains must execute successfully for the transaction to succeed.
Hooks are installed into the chain using the SetHook Transaction. When they are installed, the installer may specify install-time Parameters which may change the behaviour of the installed Hook.
In addition to the install-time operations specified in the SetHook Transaction, Hooks have some runtime control over chain execution:
A Hook may determine its own HookHash
by calling hook_hash.
A Hook may determine its location in the Hook Chain using hook_pos.
A Hook may skip (or re-enable) another Hook further down the chain using hook_skip.
A Hook may modify the Parameters of a Hook further down the chain using hook_param_set.
Hook Chains are Strongly Executed. However any Hook in any chain may flag that it requires a second, Weak Execution by calling hook_again. If all Hook Chains execute successfully then the originating transaction is applied. Once the originating transaction has been applied any Weak Executions may happen, in the following order:
cbak
execution if this was an Emitted Transaction.
Weak Transactional Stake Holders who have opted in to allow a Collect Call. Execution order is first-come first-serve according to the event that caused the TSH to be flagged (such as pathing).
Any Again as Weak (AAW) Hooks. Execution order for AAW is first numerically according to Account ID then numerically according to Hook position.
Which Hooks are allowed to run and when?
👍 Hook Design Philosophy
Every party affected by a transaction should have the opportunity to have their Hooks executed.
Transactional Stake Holders (TSH) are parties that somehow have a stake in or are otherwise affected by a transaction. Their particular stake may be a weak or strong. The degree of connection with the transaction dictates whether the party has the right to have their Hooks executed and who has to pay for that execution.
For example:
In a conventional direct XAH Payment transaction the two TSH are the originating account and the destination account.
In a SetSignerList transaction the TSH are the originating account and each account whose address appears in the signer list, where such accounts are active on the ledger.
In an OfferCreate transaction, other account's offers which are crossed by the originating transaction are all weak TSH and may opt for weak execution.
Due to the heterogenous nature of transactions on Xahau, TSH come in all shapes and sizes and can be exotic and non-intuitive. This becomes more true as time passes and more transaction types are added to the Ledger.
Each TSH has either a weak or a strong connection to the transaction.
A Strong connection means:
The originating transaction must pay the fee for the execution of the TSH Hook Chain
The TSH has the right to rollback the whole transaction by calling rollback()
from their Hook during execution.
A Weak connection means:
The originating transaction does not pay for the execution of the TSH Hook Chain.
The TSH pays for the execution of their own Hook Chain through a feature called .
The TSH must have set an account flag asfTshCollect
prior to the execution of the originating transaction.
The TSH does not have the right to rollback the whole transaction by calling rollback()
from their Hook during execution (but can still modify their own Hook state and Emit transactions.)
📘 Hint
The uint32_t
parameter in hook(uint32_t)
and cbak(uint32_t)
carries important context information from the Hooks Amendment to your Hook.
During the execution of hook
:
0 means the Hook is being executed strongly
1 means the Hook is being executed weakly
During the execution of cbak
:
0 means the Hook is being called back after a transaction it emitted was successfully accepted into a ledger.
1 means the Hook is being called back after a transaction it emitted was marked as never able to be applied to any ledger (EmitFailure).
If a Transaction Type does not appear in the table then it has no TSHes other than its originating account.
AccountSet
AccountDelete
Check
ClaimReward
DepositPreauth
Escrow
GenesisMint
Import
Invoke
Offer
Payment
PaymentChannel
SetHook
SetRegularKey
SignersListSet
Ticket
TrustSet
URIToken
👍 Hook Design Philosophy
Every party affected by a transaction should have the opportunity to have their hooks executed.
When hooks are not Strongly Executed it is unfair to bill the originating transaction for their execution. For example an OfferCreate which crosses 20 offers on the DEX should not be forced to pay for the execution of each of those account's Hooks.
Therefore during typical Weak execution the fee for the execution is collected from the owner of the Hook. To enable this:
The Hook owner must have set asfTshCollect
on their Xahau account using the AccountSet transaction.
The Hook owner must have set hsfCollect
on the specific Hook they wish to be called as a Weak TSH.
🚧 Warning
This is an advanced feature most Hook Developers will probably not use.
Install-time parameters allow Hooks to be generic and flexible
Hook developers may opt to use install-time parameters (called Hook Parameters) in their Hook. This allows subsequent installers of the Hook to change certain behaviours the programmer defines without recompiling or re-uploading the Hook (assuming at least one account still the existing Hook Definition.)
Hook Parameters are a set of Key-Value pairs set during the and retrievable by the Hook during runtime. Both the ParameterName
(key) and the ParameterValue
are set as hex blobs, and have a maximum length of 32 bytes and 256 bytes respectively.
A may define up to 16 Hook Parameters per installed Hook.
The HookParameters
array is optionally defined inside each Hook
in the Hooks
array as shown below:
The subsequent user may specify their own Parameters, overriding the Default Parameters for their installation.
To erase a Parameter in a subsequent installation, specify the ParameterName
key without specifying a ParameterValue
key.
Hook web assembly bytecode is installed onto an Xahau account using the SetHook
transaction.
An example appears below:
The transaction is deceptively simple, but hides significant complexity, described below.
The main body of the SetHook transaction is the hooks array:
Position 0 in the array corresponds to position 0 in the Hook Chain.
Position 3 in the array corresponds to position 3 in the Hook Chain, etc.
Each entry in the Hooks Array (in the SetHook Transaction) is called a HookSet Object, and its corresponding Hook in the account's Hook Chain is called the Corresponding Hook.
Each Corresponding Hook is an object containing a reference (pointer) to a HookDefinition
object.
The HookDefinition object is an unowned reference-counted ledger object that provides for de-duplication of identical web assembly bytecode. Two users using an identical hook will both point to the same HookDefinition.
There are six possible operations: No Operation, Create, Update, Delete, Install and Namespace Delete
Each operation is specified by the inclusion or omission of certain HookSet Object fields. This might seem confusing at first but by working through a few examples the reader should find it intuitive; Essentially HookSet operations are a type of diff between a specific Hook's defaults, existing and newly specified fields.
Achieving each type of operation is explained in a subsection below.
Occurs when:
The HookSet Object is empty
Behaviour:
No change of any kind is made.
Example:
Occurs when:
All of the following conditions are met:
The Corresponding Hook does not exist orFLAG_OVERRIDE
is specified.
CreateCode
field is specified and is not blank and contains the valid web assembly bytecode for a valid Hook.
No instance of the same web assembly bytecode already exists on Xahau. (If it does and all other requirements are met then interpret as an Install Operation — see below.)
Behaviour:
A reference counted HookDefinition
object is created on Xahau containing the fields in the HookSet Object, with all specified fields (Namespace, Parameters, HookOn) becoming defaults (but not Grants.)
A Hooks
array is created on the executing account, if it doesn't already exist. (This is the structure that contains the Corresponding Hooks.)
A Hook
object is created at the Corresponding Hook position if one does not already exist.
The Hook
object points at the HookDefinition
.
The Hook
object contains no fields except HookHash
which points at the created HookDefinition
.
If hsfNSDELETE
flag is specified then any HookState entires in the destination namespace are deleted if they currently exist.
Example:
Occurs when:
All of the following conditions are met:
The Corresponding Hook does not exist orFLAG_OVERRIDE
is specified.
HookHash
field is specified and is not blank and contains the hash of a Hook that already exists as a HookDefinition
on the ledger or CreateCode
field is specified and is not blank and contains the valid web assembly bytecode for a valid hook that already exists on the ledger as a HookDefinition
.
Behaviour:
The reference count of the HookDefinition
object is incremented.
A Hooks
array is created on the executing account, if it doesn't already exist. (This is the structure that contains the Corresponding Hooks.)
A Hook
object is created at the Corresponding Hook position if one does not already exist.
The Hook
object points at the HookDefinition
.
The Hook
object contains all the fields in the HookSet Object, except and unless:
A field or key-pair within a field is identical to the Hook Defaults set on the HookDefinition
, in which case it is omitted due to defaults.
If hsfNSDELETE
flag is specified then any HookState entires in the destination namespace are deleted if they currently exist.
Example:
Occurs when:
All of the following conditions are met:
The Corresponding Hook exists.
HookHash
is absent.
CreateCode
is absent.
One or more of HookNamespace
, HookParameters
or HookGrants
is present.
General Behaviour:
The Corresponding Hook is updated in such a way that the desired changes are reflected in the Corresponding Hook.
Specific Behaviour:
If HookNamespace
is specified and differs from the Corresponding Hook's Namespace:
the Corresponding Hook's HookNamespace
is updated, and
if the hsfNSDELETE
flag is specified all HookState entires in the old namespace are deleted.
If HookParameters
is specified, then for each entry:
If HookParameterName
exists but HookParameterValue
is absent and the Corresponding Hook's Parameters (either specifically or via defaults) contains this HookParameterName
then the parameter is marked as deleted on the Corresponding Hook.
If HookParameterName
exists and HookParameterValue
exists then the Corresponding Hook's Parameters are modified to include the new or updated parameter.
If HookGrants
is specified then:
The Corresponding Hook's HookGrants
array is replaced with the array.
Example:
Occurs when:
All of the following conditions are met:
The Corresponding Hook exists.
hsfOVERRIDE
is specified.
optionally hsfNSDELETE
is also specified.
HookHash
is absent.
CreateCode
is present but empty.
Behaviour:
The reference count of the HookDefinition
object is decremented.
If the reference count is now zero the HookDefintion
is removed from the ledger.
The Hook
object in the Corresponding Hook position is deleted, leaving an empty position.
If hsfNSDELETE
is specified the namespace and all HookState entries are also deleted.
Example:
Occurs when:
All of the following conditions are met:
flags
is present and hsfNSDELETE
is set. hsfOVERRIDE
can optionally also be specified if the Hook at this position is to be deleted.
HookNamespace
is specified.
CreateCode
is absent.
HookHash
is absent.
HookGrants
, HookParameters
, HookOn
and HookApiVersion
are absent.
Behaviour:
If the Corresponding Hook exists, it remains, nothing happens to it.
A subset of HookState objects and the HookState directory for the specified namespace are removed from the ledger, up to the defined limit of 512. Further transactions are needed to continue the deletion process until all relevant records are removed.
Example:
Hooks add smart contract functionality to the Xahau: layer one custom code to influence the behaviour and flow of transactions. Hooks are small, efficient pieces of code being defined on an Xahau account, allowing logic to be executed before and/or after Xahau transactions.
Please note: you're reading the technical documentation of Hooks. This documentation is highly technical & assumes prior knowledge of programming and the Xahau Ledger. If you are looking for examples on what Hooks are, will bring to the Xahau Ledger and what they could do, please .
Xahau is known and is being appreciated for its transaction throughput, speed and the low fees. Combined with available advanced transaction types like multi sign, escrows, payment channels and even a decentralized exchange (all on ledger, out of the box, without requiring smart contracts) Xahau has a lot to offer businesses and (creative) developers.
Hooks add smart contract functionality to Xahau: layer one custom code to influence the behaviour and flow of transactions. Hooks are small, efficient pieces of code being defined on an Xahau account, allowing logic to be executed before and/or after Xahau transactions. These Hooks can be really simple, like: “reject payments < 10 XAH”, or “for all outgoing payments, send 10% to my savings account” or more advanced.
By allowing Hooks to not only execute efficient logic but also to store small, simple data objects, one could define a Hook like: “for incoming payments transactions, check if the sending account is in a list maintained by another Hook, and if present: reject the transaction”.
Hooks are currently live on a public testnet. It's time for testing, coding, having fun & breaking things, so a future amendment to add Hooks to Xahau livenet can be drafted with confidence.
All Hooks are compiled to a single before they can be set onto an .
A Hook always implements and exports exactly one or both of the following functions:
int64_t hook(uint32_t ctx) { ... }
required
Executed whenever a transaction comes into or leaves from the account the Hook is set on (ctx = 0
) or
Executed when executed as a (ctx > 0
).
int64_t cbak(uint32_t ctx) { ... }
optional
Executed when an emitted transaction is successfully accepted into a ledger (ctx = 0
) or
Executed when an emitted transaction cannot be accepted into any ledger (ctx = 1
).
Hooks are not allowed to specify other functions. Instead they must make clever use of macros to do all their computation within these two functions. This is part of a computational restriction on hooks to keep their runtime predictable.
Additionally Hooks are afforded no heap memory. All required memory must be reserved and used on the stack.
Here is an example Hook written in C. The Hook prints 0...3 to the trace log before accepting the originating transaction.
📘 Hint
For educational purposes the above example deliberately does not include
hookapi.h
(which developers would typically use.)
The average Hook developer will never need to examine webassembly directly. However it is a useful conceptual exercise to review the contents of the sample Hook.
Above we can see:
Three functions are imported from the Hooks API (_g
, accept
, trace_num
)
Two functions are defined by the hook (cbak
, hook
)
Two functions are exported by the hook (again: cbak
, hook
)
Some static (constant) data is recorded in the hook (see data
at the bottom).
It is very important to note that a Hook must only import functions available to it from the Hooks API and must only export the cbak
and hook
functions. In additional all hooks must import _g
from the Hooks API, which is the guard
function.
📘 Hint
Webassembly is a platform-independent general computation
bytecode
language. It has a one-to-one mapping with a human readable equivalent. These are used interchangeably.
Most webassembly compilers (including the one above) produce additional exports for their own linking purposes. In many cases the generation of these is difficult or impossible to disable.
❗️ Important
This term refers to a range of things depending on context 1. A webassembly binary uploadable to Xahau with the type. 2. A webassembly binary already uploaded to and set or configured onto an Xahau account. 3. The of such a binary.
The account where the currently executing Hook lives. This is the account that owns the Hook, the account that performed the which created the Hook and the account to whom belongs the Hook State for the currently executing Hook.
A new transaction created by a Hook during the Hook's execution that is not the Originating Transaction. These are typically used for sending funds back to the Originating Account. See: .
A per-account key-value map of 32 byte keys to arbitrary data. All Hooks present on an account have access to the same Hook State and can modify it. Note that the Hook State lives on the Hook Account not on the Originating Account. See: .
A new Transaction Type introduced in the Hooks ammendment which sets a Hook onto an Xahau account. See: .
A special control mechanism you need to use if you write a loop into in a Hook. See: .
A way to do high precision math in Hooks such as for exchange rate computation. See: .
The way xahaud transmits and stores ledger objects. See: .
Slots can contain ledger objects and keylets identify those objects. See: .
A way to print a log line to the xrpld output from a Hook. See: .
Strong TSHes have their hooks executed before the originating transaction is applied to the ledger. This means they have the ability to the transaction (because it hasn't yet been applied.) This gives strongly executed hooks the ability to completely block a transaction from occurring.
Weak TSHes have their hooks executed after the originating transaction has been applied to the ledger. This means they have access to the but cannot prevent the transaction from occurring.
Strongly executed hooks can call to be executed a second time as a weak execution after the originating transaction has been applied.
2 means the Hook is being executed weakly after being executed strongly due to a call.
The first user to may define Hook Parameters which then become the Default Parameters for that Hook. This means any subsequent users who will receive these originally set Hook Parameters by default.
Parameters can be read by the Hooks they are set on using .
If more than one Hook is installed in a Hook Chain, then can be used in limited circumstances to modify the Hook Parameters of a Hook further down the chain on the same account.
On Xahau and the Xahau testnet, HookParameters may also be included at the top level of any transaction type according to the foregoing rules and size limits. These parameters can be accessed inside a hook using the API.
This array mirrors the installed on the account:
For more information see:
When a HookDefinition
is created it contains the initial , and supplied by the user. These become the Hook Defaults. Any Hook referencing this Hook Definition will use these defaults unless the SetHook Transaction that creates that reference explicitly overrides those defaults, or a subsequent Update Operation overrides them.
Hooks are deliberately not Turing-Complete. While often touted as the holy grail of smart contracts, Turing-Completeness is actually inappropriate for smart contracts. (See .)
A will generate valid webassembly from a C source file. Once compiled, a Hook exists as a binary .wasm
file. This contains a webassembly module. Using wasmcc
to compile and the wasm2wat
tool to convert to human readable webassembly this binary form can be rendered to the human readable form. Below appears the compilation result of the above example.
Unwanted exports will lead to an otherwise valid Hook being rejected. Therefore after compilation developers should use the to strip out these out. Failure to do so will lead to your Hook being rejected.
Don't forget to use the or your Hooks will be rejected.
AccountDelete
Strong
Destination account funds are paid out to after deletion
AccountSet
None
N/A
CheckCancel
Weak
Destination account
CheckCash
None
N/A
CheckCreate
Strong
Destination account
ClaimReward
Strong
Issuer Account
DepositPreauth
Strong
Authorized account
EscrowCancel
Weak
Destination account
EscrowCreate
Strong
Destination account
EscrowFinish
Strong
Destination account
GenesisMint
Weak
Each Destination in the GenesisMints Array
Import
Strong
Issuer Account
Invoke
Strong
Destination account
OfferCancel
None
N/A
OfferCreate
Weak
Accounts whose offers were crossed by this action.
Payment
Strong + Weak
Strong: Destination account. Weak: Any non-issuer the payment is rippled through.
PaymentChannelClaim
Weak
Destination account
PaymentChannelCreate
Strong
Destination account
PaymentChannelFund
Weak
Destination account
SetHook
None
N/A
SetRegularKey
Strong
The account whose address is being set as the key.
SignerListSet
Strong
Accounts whose addresses are set as signing keys (if they exist and have Hooks set on them).
TicketCreate
None
N/A
TrustSet
Weak
Issuer account
URITokenCancelSellOffer
None
N/A
URITokenCreateSellOffer
Strong
Destination account, Issuer if tfBurnable Flag is set
URITokenBurn
Strong
Issuer if tfBurnable Flag is set
URITokenBuy
Strong
Owner account, Issuer if tfBurnable Flag is set
URITokenMint
None
N/A
Account
Account
Strong
Account
Account
None
Account
Beneficiary
Strong
Account
Account
Strong
Strong
None
Account
Destination
Weak
Strong
None
Destination
Destination
Strong
None
Strong
Destination
Account
Weak
None
Weak
Account
Account
Strong
Account
Issuer
Strong
Account
Account
Strong
Account
Authorized
Strong
Account
Account
Strong
Strong
Strong
Account
Destination
Weak
Strong
Weak
Destination
Destination
Strong
None
Strong
Destination
Account
Weak
None
Weak
Account
Account
Strong
Account
Destination
Strong
Account
Beneficiary
Weak
Account
Account
Strong
Account
Issuer
Strong
Account
Account
Strong
Account
Destination
Weak
Account
Account
Strong
Strong
Account
Crossed
None
Weak
Account
Account
Strong
Account
Destination
Strong
Account
Crossed
Weak
Account
Account
Strong
Strong
Strong
Account
Destination
Weak
Strong
Weak
Destination
Destination
Strong
None
None
Destination
Account
Weak
None
None
Account
Account
Strong
Account
Account
Strong
Account
RegularKey
Strong
Account
Account
Strong
Account
Signer
Strong
Account
Account
Strong
Account
Account
Strong
Account
Issuer
Weak
Owner
False
Owner
None
Strong
Strong
Strong
Strong
Owner
False
Issuer
None
Weak
Weak
Weak
None
Owner
False
Buyer
None
None
None
Strong
Weak
Owner
True
Buyer
None
None
None
Strong
Weak
Owner
True
Owner
None
Strong
Strong
Strong
Strong
Owner
True
Issuer
None
Weak
Strong
Strong
None
Issuer
False
Owner
None
None
None
None
None
Issuer
False
Issuer
Strong
None
None
None
None
Issuer
False
Buyer
Weak
None
None
None
None
Issuer
True
Owner
None
Weak
None
None
None
Issuer
True
Issuer
Strong
Strong
None
None
None
Issuer
True
Buyer
Weak
None
None
None
None
Buyer
True
Buyer
None
None
Strong
None
None
Buyer
True
Owner
None
None
Weak
None
None
Again As Weak - Happens when a Strongly Executed Hook calls hook_again
Free (already paid by the Strong Execution).
Callback - Happens when an emitted transaction either makes it into a ledger or is flagged as being impossible to ever make it into a ledger.
Free (already paid during Emission).
Weak Transactional Stakeholder - Happens if a transaction in some way mildly affects your account.
Paid for by your account (not by the originating transaction) if and only if both your account is marked with asfTshCollect
flag and your Hook is marked with the hsfCollect
flag.
What to expect when your Hook runs.
SetHook transactions are charged per byte of created webassembly. The rate is 10000 drops per byte. Thus a 1kib Hook will cost 10 XAH to create.
When Hooks are Strongly Executed the originating transaction must pay for the Strong Executions in the originating transaction's fee.
Hook Execution fees are charged at a rate of 1 drop per web assembly instruction in the worst-case execution of the function hook
(or cbak
in the case of a callback). Thus a small Hook with a lot of looping may end up attracting high runtime fees.
Transaction fees on a ledger with the Hooks Amendment enabled become non-trivial to compute for end-users and/or wallet applications. This is because strong hooks must be paid for by the originator of a transaction, and there may be as many as 4 strong hooks on the sending account and 4 on the receiving account, as well as any other strong transactional stakeholders involved (as can be the case with some exotic transaction types). Further, if the transaction is a SetHook then the size of the parameters, the size of the code and whether it is a create operation or an install operation all determine the size of the fee.
Therefore it is highly recommended that all transactions be run through the updated fee RPC call before they are submitted to the ledger.
To invoke the RPC call:
Open a websocket connection to the Hooks node you will be working with.
Compose the serialized transaction you wish to know the fee for with the following:
Fee: "0"
SigningPubKey: ""
(That is: 0 byte VL of type 0x73. In hex:0x7300
.)
Do not sign the transaction.
Submit it as a hex blob to the RPC as follows:
For HTTP POST RPC submit it as follows:
The response should look something like
Take the base fee and set it as the Fee
field in the transaction. Now sign and submit it as per the normal transaction submission process.
If there is an invalid value for tx_blob
or tx_blob
is missing, a regular JSON result will be returned with a base_fee
of 10.
Hooks have access to the same computation the Fee RPC Helper does. To use this simply call etxn_fee_base with a buffer containing the serialised transaction as the arguments. As with the RPC call, you must ensure that the Fee
field is present in the serialised transaction. The value is irrelevant.
When etxn_fee_base
returns the recommended fee you may use sto_emplace to emplace it into the serialised transaction before emission. The relevant field is sfFee
.
Prevent state clobbering by using the correct namespace
To avoid two or more Hooks installed on the same account unintentionally clobbering each-other's Hook State, a 32 byte namespace must be provided when creating or installing each Hook.
The namespace may be any arbitrary 32 byte value the developer chooses. Provided the namespace is unique in the Hook chain no state clobbering will occur.
We strongly recommended using SHA256
over the developer's working name for the Hook. SHA256 is one of the two hashing algorithms used in the derivation of Xahau addresses (from an account master key), and, as such, it should be readily available to the developer.
The HookNamespace
field is supplied as a 32 byte hex blob inside each Hook
object in a Hooks
array when executing a SetHook transaction.
The configured Namespace a Hook operates under alters the Keylets its State is stored under. Therefore two Hooks under two different Namespaces installed on the same Xahau account may use the same state key to refer to different state objects. Conversely, two different Hooks using the same Namespace on the same Xahau account can access and modify eachother's state objects using the same state keys.
In javascript, importing the ripple-address-codec
yields access to SHA256.
(It is also possible to use crypto.subtle
in browser, or crypto.createHash
in node to access this hash algorithm.)
The first user to set a novel Hook defines a HookNamespace
which becomes the Default Namespace for that Hook. This means any subsequent users who reference the same HookDefinition will receive this originally set Namespace by default.
The subsequent user may specify their own Namespace, overriding the Default Namespace for their installation only.
Choice of HookNamespace affects the behaviour of the following Hook APIs:
See account_info and account_namespace for information about how to query the ledger regarding namespaces.
Hook Grants
❗️ Warning
Most Hook Developers will rarely need to use HookGrants, and should exercise extreme caution when granting state mutation permission to foreign Hooks and accounts.
While a HookGrant cannot be used to directly steal funds, intentional external modification of a Hook's State may lead a Hook to behave in an unintended way, which in some cases could lead to a theft.
If you think you need to use a Grant then please re-check your design first to ensure you actually need to use one before continuing.
Grants provide a way for a Hook Installer to assign State Management permissions to a foreign Hook on other Xahau accounts.
A SetHook Transaction may specify a HookGrants
array within any Hook
object in its Hooks
array. The HookGrants
array contains one or more HookGrant
objects (up to 8).
Unlike Parameters, the HookGrants
array is always set exactly as specified in the SetHook Transaction. Therefore if you wish to update a particular HookGrant
whilst retaining multiple other HookGrant
entires that were previously set, you must first obtain the old HookGrants
array, modify it, and then resubmit the entire array in an Update Operation.
To delete all Grants submit an empty HookGrants
array.
🚧 Important
Unlike Parameters, the
HookGrants
array is always set exactly as specified in the SetHook Transaction.
A Grant permits a foreign XRPL account or Hook to modify the Hook State within the namespace of the specific Hook for which the Grant is defined.
The HookGrant must specify at least:
HookHash
And may also specify an account:
Authorize
Only the Hook specified by HookHash may modify the Hook State within the namespace of the Hook for which the HookGrant is specified. If Authorize
is specified then this permission is tightened further to only the Hook specified by the HookHash when it is installed on the account specified by Authorize
.
📘 Tip
Grants only apply to external Hooks and never limit the operation of Hooks with respect to the Hook State on the account they are installed on.
The first grant above allows:
any instance of the Hook whose code that hashes to 78CAF69EEE950A6C55A450AC2A980DE434D624CD1B13148E007E28B7B6461CC8
executing on any account
to modify the Hook State of account rALicebv3hMYNBWtu1VEEWkToArgYsYERs
inside the Namespace 3963ADEB1B0E8934C0963680531202FD511FF1E16D5864402C2DA63861C420A8
The second grant above allows:
any instance of the Hook whose code that hashes to A5B8D62154DA1C329BE13582086B52612476720CEBD097EB85CEE1455E1C70A6
but only when executed on account rCLairev2ma2gNZdcHJeTk7fCQ1ki84vr9
to modify the Hook State of account rALicebv3hMYNBWtu1VEEWkToArgYsYERs
inside the Namespace 3963ADEB1B0E8934C0963680531202FD511FF1E16D5864402C2DA63861C420A8
To make use of a grant, a Hook modifies State objects on a foreign account by calling state_foreign_set.
Hooks can read and save small pieces of on-ledger data 🚀
State in computer science describes information held by a system between executions (as distinct from inputs and outputs.) For example your browser leaves you logged in to a website even after you close and reopen it. The login cookie is held in the browser's state.
Hook State refers to a key-value mapping that logically exists for each account on Xahau whether or not any keys are currently present. The keys are always 32 bytes (unsigned 256 bit integer) and the values are variable length with a maximum size determined by validator voting, at time of writing 256 bytes.
State Management is achieved using
The below example uses the state_set Hook API to assign the value 0xC001CAFE
to the key 0x0..000001
(uint256 = 1) in the Hook State of the Hook Account.
In a subsequent Hook execution this value can now be retrieved using the same key:
After the above code has run the value
buffer will be populated with the value found at the key.
📘 Hint
The buffer
state()
reads into (writeptr) must be large enough to store the value currently held at that key. If it isn't the Hook API returns with aTOO_SMALL
error.
From time to time it may be advantageous for one Hook running on one account to read the Hook State of another Hook running on another account. The state_foreign Hook API does exactly this. Because the ledger is public there is no reasonable expectation of privacy anyway. Any Hook may therefore read (but not write) the Hook State of any other Hook.
Please see Namespaces
Your Hook can do a lot more than just block or allow transactions!
All changes made to Xahau must be the result of applying a valid transaction to the ledger. Thus if some change X is made then some transaction Y is responsible.
When designing the Hooks API we needed a way for Hooks to make changes to the ledger beyond simply accepting or rejecting a transaction. However attaching these changes to the Originating Transaction was confusing and resulted in a large increase in the general complexity of the system.
Suppose for example that a Hook needs to send you some funds... the send operation would be effectively enacted onto the ledger by the Originating Transaction which might have been something completely unrelated such as an Account Set transaction. Additionally this send operation would need to be able to potentially trigger another Hook on the receiving end of a payment.
The solution: Emitted Transactions. We allow the Originating Transaction to do exactly what the contents of the Transaction say it will do. If our Hook needs to make an additional change to the ledger such as sending a payment, it creates and then emits a brand new transaction.
Emitted Transactions are new transactions created by the execution of a Hook and entered into consensus for processing in the next ledger. The transaction may be of any Transaction Type but must follow strict emission rules.
To emit a transaction the Hook first prepares the serialized transaction then calls emit.
Because emitted transactions can trigger Hooks in the next ledger which in turn may emit more transactions, all emitted transactions carry a burden
and a generation
field in their EmitDetails
block. The EmitDetails
block replaces the signature field in a traditional transaction.
The burden
and generation
fields collectively prevent Fork bomb attacks on the ledger by exponentially increasing the cost of exponentially expanding emtited transactions.
It is important to note that the Hooks API follows the strict rule of no rewriting. You must present an emitted transaction in full, valid and canonically formed to xahaud for emission or it will be rejected. It is not xahaud's job to build your transaction for you. The Hook must do this itself.
As introduced in Introduction and Terminology emitted transactions trigger callbacks when they are accepted into a ledger. Due to the decentralised nature of consensus acceptance into a ledger of an emitted transaction is not a guarantee, although it is usually all-but guaranteed.
If an emitted transaction expires before it can be accepted into a ledger (for any number of reasons: the ledgers may be full, the fee may be too high for the emitted transaction or the emitted transaction may be somehow invalid) then a pseudo transaction is created in the ledger to clean up the emitted transaction. This pseudo transaction also calls the callback of your hook, with parameter = 1
to indicate the emitted transaction indeed failed.
The emit Hook API will enforce the following rules on a proposed (to be emitted) transaction.
1
sfSequence
= 0
Emitted Transactions do not increase the sequence number of the Hook Account. This must always be set to zero.
2
sfPubSigningKey
= 0
Emitted Transactions are not signed but this is a required field for xrpld processing. It must be set to all zeros.
3
sfEmitDetails
present and valid
Emitted Transactions require an sfEmitDetails
block and this must be correctly filled. See EmitDetails section below.
4
sfSignature
absent
This field must be absent in the emitted transaction because if it were not then the transaction would be ambiguous.
5
LastLedgerSequence
valid and in the future
All emitted transactions must have a last ledger sequence set so that the Hook knows if the emitted transaction failed (since it did not get a callback in time). This is currently set to a maximum of 5 ledgers after the current ledger.
6
FirstLedgerSequence
valid and set to the next ledger
All emitted transactions must have a first ledger sequence set to the next ledger (after the current ledger) so that Hooks do not recursively cascade within a single ledger. This is currently enforced to be the next ledger after the current ledger.
7
Fee appropirately computed and set
The fee is dependent on the size of the emtited transaction and the burden on the network (i.e. whether this emitted transaction was the result of another emitted transaction.)
8
Generation cap not exceeded
An emitted transaction can produce other emitted transactions, and these can form a chain. The length of the chain is the sfEmitGeneration
. This is currently capped at 10.
All emitted transactions must contain an sfEmitDetails
object correctly populated with the fields in the table below.
sfEmitGeneration
This field keeps track of a chain of emitted transactions that in turn cause other transactions to be emitted.
sfEmitBurden
This field is a heuristic for detecting forkbombs. Fees are based on burden and will increase exponentially when a chain reaction is started to prevent the network becoming overun by self-reinforcing emitted transactions.
sfEmitParentTxnID
The transaction ID of the Originating Transaction
The Hook Execution that emitted the transaction is connected to the Originating Transaction. Therefore this field is always required for the efficient tracing of behaviour.
sfEmitNonce
Emitted Transactions would be identical with the same fields and therefore have identical transaction hashes if a nonce were not used. However every node on the network needs to agree on the nonce, so a special Hook API to produce a deterministic nonce is made available.
sfEmitCallback
The 20 byte Hook Account ID
This field is used by xahaud when it needs to intitate a callback, such that it knows which Hook and account to initate the callback on. Callbacks happen when an emitted transaction is accepted into a ledger.
👍 Check the examples
The Example Hooks, in particular Peggy, Carbon and Doubler, demonstrate how to emit both simple and more complicated transactions.
Inspect and manipulate on-ledger objects.
Xahau contains numerous heterogenous object types which a Hook has read-access to. For example: transactions, accounts, ledgers, and the subcomponents of each of these, to name just a few.
It is very easy to carelessly program a computer to do a lot of needless copy operations when disciplined access to the same underlying data (i.e. through a view) would suffice. The deliberate avoidance of copy operations in programming is referred to as Zero copy in programming.
With Hooks the same principle applies. We want to avoid copying where possible. In particular we want to avoid as much as possible needlessly copying large objects such as whole ledgers, we also want to avoid serializaing and unserializing these where possible.
Slots are part of the Hook API and provide a zero-copy heterogenous access system for on-ledger objects and transactions.
Each Hook has access to 255 slots during runtime.
Each slot may be empty or may contain a slotted object.
The slot API allows traversal into inner objects, and allows these inner objects themselves to be slotted.
The slot API allows slotted objects to be dumped to a buffer or otherwise read by the Hook.
The avilable slot APIs are:
Serialize and output a slotted object
Free up a currently occupied slot
Count the elements of an array object in a slot
slot_id
Compute the canonical hash of the slotted object and return it
Locate an object based on its keylet and place it into a slot
Index into a slotted array and assign a sub-object to another slot
Index into a slotted object and assign a sub-object to another slot
Retrieve the field code of an object in a slot and, optionally, some other information
Parse the STI_AMOUNT in the specified slot and return it as an XFL enclosed number
Compute the serialized size of an object in a slot
Keylets are used to locate (point to) on-ledger objects. In brief they are a hash of identifying information from the object, which is the canonical handle for that object.
Hooks use a serialized 34 byte keylet format which can be derrived using the important util_keylet function. Without this looking up and slotting objects would be generally impossible.
🚧 Tip
The Hook APIs which accept a 34 byte keylet will also generally accept a 32 byte canonical transaction hash.
In the following example a 34 byte keylet for a signers
object is used to slot that object.
Manipulate raw serialized xahaud objects!
The XRP Ledger has canonical serialized forms of all objects subject to consensus. When writing a Hook it is inevitable you will come across serialized objects. These manifest as buffers containing what might appear to the developer as opaque binary blobs. In fact you can read these with the XRPL-Binary-Visualiser.
For example an sfAmount
field serializes to a collection of bytes like 61D50F26109A32B7EC
To assist Hook developers in working with serialized objects the sto
namespace was created within the Hooks API. These functions manipulate pointers within a Hook-provided buffer. See table below.
Index into a xrpld serialized object and return the location and length of a subfield
Index into a xrpld serialized array and return the location and length of an index
Emplace a field into an existing STObject at its canonical placement
Remove a field from an STObject
Validate an STObject
Where applicable these APIs return an offset and a length encoded into a single int64_t. See individual documentation for details.
At typical scenario in which you would use the STO API is in processing memos on an Originating Transaction. Since you will likely need access to the whole memo anyway, an efficient way to process a set of memos is simply to dump the whole sfMemos
field into a buffer then index around within it. While it is also possible to use the slot API to do this by slotting the Originating Transaction it would result in additional code and additional copying.
You may notice some overlap between slot APIs and STO APIs. The key difference here is who owns the underlying data:
If you are using slots then xrpld owns the object you are interacting with.
If you are using the STO API then the Hook owns the buffer you are interacting with.
Both sets of functions index into a Serialized Object without unnecessary copying.
Avoid re-uploading the same bytecode to the ledger
When a novel Hook's web assembly byte-code is uploaded to Xahau, a significant storage burden is imposed on the network. This storage burden is reflected in the Hook Fees charged by the network.
To avoid this burden (and high fees for end users) reference counting is used:
The first time a novel Hook is installed, the SetHook Transaction must provide a significant fee.
The Hook's web assembly byte-code becomes an unowned and reference counted object on the ledger (called a HookDefinition
).
Subsequent installations by the same or other users for an identical Hook (i.e. with identical byte-code) increment the reference count. These installations point at the same object on the ledger. These transactions are billed in a similar way to setting a Trust Line, as the storage burden for the Hook was already paid for in the original Set Hook transaction.
While the reference count on the Hook Definition is greater than zero (meaning one or more accounts still have the Hook installed) the object remains on the ledger.
Specify which transaction types a Hook should be triggered on
Each bit in this unsigned 256-bit integer indicates whether the Hook should execute on a particular transaction type. All bits are active low except bit 22 which is active high. Since 22 is ttHOOK_SET this means the default value of all 0's will not fire on a SetHook transaction but will fire on every other transaction type. This is a deliberate design choice to help people avoid bricking their Xahau account with a misbehaving hook.
Bits are numbered from right to left:
bit 0 - right most, i.e. the least significant bit.
bit 63 - the left-most, i.e. the most significant bit.
Examples (assuming a 256-bit unsigned integer type):
If we want to completely disable the hook:
If we want to disable the hook on everything except ttPAYMENT:
If we want to enable the hook on everything except ttHOOK_SET
If we want to enable hook firing on ttHOOK_SET (dangerous) and every other transaction type:
What to expect when your Hook runs.
When Hooks execute they leave behind information about the status of that execution. This appears in the Originating Transaction metadata as an sfHookExecutions
block. This block contains the following fields:
sfHookResult
Hooks can end in three ways: accept
, rollback
and error
.
This is not the same as sfHookReturnCode!
sfHookHash
The SHA512H of the Hook at the time it was executed.
sfHookAccount
The account the Hook ran on.
sfHookReturnCode
The integer returned as the third parameter of accept
or rollback
.
sfHookReturnString
The string returned in the first two parameters of accept
or rollback
, if any.
sfHookInstructionCount
The total number of webassembly instructions that were executed when the Hook ran.
sfHookEmitCount
The total number of produced by the Hook.
sfHookExecutionIndex
The order in which the Hook was executed (as distinct from other Hook Executions on the same Originating Transaction.)
sfHookStateChangeCount
The number of changes the Hook made during execution.
How to print "hello world" from your Hook!
The Hook API provides a set of functions in the namespace trace
which write output to the xrpld
log file when xrpld is configured with the trace log-level. These functions, generally speaking, allow you to see the value of variables, buffers and otherwise trace the execution and state of a Hook at runtime.
📘 Hint
At time of writing there is no interactive Hook Debugger. You must use the trace functions.
The following trace
functions are available in the Hooks API
The following code will print a single trace line then accept the Originating Transaction.
An example of the log-line produced by xahaud
when a payment is sent out of or into the Hook Account:
The above appears in the log as all-one-line, but split here for visibility.
👍 Use testnet
A breakdown of the log format appears in the table below
🚧 Tip
Xahaud
produces a lot of output. It is therefore generally advisible to grep logs for the account/s you are interested in. For example use:tail -f log | grep HookTrace | grep <account>
High precision calculations are native to Hooks.
are widely used in computer science to do calculation of finite precision but arbitrary scale numbers.
Most modern CPUs are capable of performing fast floating point operations using the however xahaud
does not use this format. Instead Xahau uses a .
This custom format has three basic properties:
The format is inherently decimal, expressed as a decimal mantissa
multipled by 10
to the power of an exponent
.
All values expressed have 16 significant (decimal) figures.
The range of exponents is -96
to +80
When serialized the mantissa is 54 bits, and the exponent is 8 bits, with a final sign bit bringing the total size of the serialized floating point to 63 bits.
is an XRPL standards proposal that defines an efficient way to pack and store xrpld floating point numbers (as described above).
XFLs store the bits of the floating point number within an enclosing number. This is always an int64_t
. Negative enclosing numbers represent invalid XFLs (for example as a result of division by zero.)
📘 Hint
Use the XFL-tool to compose and decompose XFLs in your browser!
Some example XFLs follow
This format is very convenient for Hooks, as Hooks can only exchange integer values with xrpld. By enclosing the floating point inside an integer in a well defined way it becomes possible to do complex floating point computations from a Hook. This is useful for computing exchange rates.
Floating point regimes typically have a number of different ways to express zero, which can be a problem for testing for zero. For example 0 x 10 ^ 1
is zero and 0 x 10 ^ 2
is also zero. For this reason there is a canonical zero enforced by the standard and the Hook API. The canonical zero is also enclosing number zero (0
).
Once you have an XFL you can use the Float API to do various computations. The Float API appears in the table below. Each API takes one or more XFL enclosing numbers and returns an XFL enclosing number. Negative return values always represent a computational error (such as division by zero). There are no valid negative enclosing numbers.
❗️ Warning
You should never do any direct math or comparison on the enclosing number. This will almost always result in incorrect computations.
The sole exception is checking for canonical zero.
In the below example an exchange rate conversion is performed, followed by a high precision fraction multiplication.
🚧 Tip
If a float API returns a negative value and you do no check for negatives then feeding that negative value into another float API will also produce a negative value. In this way errors are propagated much as
NaN
(not a number) is propagated in other languages.If you ever end up with a negative enclosing number an error occured somewhere in your floating point calculations.
If the Originating Transaction was itself an emitted transaction then one more than the sfEmitGeneration
of that transaction.
If the Originating Transaction was not an emitted transaction then 1
.
This should be populated using .
If the Originating Transaction was itself an emitted transaction then the burden
of the Originating Transaction multiplied by the maximum number of transactions the Hook has declared it will emit using .
If the Originating Transaction was not an emitted transaction then 1
.
This should be populated using .
A special deterministic nonce produced by a call to
The is the perfect place to test your Hooks.
2021-Apr-13 13:59:11.083700726 UTC View:TRC
xahaud
's prefix to the log line
1
HookTrace
This is a trace initiated by the Hook itself not some other information about the Hook. Other information is available on tags HookError
, HookEmit
and HookInfo
.
2
[rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh
The first account in the square brackets is the Hook Account.
3
-rE3SfnjwfzZFL3JK9cLVfJuy8Ar1XnCqPw]:
The second account in the square brackets is the Originating Account.
4
A number
This is the message the Hook was told to deliver before the trace payload
5
10
This is the trace payload
6
Create a float from an exponent and mantissa
Multiply two XFL numbers together
Multiply an XFL floating point by a non-XFL numerator and denominator
Negate an XFL floating point number
Perform a comparison on two XFL floating point numbers
Add two XFL numbers together
Output an XFL as a serialized object
Read a serialized amount into an XFL
Divide one by an XFL floating point number
Divide an XFL by another XFL floating point number
Return the number 1 represented in an XFL enclosing number
Get the exponent of an XFL enclosing number
Get the mantissa of an XFL enclosing number
Get the sign of an XFL enclosing number
float_exponent_set
Set the exponent of an XFL enclosing number
float_mantissa_set
Set the mantissa of an XFL enclosing number
float_sign_set
Set the sign of an XFL enclosing number
Convert an XFL floating point into an integer (floor)
Compute the nth root of an XFL
Compute the decimal log of an XFL
-1
1478180677777522688
-1000000000000000 * 10^(-15)
0
0
0 (canonical zero)
1
6089866696204910592
+1000000000000000 * 10^(-15)
PI
6092008288858500385
+3141592653589793 * 10^(-15)
-PI
1480322270431112481
-3141592653589793 * 10^(-15)
Print a utf-8 message, followed by a user-specified buffer (this last optionally as hex.)
Print a utf-8 message, followed by an integer.
Print a utf-8 message, followed by an XFL Floating point number.
trace_slot
Print a utf-8 message, followed by the serialized contents of a slot.