Files
xo-cli/tests/cli/mocks/template-p2pkh.ts
2026-04-20 12:26:35 +00:00

1452 lines
48 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);