Vision to Reality 3: Template Updates
This article covers improvements and extensions we have done to the XO template structures and highlights differences since the Fungible Token Request and Managing State and Ownership articles.
Templating Updates
Simplified Burn Actions
When working with the token burn features in the P2PKH template, there was some edge case handling around change that was difficult to resolve with simple CashASM statements. Specifically, there was a case where the calculated values of the defined non-burn outputs would contain 0 tokens and therefore shouldn't exist.
To resolve this, we instead introduced a omitChangeAmounts parameter to inputs that allows a template to declare how much of the input values that should ignored when creating a potential change output.
// Ignore the burned token amount when determining change for this output.
omitChangeAmounts:
{
fungibleTokens: '${<burnedTokenAmount>}'
}This allows us to remove the full output definition in the burn actions and avoids the need for exact math and edge-case handling in burn actions.
Action Intents
Previously, the expectations for the engine was that it would be capable of looking through the full dependency tree for requirements and then summarize a list of the requirements for the users. This actually works reasonably well, but while working on unrelated user experience improvements we reached a conclusion that simple references to actions are not sufficient in the starting points and follow-up actions from outputs or locking scripts.
When we redesigned the starting and follow-up action intents to carry additional information it became possible to front-load all requirements and simplify the engine implementation, so we now have a generic intent structure for starting points:
start:
[
{
action: 'receive',
role: 'owner',
},
]We also reuse this intent interface in the output and locking scripts follow-up actions:
actions:
[
{
action: 'sign',
role: 'owner',
secrets: 'ownerKey',
},
]This allows us to have both role based and role-less starting points, a place to declare which state should be carried through to the next action and a place to expand functionality to provide more contextful starting options.
Generic Role Sections
In the previous template structures, we had a great set of descriptions for the various actions, inputs/outputs and locking scripts, but they were written to be generic such that they would fit for any participant looking at them. This is an acceptable solution, and the template retain this capability for those that prefer it, but we also wanted to make sure that it was possible to provide descriptions that are different based on who is looking at a given component.
For example, the funds in a requestSatoshisTransaction are received by one party, but they are sent by another.
While trying to address this and provide for role-specific ways to define descriptions, we decided to go with a generic approach where any property of a field that can have roles can be defined under the role and the engine can flatten the structure as needed:
someSection:
{
propertyA: 'someGenericValue',
propertyB: 'someGenericValue',
propertyC: 'someGenericValue',
roles:
{
provider:
{
propertyA: 'aRoleBasedValue',
},
beneficiary:
{
propertyA: 'aDifferentRoleBasedValue',
}
}
}Depending on what role the participants engine have in this section, the value for propertyA can either come from the generic, provider's or beneficiary's definition.
With this, user interfaces can show different names, icons, descriptions, requirements, etc. depending in the users role.
Role Slots / Instances
Most use cases we have looked at have a fixed and well-defined set of roles. However, there are some cases where the number of participants is more fluid, and it is often not possible to know how many participants there will be, when the templates are created.
For example, a fundraising campaign could have any number of pledgers, and to address this we have added new restrictions to roles, which can be left undefined as needed in open-ended cases:
roles:
[
{
role: 'owner',
slots: { min: 1, max: 1 }
},
{
role: 'sender',
slots: { min: 1, max: undefined }
}
],This allows the engine to verify that a valid number of roles have been claimed and adds support for use cases like fundraisers where the number of pledges are not known at the template level.
Explicit Key Generation
In the previous versions of the templating system, keys were generated by the engine as needed. While implementing the engine, it wasn't always clear when this would happen, which always felt a little bit like black magic.
As part of adding the starting and follow-up intents, we now also have a generate section in the intents that lets the engine know when to create the ownerKey:
requirements:
{
generate: [ 'ownerKey' ],
},This allows us to declare what data the engine should automatically generate, rather than rely on hidden assumed behaviour.
Explicit Transaction Shape
While there is a goal to keep templates small and easy to work with, previously we had made an assumption around how inputs and outputs are specified which led to an implicit rather than explicit ordering. For most situations, this was not a problem, but when discussing how to support composing multiple transaction it wasn't not clear how to form the transaction shape.
Transaction composition is not fully complete yet, but we have restructured the input and output section of transactions to provide explicit ordering:
outputs:
[
{
output: 'receiveOutput',
index: undefined,
},
],This allows us to simplify transaction composition and makes it easier to do validation.
Testing Scenarios
The most ambitious change is the introduction of scenarios. These are template-defined examples for the various actions in the template that the engine can use to provide automatic testing.
This can be a great tool for the template authors, but also allows for more strict safety in the sense that a template that fails to execute on the provided scenarios can fail early, before users provide funding to it, rather than fail after funds have already been sent.
The scenarios are relatively large and are likely to undergo more changes as we implement more of them, but to illustrate the idea, here is a shortened example:
scenarios:
[
{
name: 'requesting satoshis',
description: 'happy-path evaluation for requesting satoshis.',
// The action being run in the scenario.
action: 'requestSatoshis',
// List of roles taken in this scenario, and the resources they provided.
roles:
[
// See values below for example.
],
// List of resources provided outside the context of a role.
values:
{
generated:
{
// This scenario does not have any non-role generated values.
},
variables:
{
// This scenario does not have any non-role variables.
},
secrets:
{
// This scenario does not have any non-role secrets.
},
},
// Outcomes provides the set of resulting values created from the action.
outcome:
{
roles:
{
// .. list of generated data per-role
},
transactions:
{
requestSatoshisTransaction:
{
// .. list of all data related to the transction
}
},
},
}
]Note that scenarios are not suitable for providing trust, as a malicious template author can make actions that steal your funds, and write valid functional scenarios that prove that they would be successful at stealing your funds, if used.
Miscellaneous
We have also added and updated many of the descriptions, moved some inline number to constants, added some rationales for additional design decisions and other minor changes.