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);