UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

232 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transferPushDrop = transferPushDrop; exports.inputPushDrop = inputPushDrop; const sdk_1 = require("@bsv/sdk"); const src_1 = require("../../src"); const TestUtilsWalletStorage_1 = require("../utils/TestUtilsWalletStorage"); describe('pushdrop example tests', () => { jest.setTimeout(99999999); test('0 pushdrop', async () => { if (src_1.Setup.noEnv('main')) return; await transferPushDrop(); }); }); /** * Example of moving satoshis from one wallet to another using the BRC29 script template. * * This example can be run by the following command: * * ```bash * npx tsx brc29.ts * ``` * * Combine this with the [balances](./README.md#function-balances) example to observe satoshis being transfered between * two wallets. * * @publicbody */ async function transferPushDrop() { // obtain the secrets environment for the testnet network. const env = src_1.Setup.getEnv('main'); // setup1 will be the sending wallet using the rootKey associated with identityKey, which is the default. const setup1 = await src_1.Setup.createWalletClient({ env }); // setup2 will be the receiving wallet using the rootKey associated with identityKey2 const setup2 = setup1; // create a new transaction with an output for setup2 in the amount of 42 satoshis. const o = await outputPushDrop(setup1, setup2.identityKey, 42); // use setup2 to consume the new output to demonstrate unlocking the output and adding it to the wallet's "change" outputs. await inputPushDrop(setup2, o); await setup1.wallet.destroy(); await setup2.wallet.destroy(); } async function outputPushDrop(setup, toIdentityKey, satoshis) { const t = new sdk_1.PushDrop(setup.wallet); const protocol = [2, 'pushdropexample']; const keyId = '7'; const lock = await t.lock([ [1, 2, 3], [4, 5, 6] ], protocol, keyId, toIdentityKey, false, true, 'before'); const lockingScript = lock.toHex(); // Use this label the new transaction can be found by `listActions` and as a "description" value. const label = 'outputPushDrop'; // This call to `createAction` will create a new funded transaction containing the new output, // as well as sign and broadcast the transaction to the network. const car = await setup.wallet.createAction({ outputs: [ // Explicitly specify the new output to be created. // When outputs are explictly added to an action they must be funded: // Typically, at least one "change" input will automatically be added to fund the transaction, // and at least one output will be added to recapture excess funding. { lockingScript, satoshis, outputDescription: label, tags: ['relinquish'], customInstructions: JSON.stringify({ protocol, keyId, counterparty: toIdentityKey, type: 'PushDrop' }) } ], options: { // Turn off automatic output order randomization to avoid having to figure out which output is the explicit one. // It will always be output zero. randomizeOutputs: false, // This example prefers to immediately wait for the new transaction to be broadcast to the network. // Typically, most production applications benefit from performance gains when broadcasts are handled in the background. acceptDelayedBroadcast: true }, labels: [label], description: label }); if (car.sendWithResults.some(r => r.status === 'failed')) throw new Error('failed to send output creating transaction'); // Both the "tx" and "txid" results are expected to be valid when an action is created that does not need explicit input signing, // and when the "signAndProcess" option is allowed to default to true. // The `Beef` class is used here to decode the AtomicBEEF binary format of the new transaction. const beef = sdk_1.Beef.fromBinary(car.tx); // The outpoint string is constructed from the new transaction's txid and the output index: zero. const outpoint = `${car.txid}.0`; (0, TestUtilsWalletStorage_1.logger)(` outputPushDrop to ${toIdentityKey} outpoint ${outpoint} satoshis ${satoshis} BEEF ${beef.toHex()} ${beef.toLogString()} `); // Return the bits and pieces of the new output created. return { beef, outpoint, fromIdentityKey: setup.identityKey, satoshis, protocol, keyId }; } /** * Consume a PushDrop output. * * To spend a PushDrop output a transaction input must be created and signed using the * associated private key. * * In this example, an initial `createAction` call constructs the overall shape of a * new transaction, returning a `signableTransaction`. * * The `tx` property of the `signableTransaction` should be parsed using * the standard `Beef` class. Note that it is not an ordinary AtomicBEEF for the * simple reason that the transaction has not yet been fully signed. * * You can either use the method shown here to obtain a signable `Transaction` object * from this beef or you can use the `Transaction.fromAtomicBEEF` method. * * To sign an input, set the corresponding input's `unlockingScriptTemplate` to an appropriately * initialized unlock object and call the `Transaction` `sign` method. * * Once signed, capture the input's now valid `unlockingScript` value and convert it to a hex string. * * @param {SetupWallet} setup The setup context which will consume a PushDrop output as an input to a new transaction transfering * the output's satoshis to the "change" managed by the context's wallet. * @param {Beef} outputPushDrop.beef - An object proving the validity of the new output where the last transaction contains the new output. * @param {string} outputPushDrop.outpoint - The txid and index of the outpoint in the format `${txid}.${index}`. * @param {string} outputPushDrop.fromIdentityKey - The public key that locked the output. * @param {number} outputPushDrop.satoshis - The amount assigned to the output. * * @publicbody */ async function inputPushDrop(setup, outputPushDrop) { const { protocol, keyId, fromIdentityKey, satoshis, beef: inputBeef, outpoint } = outputPushDrop; const { keyDeriver } = setup; const t = new sdk_1.PushDrop(setup.wallet); // Construct an "unlock" object which is then associated with the input to be signed // such that when the "sign" method is called, a signed "unlockingScript" is computed for that input. const unlock = t.unlock(protocol, keyId, fromIdentityKey, 'single', false, satoshis); const label = 'inputPushDrop'; /** * Creating an action with an input that requires it's own signing template is a two step process. * The call to createAction must include only the expected maximum script length of the unlockingScript. * This causes a "signableTransaction" to be returned instead of a completed "txid" and "tx". */ const car = await setup.wallet.createAction({ /** * An inputBEEF is always required when there are explicit inputs to the new action. * This beef must include each transaction with a corresponding outpoint txid. * Unlike an AtomicBEEF, inputBEEF validates the transactions containing the outpoints, * and may contain multiple unrelated transaction subtrees. */ inputBEEF: inputBeef.toBinary(), inputs: [ { outpoint, // The value of 73 is a constant for the PushDrop template. // You could use the `unlock.estimateLength` method to obtain it. // Or a quick look at the PushDrop source code to confirm it. unlockingScriptLength: 73, inputDescription: label } ], labels: [label], description: label }); /** * Here is the essense of using `signAction` and custom script template: * * The `tx` property of the `signableTransaction` result can be parsed using * the standard `Beef` class, but it is not an ordinary valid AtomicBEEF for the * simple reason that the transaction has not been fully signed. * * You can either use the method shown here to obtain a signable `Transaction` object * from this beef or you can use the `Transaction.fromAtomicBEEF` method. * * To sign an input, set the corresponding input's `unlockingScriptTemplate` to an appropriately * initialized unlock object and call the `Transaction` `sign` method. * * Once signed, capture the now valid `unlockingScript` valoue for the input and convert it to a hex string. */ const st = car.signableTransaction; const beef = sdk_1.Beef.fromBinary(st.tx); const tx = beef.findAtomicTransaction(beef.txs.slice(-1)[0].txid); tx.inputs[0].unlockingScriptTemplate = unlock; await tx.sign(); const unlockingScript = tx.inputs[0].unlockingScript.toHex(); /** * Note that the `signArgs` use the `reference` property of the `signableTransaction` result to * identify the `createAction` result to finish processing and optionally broadcasting. */ const signArgs = { reference: st.reference, spends: { 0: { unlockingScript } }, options: { // Force an immediate broadcast of the signed transaction. acceptDelayedBroadcast: true } }; /** * Calling `signAction` completes the action creation process when inputs must be signed * using specific script templates. */ const sar = await setup.wallet.signAction(signArgs); if (sar.sendWithResults.some(r => r.status === 'failed')) throw new Error('failed to send output creating transaction'); // This completes the example by logging evidence of what was created. { const beef = sdk_1.Beef.fromBinary(sar.tx); const txid = sar.txid; (0, TestUtilsWalletStorage_1.logger)(` inputP2PKH to ${setup.identityKey} input's outpoint ${outpoint} satoshis ${satoshis} txid ${txid} BEEF ${beef.toHex()} ${beef.toLogString()} `); } } //# sourceMappingURL=pushdrop.test.js.map