Files
xo-cli/tests/cli/mocks/template-p2pkh.ts

1395 lines
56 KiB
TypeScript

import type { XOTemplate } from '@xo-cash/types';
import { generateTemplateIdentifier } from '@xo-cash/engine';
export const p2pkhTemplate: XOTemplate = {
$schema: 'https://libauth.org/schemas/wallet-template-v0.schema.json',
// Name for this template.
name: 'Wallet (P2PKH)',
// Description for this template.
description: 'A standard single-factor wallet template that uses Pay-to-Public-Key-Hash (P2PKH) locking scripts.',
// Icon for this template.
icon: 'wallet',
// Version number for this template.
version: 0,
// List of VM versions that can be used to run this template.
supported: [ 'BCH_2023_05', 'BCH_2024_05', 'BCH_2025_05', 'BCH_2026_05' ],
// Describe a list of roles that are used in this template.
roles: {
owner: {
name: 'Wallet Owner',
description: 'The party who can spend from this wallet.',
icon: 'owner',
},
receiver: {
name: 'Receiver',
description: 'A party that is receiving value.',
icon: 'receiver',
},
sender: {
name: 'Sender',
description: 'A party that is sending value.',
icon: 'sender',
},
},
// Define a list of entrypoints supported by this template.
start: [
{
action: 'receive',
role: 'receiver',
},
{
action: 'requestSatoshis',
role: 'receiver',
},
{
action: 'requestFungibleTokens',
role: 'receiver',
},
{
action: 'requestNonfungibleTokens',
role: 'receiver',
},
],
// Define a list of actions that can be taken by this template.
// NOTE: There is no action to generate an address, but a wallet can create an invitation to a receive action and
// extract the generated lockscript as needed as the engine will track all lockscripts it generates.
actions: {
receive: {
// TODO: Consider rewriting to be generic/role-less.
name: 'Receive',
description: 'Receive an unspecified amount of cash and/or tokens from one or more senders.',
icon: 'receive',
roles: {
receiver: {
name: 'Receive',
description: 'Receive an unspecified amount of cash and/or tokens from one or more senders.',
icon: 'receive',
requirements: {
generate: [ 'ownerKey' ],
},
},
sender: {
name: 'Send',
description: 'Send an unspecified amount of cash and/or tokens to the provided receiver.',
icon: 'send',
},
},
requirements: {
roles: [
{
role: 'receiver',
slots: { min: 1, max: 1 },
},
{
role: 'sender',
slots: { min: 1, max: undefined },
},
],
variables: [ 'requestedSatoshis' ],
},
transaction: 'receiveTransaction',
},
requestSatoshis: {
// TODO: Consider rewriting to be generic/role-less.
name: 'Request Satoshis',
description: 'Requests a specific amount of Bitcoin Cash from one or more senders.',
icon: 'request',
roles: {
receiver: {
name: 'Request Satoshis',
description: 'Requests a specific amount of Bitcoin Cash from one or more senders.',
icon: 'request',
requirements: {
generate: [ 'ownerKey' ],
variables: [ 'requestedSatoshis' ],
},
},
sender: {
name: 'Send',
description: 'Send a specific amount of Bitcoin Cash to the provided receiver.',
icon: 'send',
},
},
requirements: {
roles: [
{
role: 'receiver',
slots: { min: 1, max: 1 },
},
{
role: 'sender',
slots: { min: 1, max: undefined },
},
],
},
transaction: 'requestSatoshisTransaction',
},
requestFungibleTokens: {
// TODO: Consider rewriting to be generic/role-less.
name: 'Request Fungible Tokens',
description: 'Requests a specific amount of a fungible tokens from one or more senders.',
icon: 'request',
roles: {
receiver: {
name: 'Request Fungible Tokens',
description: 'Requests a specific amount of a fungible tokens from one or more senders.',
icon: 'request',
requirements: {
generate: [ 'ownerKey' ],
variables: [ 'requestedTokenCategory', 'requestedTokenAmount' ],
},
},
sender: {
name: 'Send',
description: 'Send a specific amount of fungible tokens to the provided receiver.',
icon: 'send',
},
},
requirements: {
roles: [
{
role: 'receiver',
slots: { min: 1, max: 1 },
},
{
role: 'sender',
slots: { min: 1, max: undefined },
},
],
},
transaction: 'requestFungibleTokensTransaction',
},
requestNonfungibleTokens: {
// TODO: Consider rewriting to be generic/role-less.
name: 'Request a Non-fungible Token',
description: 'Requests a non-fungible token from one or more senders.',
icon: 'request',
roles: {
receiver: {
name: 'Request a Non-fungible Token',
description: 'Requests a non-fungible token from one or more senders.',
icon: 'request',
requirements: {
generate: [ 'ownerKey' ],
variables: [ 'requestedTokenCategory', 'requestedTokenCapability', 'requestedTokenCommitment' ],
},
},
sender: {
name: 'Send',
description: 'Send a non-fungible token to the provided receiver.',
icon: 'send',
},
},
requirements: {
roles: [
{
role: 'receiver',
slots: { min: 1, max: 1 },
},
{
role: 'sender',
slots: { min: 1, max: undefined },
},
],
},
transaction: 'requestNonfungibleTokensTransaction',
},
// NOTE: Sending value can be done without explicit template support.
// NOTE: This feature is explicitly defined in this template to demonstrate how versatile templates can be, and
// to ensure the feature is discoverable by the user from outputs that hold value.
sendSatoshis: {
name: 'Send Satoshis',
description: 'Sends a specific amount of Bitcoin Cash to a given recipient.',
icon: 'send',
roles: {
sender: {
requirements: {
variables: [ 'transferredSatoshis', 'recipientLockingscript' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'sender',
slots: { min: 1, max: 1 },
},
],
},
// Sending is only available for outputs that have sufficient satoshis on them.
// NOTE: Dust is enforced here according to standardness rules.
condition: '$(OP_INPUTINDEX OP_UTXOVALUE <dustLimit> OP_GREATERTHAN)',
transaction: 'transferSatoshisTransaction',
},
sendFungibleTokens: {
name: 'Send Fungible Tokens',
description: 'Send a specific amount of a fungible token to a given recipient.',
icon: 'send',
roles: {
sender: {
requirements: {
variables: [ 'transferredTokenCategory', 'transferredTokenAmount', 'recipientLockingscript' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'sender',
slots: { min: 1, max: 1 },
},
],
},
// Sending is only available for outputs that have fungible tokens on them.
condition: '$(OP_INPUTINDEX OP_UTXOTOKENAMOUNT <0> OP_GREATERTHAN)',
transaction: 'transferFungibleTokensTransaction',
},
sendNonfungibleTokens: {
name: 'Send a Non-fungible Token',
description: 'Send a non-fungible token to a given recipient.',
icon: 'send',
roles: {
sender: {
requirements: {
variables: [ 'transferredTokenCategory', 'transferredTokenCapability', 'transferredTokenCommitment', 'recipientLockingscript' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'sender',
slots: { min: 1, max: 1 },
},
],
},
// Sending is only available for outputs that have a non-fungible token on them.
condition: '$(OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_SIZE OP_NIP <32> OP_GREATERTHAN)',
transaction: 'transferNonfungibleTokensTransaction',
},
// NOTE: Burning tokens can be done without explicit template support.
// NOTE: This feature is explicitly defined in this template to demonstrate how versatile templates can be, and
// to ensure the feature is discoverable by the user from outputs that hold tokens.
burnFungibleTokens: {
name: 'Delete Fungible Tokens',
description: 'Permanently and irreversibly deletes one or more fungible tokens.',
icon: 'burn',
roles: {
owner: {
requirements: {
variables: [ 'burnedTokenCategory', 'burnedTokenAmount' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'owner',
slots: { min: 1, max: 1 },
},
],
},
// Burning is only available for outputs that have fungible tokens on them.
condition: '$(OP_INPUTINDEX OP_UTXOTOKENAMOUNT <0> OP_GREATERTHAN)',
transaction: 'burnFungibleTokensTransaction',
},
burnNonfungibleTokens: {
name: 'Delete a Non-fungible Token',
description: 'Permanently and irreversibly deletes one non-fungible token.',
icon: 'burn',
roles: {
owner: {
requirements: {
variables: [ 'burnedTokenCategory', 'burnedTokenCapability', 'burnedTokenCommitment' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'owner',
slots: { min: 1, max: 1 },
},
],
},
// Burning is only available for outputs that have non-fungible tokens on them.
condition: '$(OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_SIZE OP_NIP <32> OP_GREATERTHAN)',
transaction: 'burnNonfungibleTokenTransaction',
},
sign: {
name: 'Sign Message',
description: 'Signs a provided message using the Bitcoin message signing protocol.',
icon: 'sign',
roles: {
owner: {
requirements: {
variables: [ 'messageToSign' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'owner',
slots: { min: 1, max: 1 },
},
],
},
data: [ 'messageSignature' ],
},
verify: {
name: 'Verify Message Signature',
description: 'Verifies a provided message signature according to the Bitcoin message signing protocol.',
icon: 'verify',
roles: {
owner: {
requirements: {
variables: [ 'messageSignature', 'messageToVerify' ],
secrets: [ 'ownerKey' ],
},
},
},
requirements: {
roles: [
{
role: 'owner',
slots: { min: 1, max: 1 },
},
],
},
data: [ 'messageSignatureValidity' ],
},
},
// Define a set of data that can be used in this template.
data: {
messageSignature: {
// Evaluate CashASM expression to get the signature needed.
// NOTE: Pushes the prefix and message, then concatenates them together to form the data to sign.
// NOTE: In libauth today, it seems that this is done by defining the signature as a variable, and tying it to the key and message,
// and so this is different
// TODO: Check with Jason and see if there is any reason why this cannot be done like this.
value: '$(<messagePrefix> <message> OP_CAT <ownerKey.data_signature.top_stack_element>)',
type: 'bytes',
hint: 'signature',
},
messageSignatureValidity: {
// Evaluate the validity of the message with the owners public key.
value: '$(<messageSignature> <messagePrefix> <message> OP_CAT <ownerKey.public_key> OP_CHECKDATASIG)',
type: 'integer',
hint: 'script_boolean',
},
},
// Define a set of transactions that can be used in this template.
transactions: {
receiveTransaction: {
name: 'Transfer request',
description: 'Transfer request for an unspecified amount of cash and/or tokens.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description: 'Received an unspecified amount of cash and/or tokens.',
icon: 'receive',
},
sender: {
name: 'Sent',
description: 'Sent an unspecified amount of cash and/or tokens.',
icon: 'send',
},
},
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no inputs required, but the engine should detect that there is not sufficient input value to
// match the output and thus generate an invitation to participate in this action.
// When the invitation is shared, the other parties can add as many inputs and change outputs as needed.
inputs: [],
outputs: [
{
output: 'receiveOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
requestSatoshisTransaction: {
name: 'Transfer request',
description: 'Transfer request for $(<requestedSatoshis>) satoshis.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description: 'Received $(<requestedSatoshis>) satoshis.',
icon: 'receive',
},
sender: {
name: 'Sent',
description: 'Sent $(<requestedSatoshis>) satoshis.',
icon: 'send',
},
},
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no inputs required, but the engine should detect that there is not sufficient input value to
// match the output and thus generate an invitation to participate in this action.
// When the invitation is shared, the other party can add as many inputs and outputs as needed since this transaction is composable.
inputs: [],
outputs: [
{
output: 'requestSatoshisOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
requestFungibleTokensTransaction: {
name: 'Transfer request',
description:
'Transfer request for $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description:
'Received $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens.',
icon: 'receive',
},
sender: {
name: 'Sent',
description:
'Sent $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens.',
icon: 'send',
},
},
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no inputs required, but the engine should detect that there is not sufficient input value to
// match the output and thus generate an invitation to participate in this action.
// When the invitation is shared, the other party can add as many inputs and outputs as needed since this transaction is composable.
inputs: [],
outputs: [
{
output: 'requestFungibleTokensOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
requestNonfungibleTokensTransaction: {
name: 'Transfer request',
description:
'Transfer request for one non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<requestedTokenCategory.symbol>) token, with $(<requestedTokenCommitment>) commitment.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description:
'Received one non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<requestedTokenCategory.symbol>) token, with $(<requestedTokenCommitment>) commitment.',
icon: 'receive',
},
sender: {
name: 'Sent',
description:
'Sent the requested non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<requestedTokenCategory.symbol>) token, with $(<requestedTokenCommitment>) commitment.',
icon: 'send',
},
},
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no inputs required, but the engine should detect that there is not sufficient input value to
// match the output and thus generate an invitation to participate in this action.
// When the invitation is shared, the other party can add as many inputs and outputs as needed since this transaction is composable.
inputs: [],
outputs: [
{
output: 'requestNonfungibleTokensOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
transferSatoshisTransaction: {
name: 'Satoshis Transferred',
description: '$(<transferredSatoshis>) satoshis were transferred to a recipient.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description: 'Received $(<transferredSatoshis>) satoshis.',
icon: 'receive',
},
sender: {
name: 'Sent',
description: 'Sent $(<transferredSatoshis>) satoshis.',
icon: 'send',
},
},
// Enforce the inputs and outputs required by the transaction.
// NOTE: The input is provided from the action since it is only available on outputs with satoshis.
inputs: [],
outputs: [
{
output: 'transferSatoshisOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
transferFungibleTokensTransaction: {
name: 'Fungible Tokens Transferred',
description:
'$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens were transferred to a recipient.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description:
'Received $(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens.',
icon: 'receive',
},
sender: {
name: 'Sent',
description:
'Sent $(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens.',
icon: 'send',
},
},
// Enforce the inputs and outputs required by the transaction.
// NOTE: The input is provided from the action since it is only available on outputs with fungible tokens.
inputs: [],
outputs: [
{
output: 'transferFungibleTokensOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
transferNonfungibleTokensTransaction: {
name: 'Non-fungible Token Transferred',
description:
'One non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<transferredTokenCategory.symbol>) token was transferred to a recipient, with $(<transferredTokenCommitment>) commitment.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description:
'Received one non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<transferredTokenCategory.symbol>) token, with $(<transferredTokenCommitment>) commitment.',
icon: 'receive',
},
sender: {
name: 'Sent',
description:
'Sent one non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<transferredTokenCategory.symbol>) token, with $(<transferredTokenCommitment>) commitment.',
icon: 'send',
},
},
// Enforce the inputs and outputs required by the transaction.
// NOTE: The input is provided from the action since it is only available on outputs with a non-fungible token.
inputs: [],
outputs: [
{
output: 'transferNonfungibleTokenOutput',
index: undefined,
},
],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
burnFungibleTokensTransaction: {
name: 'Deleted fungible tokens',
description:
'Permanently and irreversibly deleted $(<burnedTokenAmount> <burnedTokenCategory.decimalsFactor> OP_DIV).$(<burnedTokenAmount> <burnedTokenCategory.decimalsFactor> OP_MOD) $(<burnedTokenCategory.symbol>) tokens.',
icon: 'burn',
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no defined outputs as any non-burned value is automatically returned as change.
inputs: [
{
input: 'burnFungibleTokensInput',
index: undefined,
},
],
outputs: [],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
burnNonfungibleTokenTransaction: {
name: 'Deleted non fungible token',
description:
'Permanently and irreversibly deleted a non-fungible $(<burnedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <burnedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) (<burnedTokenCategory.symbol>) token, with $(<burnedTokenCommitment>) commitment.',
icon: 'burn',
// Inputs and outputs that must exist in the transaction.
// NOTE: There is no defined outputs as any non-burned value is automatically returned as change.
inputs: [
{
input: 'burnNonfungibleTokenInput',
index: undefined,
},
],
outputs: [],
// Standard transaction without a locktime.
version: 2,
locktime: 0,
// ...
composable: true,
},
},
// Define a set of outputs that can be used within transactions in this template.
outputs: {
receiveOutput: {
name: 'Recipient output',
description: 'Transferred an unspecified amount of cash and/or tokens to a recipient.',
icon: 'receive',
roles: {
receiver: {
name: 'Received',
description: 'Received an unspecified amount of cash and/or tokens.',
},
sender: {
name: 'Sent',
description: 'Sent an unspecified amount of cash and/or tokens.',
},
},
// Defines how the requested funds should be locked.
lockscript: 'receivingLockingScript',
},
requestSatoshisOutput: {
name: 'Recipient output',
description: 'Transferred $(<requestedSatoshis>) satoshis to a recipient.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description: 'Received $(<requestedSatoshis>) satoshis.',
},
sender: {
name: 'Sent',
description: 'Sent $(<requestedSatoshis>) satoshis.',
},
},
// Defines how the requested funds should be locked.
lockscript: 'receivingLockingScript',
// Require the specified number of satoshis and no tokens.
valueSatoshis: '$(<requestedSatoshis>)',
token: null,
},
requestFungibleTokensOutput: {
name: 'Recipient output',
description:
'Transferred $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens to a recipient.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description:
'Received $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens.',
},
sender: {
name: 'Sent',
description:
'Sent $(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_DIV).$(<requestedTokenAmount> <requestedTokenCategory.decimalsFactor> OP_MOD) $(<requestedTokenCategory.symbol>) tokens.',
},
},
// Defines how the requested funds should be locked.
lockscript: 'receivingLockingScript',
// Require a flat 1000 satoshis to ensure the fungible tokens remains transferrable.
valueSatoshis: '1000',
// Require only the specified amount and type of fungible tokens.
// NOTE: This can be composed with a request for a non-fungible token, but will always result in two separate outputs.
token: {
category: '$(<requestedTokenCategory>)',
amount: '$(<requestedTokenAmount>)',
nft: null,
},
},
requestNonfungibleTokensOutput: {
name: 'Recipient output',
description:
'Transferred one non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<requestedTokenCategory.symbol>) token to a recipient, with $(<requestedTokenCommitment>) commitment.',
icon: 'request',
roles: {
receiver: {
name: 'Received',
description:
'Received one non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<requestedTokenCategory.symbol>) token, with $(<requestedTokenCommitment>) commitment.',
},
sender: {
name: 'Sent',
description:
'Sent the requested non-fungible $(<requestedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <requestedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<requestedTokenCategory.symbol>) token, with $(<requestedTokenCommitment>) commitment.',
},
},
// Defines how the requested funds should be locked.
lockscript: 'receivingLockingScript',
// Require a flat 1000 satoshis to ensure the non-fungible token remains transferrable.
valueSatoshis: '1000',
// Require only an NFT with specified category, capability and commitment.
// NOTE: This can be composed with a request for fungible token amounts, but will always result in two separate outputs.
token: {
category: '$(<requestedTokenCategory>)',
amount: null,
nft: {
capability: '$(<requestedTokenCapability>)',
commitment: '$(<requestedTokenCommitment>)',
},
},
},
transferSatoshisOutput: {
name: 'Recipient output',
description: 'Transferred $(<transferredSatoshis>) satoshis to a recipient.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description: 'Received $(<transferredSatoshis>) satoshis.',
},
sender: {
name: 'Sent',
description: 'Sent $(<transferredSatoshis>) satoshis.',
},
},
// Use the recipients lockscript.
lockscript: 'sendingLockingscript',
// Set the amount of satoshis to transfer.
valueSatoshis: '$(<transferredSatoshis>)',
},
transferFungibleTokensOutput: {
name: 'Recipient output',
description:
'Transferred $(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens to a recipient.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description:
'Received $(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens.',
},
sender: {
name: 'Sent',
description:
'Sent $(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_DIV).$(<transferredTokenAmount> <transferredTokenCategory.decimalsFactor> OP_MOD) $(<transferredTokenCategory.symbol>) tokens.',
},
},
// Use the recipients lockscript.
lockscript: 'sendingLockingscript',
// Set the amount of fungible tokens to transfer.
token: {
category: '$(<transferredTokenCategory>)',
amount: '$(<transferredTokenAmount>)',
},
},
transferNonfungibleTokenOutput: {
name: 'Recipient output',
description:
'Transferred one non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<transferredTokenCategory.symbol>) token to a recipient, with $(<transferredTokenCommitment>) commitment.',
icon: 'send',
roles: {
receiver: {
name: 'Received',
description:
'Received one non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<transferredTokenCategory.symbol>) token, with $(<transferredTokenCommitment>) commitment.',
},
sender: {
name: 'Sent',
description:
'Sent one non-fungible $(<transferredTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <transferredTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $(<transferredTokenCategory.symbol>) token, with $(<transferredTokenCommitment>) commitment.',
},
},
// Use the recipients lockscript.
lockscript: 'sendingLockingscript',
// Set the non-fungible token to transfer.
token: {
category: '$(<transferredTokenCategory>)',
nft: {
capability: '$(<transferredTokenCapability>)',
commitment: '$(<transferredTokenCommitment>)',
},
},
},
},
inputs: {
burnFungibleTokensInput: {
name: 'Deleted fungible tokens',
description: 'Permanently and irreversibly deleted $(<burnedTokenAmount>) $(<burnedTokenCategory>).',
icon: 'burn',
// Define which unlocking script unlocks this input.
unlockingScript: 'unlockP2PKH',
// Require a fungible token of the requested token category, with an amount larger than or equal to the requested amount to burn.
token: {
category: '$(<burnedTokenCategory>)',
amount: '$(<this.fungibleTokenAmount> <burnedTokenAmount> OP_GREATERTHANOREQUAL OP_IF <this.fungibleTokens> OP_ENDIF)',
},
// Ignore the burned token amount when determining change for this output.
// NOTE: The engine must check that this does not exceed the input value.
omitChangeAmounts: {
fungibleTokens: '${<burnedTokenAmount>}',
},
},
burnNonfungibleTokenInput: {
name: 'Deleted non-fungible token',
description:
'Permanently and irreversibly burned one non-fungible $(<burnedTokenCapability> <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <burnedTokenCapability> <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) token of category $(<burnedTokenCategory>), with a $(<burnedTokenCommitment>) commitment.',
icon: 'burn',
// Define which unlocking script unlocks this input.
unlockingScript: 'unlockP2PKH',
// Require a non-fungible token of the specified category, capability and commitment to burn.
token: {
category: '$(<burnedTokenCategory>)',
nft: {
capability: '$(<burnedTokenCapability>)',
commitment: '$(<burnedTokenCommitment>)',
},
},
// Ignore the burned token when determining change for this output.
// NOTE: The engine must check that this does not exceed the input value.
omitChangeAmounts: {
nonfungibleTokens: 1,
},
},
},
// Define locking scripts used by this template.
// NOTE: Template supported wallets should automatically track all generated lockscripts for on-chain events.
lockingScripts: {
sendingLockingscript: {
// TODO: This currently describes outputs locked to this script, but all actions creating such outputs already have descriptions.
// This can either be dropped, or maybe more importantly, should be disambiguated so that lockscripts can provide separate
// descriptions for their script and the outputs that are locked to them. Leaving as a TODO for now and will address later.
name: 'Sent',
description: 'Funds sent to an external recipient',
icon: 'address',
// ...
lockingType: 'standard',
lockingScript: 'lockToRecipient',
// Indicate that the sent output does not belong to the initiating user.
// NOTE: These values default to false/empty, but added here for additional clarity.
actions: [],
state: [],
secrets: [],
balance: false,
selectable: false,
privacy: false,
},
receivingLockingScript: {
// NOTE: Outputs to this lockscript by external actors defaults to this description when detected on-chain.
name: 'Received',
description: 'Funds received without wallet coordination.',
icon: 'address',
// Defines how spending future received funds should be locked.
lockingType: 'standard',
lockingScript: 'lockP2PKH',
// Define a default unlocking script to be used when no action provided script is present.
unlockingScript: 'unlockP2PKH',
roles: {
receiver: {
// The only state that is required to be persisted when receiving funds is the owners private key.
// NOTE: This is defined as a secret to not leak when creating invitations for others to participate in request or send actions.
state: {
variables: [],
secrets: [ 'ownerKey' ],
},
// List actions that can be taken with the ownerKey for each address/lockscript.
actions: [
{
action: 'sign',
role: 'owner',
secrets: [ 'ownerKey' ],
},
{
action: 'verify',
role: 'owner',
secrets: [ 'ownerKey' ],
},
{
action: 'sendSatoshis',
role: 'sender',
secrets: [ 'ownerKey' ],
},
{
action: 'sendFungibleTokens',
role: 'sender',
secrets: [ 'ownerKey' ],
},
{
action: 'sendNonfungibleTokens',
role: 'sender',
secrets: [ 'ownerKey' ],
},
{
action: 'burnFungibleTokens',
role: 'sender',
secrets: [ 'ownerKey' ],
},
{
action: 'burnNonfungibleTokens',
role: 'sender',
secrets: [ 'ownerKey' ],
},
],
// Indicates how much of received funds should be part of a wallets total balance.
// NOTE: Evaluates in the context of the source transaction, with the current outputs technical data available under this.output.*
// NOTE: This can either evaluate to a boolean or a number. If evaluated to a boolean, then true means 100% of the output.
// NOTE: Since we know that the owner controls all assets, we short-cut the evaluations by setting true directly.
balance: {
satoshis: true,
fungibleTokens: true,
// nonFungibleToken: true,
},
// Indicate when received funds should be considered in automatic coin selection to meet input requirements of other transactions.
// NOTE: Evaluates in the context of the source transaction, with the current outputs technical data available under this.output.*
// NOTE: This evaluates to a boolean. If non-true, the output should not be considered for automatic coin selection.
// NOTE: Since we know that the secret key exist for the owner, this template short-cuts the evaluation by setting true directly.
selectable: true,
// Indicate that received funds are not considered good privacy.
// NOTE: This information allows wallets to make better privacy decisions, like avoid mixing private and non-private outputs.
privacy: false,
},
},
},
},
// Define a set of scripts that can be used in this template.
scripts: {
lockP2PKH: 'OP_DUP OP_HASH160 <$(<ownerKey.public_key> OP_HASH160)> OP_EQUALVERIFY OP_CHECKSIG',
unlockP2PKH: '<ownerKey.schnorr_signature.all_outputs> <ownerKey.public_key>',
lockToRecipient: '<recipientLockingscript>',
},
// TODO: Add icons
constants: {
dustLimit: {
name: 'Dust Limit',
description: 'Standard required minimum satoshis for Pay to Public Key Hash outputs.',
type: 'integer',
value: 546,
},
// Define a message prefix for use in arbitrary message signing.
messagePrefix: {
name: 'Message Prefix',
description: 'Standard message prefix used in the bitcoin signed message protocol.',
type: 'bytes',
// Value is enforced to the bitcoin signing magic string:
// "\x18Bitcoin Signed Message:\n"
value: '0x18426974636f696e205369676e6564204d6573736167653a0a',
},
},
// TODO: Add icons
variables: {
// Describe the secret private key.
ownerKey: {
name: 'Owners Private Key',
description: 'The private key used to authorize spending of received funds.',
type: 'bytes',
hint: 'private_key',
},
messageToSign: {
name: 'Message',
description: 'The text message to sign.',
type: 'string',
},
messageToVerify: {
name: 'Message',
description: 'The text message to verify.',
type: 'string',
},
messageSignature: {
name: 'Message Signature',
description: 'The signature for the message.',
type: 'bytes',
hint: 'signature',
},
// Describe the parameters used when requesting value.
requestedSatoshis: {
name: 'Requested Amount',
description: 'The Bitcoin Cash amount requested',
type: 'integer',
hint: 'satoshis',
},
requestedTokenCategory: {
name: 'Requested Token Category',
description: 'The token category requested',
type: 'bytes',
hint: 'token_category',
},
requestedTokenAmount: {
name: 'Requested Token Amount',
description: 'The fungible token amount requested',
type: 'integer',
hint: 'token_amount',
},
requestedTokenCapability: {
name: 'Requested Token Capability',
description: 'The non-fungible token capability requested',
type: 'bytes',
hint: 'token_capability',
},
requestedTokenCommitment: {
name: 'Requested Token Commitment',
description: 'The non-fungible token commitment requested',
type: 'bytes',
hint: 'token_commitment',
},
transferredTokenCategory: {
name: 'Sending Token Category',
description: 'The token category of the token(s) to send',
type: 'bytes',
hint: 'token_category',
},
transferredTokenAmount: {
name: 'Sending Token Amount',
description: 'The fungible token amount to send',
type: 'integer',
hint: 'token_amount',
},
transferredTokenCapability: {
name: 'Sending Token Capability',
description: 'The token capability for the non-fungible token to send',
type: 'bytes',
hint: 'token_capability',
},
transferredTokenCommitment: {
name: 'Sending Token Commitment',
description: 'The token commitment for the non-fungible token to send',
type: 'bytes',
hint: 'token_commitment',
},
burnedTokenCategory: {
name: 'Deleted Token Category',
description: 'The token category of the token(s) to delete',
type: 'bytes',
hint: 'token_category',
},
burnedTokenAmount: {
name: 'Deleted Token Amount',
description: 'The fungible token amount to delete',
type: 'integer',
hint: 'token_amount',
},
burnedTokenCapability: {
name: 'Deleted Token Capability',
description: 'The token capability for the non-fungible token to delete',
type: 'bytes',
hint: 'token_capability',
},
burnedTokenCommitment: {
name: 'Deleted Token Commitment',
description: 'The token commitment for the non-fungible token to delete',
type: 'bytes',
hint: 'token_commitment',
},
},
// Define a list of re-usable icons that can be used as part of metadata.
// NOTE: the actual icons are not embedded in the template but only referenced by hashes here,
// and can be distributed either as an asset pack or looked up through something like IPFS.
icons: [
{
name: 'wallet',
hash: '0000000000000000000000',
},
{
name: 'owner',
hash: '0000000000000000000000',
},
{
name: 'sender',
hash: '0000000000000000000000',
},
{
name: 'address',
hash: '0000000000000000000000',
},
{
name: 'receive',
hash: '0000000000000000000000',
},
{
name: 'request',
hash: '0000000000000000000000',
},
{
name: 'send',
hash: '0000000000000000000000',
},
{
name: 'burn',
hash: '0000000000000000000000',
},
{
name: 'sign',
hash: '0000000000000000000000',
},
{
name: 'verify',
hash: '0000000000000000000000',
},
],
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: [
{
role: 'receiver',
values: {
generated: {
ownerKey: 'KyRQa5pEXuzVcDwnXRLpYAascjchQW5DoxVRMbj4DTxS83573mz8',
},
variables: {
requestedSatoshis: 2000,
},
secrets: {
// This scenario does not carry any secrets.
},
inputs: [],
outputs: [
{
lockingBytecode: '76a91475c715ecb74178fe87933e57e947e5e92d904b8188ac',
valueSatoshis: 2000,
},
],
},
},
{
role: 'sender',
values: {
// The sender provides no raw data to the action.
generated: {},
variables: {},
secrets: {},
// The sender does provide input and change.
inputs: [
{
outpointTransactionHash: '4ef28553a31a266719e66ba97fee3aeecd6d1788f7ff6ab12f8ebceda49660c0',
outpointIndex: 0,
sequenceNumber: 0,
unlockingBytecode:
'41226b2be7c2890c8bbde2f79e79640e56d866843f2e822ec51c469019d13db04a422c9ee49f5eefd26fee24e91910edbbb032b90cc54c34da80a61e69b0ee3d22412103e7ab26c36a7c7f45b2c26f33c08b0fa43a633268700f47216646d4cb37ae5696',
},
],
outputs: [
{
lockingBytecode: '76a91475c715ecb74178fe87933e57e947e5e92d904b8188ac',
valueSatoshis: 2000,
},
],
},
},
],
// 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: {
receiver: {
name: 'Request',
description: 'Requested a specific amount of satoshis from one or more senders.',
icon: 'request',
},
sender: {
name: 'Send',
description: 'Sent a specific amount of satoshis to the provided receiver.',
icon: 'send',
},
},
transactions: [
{
transaction:
'0200000001c06096a4edbc8e2fb16afff788176dcdee3aee7fa96be61967261aa35385f24e000000006441226b2be7c2890c8bbde2f79e79640e56d866843f2e822ec51c469019d13db04a422c9ee49f5eefd26fee24e91910edbbb032b90cc54c34da80a61e69b0ee3d22412103e7ab26c36a7c7f45b2c26f33c08b0fa43a633268700f47216646d4cb37ae5696000000000267530300000000001976a91475c715ecb74178fe87933e57e947e5e92d904b8188acd0070000000000001976a91475c715ecb74178fe87933e57e947e5e92d904b8188ac00000000',
value: '',
},
],
},
},
],
};
export const p2pkhTemplateIdentifier = generateTemplateIdentifier(p2pkhTemplate);