@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
232 lines • 10.5 kB
JavaScript
;
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