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 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: "$( OP_CAT )", type: "bytes", hint: "signature", }, messageSignatureValidity: { // Evaluate the validity of the message with the owners public key. value: "$( OP_CAT 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 $() satoshis.", icon: "request", roles: { receiver: { name: "Received", description: "Received $() satoshis.", icon: "receive", }, sender: { name: "Sent", description: "Sent $() 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 $( OP_DIV).$( OP_MOD) $() tokens.", icon: "request", roles: { receiver: { name: "Received", description: "Received $( OP_DIV).$( OP_MOD) $() tokens.", icon: "receive", }, sender: { name: "Sent", description: "Sent $( OP_DIV).$( OP_MOD) $() 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 $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() commitment.', icon: "request", roles: { receiver: { name: "Received", description: 'Received one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() commitment.', icon: "receive", }, sender: { name: "Sent", description: 'Sent the requested non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() 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: "$() satoshis were transferred to a recipient.", icon: "send", roles: { receiver: { name: "Received", description: "Received $() satoshis.", icon: "receive", }, sender: { name: "Sent", description: "Sent $() 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: "$( OP_DIV).$( OP_MOD) $() tokens were transferred to a recipient.", icon: "send", roles: { receiver: { name: "Received", description: "Received $( OP_DIV).$( OP_MOD) $() tokens.", icon: "receive", }, sender: { name: "Sent", description: "Sent $( OP_DIV).$( OP_MOD) $() 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 $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token was transferred to a recipient, with $() commitment.', icon: "send", roles: { receiver: { name: "Received", description: 'Received one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() commitment.', icon: "receive", }, sender: { name: "Sent", description: 'Sent one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() 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 $( OP_DIV).$( OP_MOD) $() 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 $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) () token, with $() 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 $() satoshis to a recipient.", icon: "request", roles: { receiver: { name: "Received", description: "Received $() satoshis.", }, sender: { name: "Sent", description: "Sent $() satoshis.", }, }, // Defines how the requested funds should be locked. lockscript: "receivingLockingScript", // Require the specified number of satoshis and no tokens. valueSatoshis: "$()", token: null, }, requestFungibleTokensOutput: { name: "Recipient output", description: "Transferred $( OP_DIV).$( OP_MOD) $() tokens to a recipient.", icon: "request", roles: { receiver: { name: "Received", description: "Received $( OP_DIV).$( OP_MOD) $() tokens.", }, sender: { name: "Sent", description: "Sent $( OP_DIV).$( OP_MOD) $() 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: "$()", amount: "$()", nft: null, }, }, requestNonfungibleTokensOutput: { name: "Recipient output", description: 'Transferred one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token to a recipient, with $() commitment.', icon: "request", roles: { receiver: { name: "Received", description: 'Received one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token, with $() commitment.', }, sender: { name: "Sent", description: 'Sent the requested non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token, with $() 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: "$()", amount: null, nft: { capability: "$()", commitment: "$()", }, }, }, transferSatoshisOutput: { name: "Recipient output", description: "Transferred $() satoshis to a recipient.", icon: "send", roles: { receiver: { name: "Received", description: "Received $() satoshis.", }, sender: { name: "Sent", description: "Sent $() satoshis.", }, }, // Use the recipients lockscript. lockscript: "sendingLockingscript", // Set the amount of satoshis to transfer. valueSatoshis: "$()", }, transferFungibleTokensOutput: { name: "Recipient output", description: "Transferred $( OP_DIV).$( OP_MOD) $() tokens to a recipient.", icon: "send", roles: { receiver: { name: "Received", description: "Received $( OP_DIV).$( OP_MOD) $() tokens.", }, sender: { name: "Sent", description: "Sent $( OP_DIV).$( OP_MOD) $() tokens.", }, }, // Use the recipients lockscript. lockscript: "sendingLockingscript", // Set the amount of fungible tokens to transfer. token: { category: "$()", amount: "$()", }, }, transferNonfungibleTokenOutput: { name: "Recipient output", description: 'Transferred one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token to a recipient, with $() commitment.', icon: "send", roles: { receiver: { name: "Received", description: 'Received one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token, with $() commitment.', }, sender: { name: "Sent", description: 'Sent one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) $() token, with $() commitment.', }, }, // Use the recipients lockscript. lockscript: "sendingLockingscript", // Set the non-fungible token to transfer. token: { category: "$()", nft: { capability: "$()", commitment: "$()", }, }, }, }, inputs: { burnFungibleTokensInput: { name: "Deleted fungible tokens", description: "Permanently and irreversibly deleted $() $().", 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: "$()", amount: "$( OP_GREATERTHANOREQUAL OP_IF 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: "${}", }, }, burnNonfungibleTokenInput: { name: "Deleted non-fungible token", description: 'Permanently and irreversibly burned one non-fungible $( <0x02> OP_EQUAL OP_IF <"minting"> OP_ELSE <0x01> OP_EQUAL OP_IF <"mutable"> OP_ELSE <"immutable"> OP_ENDIF OP_ENDIF) token of category $(), with a $() 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: "$()", nft: { capability: "$()", commitment: "$()", }, }, // 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 <$( OP_HASH160)> OP_EQUALVERIFY OP_CHECKSIG", unlockP2PKH: " ", lockToRecipient: "", }, // 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);