@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
417 lines (410 loc) • 20.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWalletPaymentOutput = createWalletPaymentOutput;
exports.createWalletPaymentAction = createWalletPaymentAction;
/* eslint-disable @typescript-eslint/no-unused-vars */
const sdk_1 = require("@bsv/sdk");
const index_all_1 = require("../../../src/index.all");
const TestUtilsWalletStorage_1 = require("../../utils/TestUtilsWalletStorage");
const dotenv_1 = __importDefault(require("dotenv"));
dotenv_1.default.config();
describe('walletLive test', () => {
jest.setTimeout(99999999);
const env = TestUtilsWalletStorage_1._tu.getEnv('test');
const myIdentityKey = env.identityKey;
const myRootKeyHex = env.devKeys[myIdentityKey];
if (!myIdentityKey || !myRootKeyHex)
throw new index_all_1.sdk.WERR_INVALID_OPERATION(`Requires a .env file with MY_${env.chain.toUpperCase()}_IDENTITY and corresponding DEV_KEYS entries.`);
const myIdentityKey2 = env.identityKey2;
if (!myIdentityKey2)
throw new index_all_1.sdk.WERR_INVALID_OPERATION(`Requires a .env file with MY_${env.chain.toUpperCase()}_IDENTITY2 and corresponding DEV_KEYS entries.`);
const myRootKeyHex2 = env.devKeys[myIdentityKey2];
if (!myRootKeyHex2)
throw new index_all_1.sdk.WERR_INVALID_OPERATION(`Requires a .env file with MY_${env.chain.toUpperCase()}_IDENTITY2 and corresponding DEV_KEYS entries.`);
let myCtx;
let myCtx2;
const ctxs = [];
let stagingStorage;
beforeAll(async () => {
myCtx = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex,
chain: env.chain
});
myCtx2 = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex2,
chain: env.chain
});
const connection = JSON.parse(process.env.TEST_CLOUD_MYSQL_CONNECTION || '');
const knex = TestUtilsWalletStorage_1._tu.createMySQLFromConnection(connection);
stagingStorage = (await TestUtilsWalletStorage_1._tu.createKnexTestWallet({
knex,
chain: 'test',
databaseName: 'staging_wallet_storage',
rootKeyHex: myRootKeyHex
})).activeStorage;
});
afterAll(async () => {
await stagingStorage.destroy();
await myCtx.storage.destroy();
for (const ctx of ctxs)
await ctx.storage.destroy();
});
test('1 set change outputs spendable false if not valid utxos', async () => {
// Check the list of outputs first using the debugger breakpoint, before updating spendable flags.
for (const { wallet, activeStorage: storage, services } of ctxs) {
const { invalidSpendableOutputs: notUtxos } = await confirmSpendableOutputs(storage, services);
const outputsToUpdate = notUtxos.map(o => ({
id: o.outputId,
satoshis: o.satoshis
}));
const total = outputsToUpdate.reduce((t, o) => t + o.satoshis, 0);
debugger;
// *** About set spendable = false for outputs ***/
for (const o of outputsToUpdate) {
await storage.updateOutput(o.id, { spendable: false });
}
}
});
test('2 review available change', async () => {
for (const { wallet, activeStorage: storage, services, userId } of ctxs) {
const { basketId } = (0, index_all_1.verifyOne)(await storage.findOutputBaskets({
partial: { userId, name: 'default' }
}));
const r = {};
for (const { name, txStatus } of [
{ name: 'completed', txStatus: ['completed'] },
{ name: 'nosend', txStatus: ['nosend'] },
{ name: 'unproven', txStatus: ['unproven'] },
{ name: 'failed', txStatus: ['failed'] },
{ name: 'sending', txStatus: ['sending'] },
{
name: 'unprocessed',
txStatus: ['unprocessed']
},
{ name: 'unsigned', txStatus: ['unsigned'] }
]) {
const or = {
txStatus,
outputs: await storage.findOutputs({
partial: { basketId, spendable: true },
txStatus
}),
outputCount: 0,
total: 0
};
or.total = or.outputs.reduce((t, o) => t + o.satoshis, 0);
or.outputCount = or.outputs.length;
or.outputs = [];
r[name] = or;
}
expect(r).toBeTruthy();
let log = '';
for (const [k, v] of Object.entries(r)) {
if (v.outputCount > 0)
log += `${k} count=${v.outputCount} total=${v.total}\n`;
}
console.log(log);
}
});
test.skip('3 abort incomplete transactions', async () => {
for (const { wallet, activeStorage: storage, services, userId } of ctxs) {
const txs = await storage.findTransactions({
partial: { userId },
status: ['unsigned']
});
const total = txs.reduce((s, t) => s + t.satoshis, 0);
debugger;
for (const tx of txs) {
await wallet.abortAction({ reference: tx.reference });
}
}
});
test.skip('4 create a wallet payment output', async () => {
const r = await createWalletPaymentAction({
toIdentityKey: '02bec52b12b8575f981cf38f3739ffbbfe4f6c6dbe4310d6384b6e97b122f0d087',
outputSatoshis: 100 * 1000,
keyDeriver: myCtx.keyDeriver,
wallet: myCtx.wallet,
logResult: true
});
});
test('5 pull out txid from BEEF', async () => {
const beefHex = '010101015c574f48257202b9bff1b14baaa31cea24b9132555216900c277566d440250c50200beef01fee100190003020602f1fdbfa55c7227d2d9f93b7c2b83596a8e336ced483c1616dd98e8a32054dc6307010102009602f0b0959d085cbfda1a0958f65882b1a2829d66853582c0a530586dd00e930100002a7f27bd83b7d490f6641bbd3a8bdeff31490c14430597302ba579392be33f730201000100000001faba5977e9d7894778490ad3d3cf3ff0144da2920f6b31869dbcc026b693061b000000006a473044022050b2a300cad0e4b4c5ecaf93445937f21f6ec61d0c1726ac46bfb5bc2419af2102205d53e70fbdb0d1181a3cb1ef437ae27a73320367fdb78e8cadbfcbf82054e696412102480166f272ee9b639317c16ee60a2254ece67d0c7190fedbd26d57ac30f69d65ffffffff1da861000000000000c421029b09fdddfae493e309d3d97b919f6ab2902a789158f6f78489ad903b7a14baeaac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a4735840423b7e26b5fd304a88f2ea28c9cf04d6c0a6c52a3174b69ea097039a355dbc6d95e702ac325c3f07518c9b4370796f90ad74e1c46304402206cd8228dd5102f7d8bd781e71dbf60df6559e90df9b79ed1c2b51d0316432f5502207a8713e899232190322dd4fdac6384f6b416ffa10b4196cdc7edbaf751b4a1156d7502000000000000001976a914ee8f77d351270123065a30a13e30394cbb4a6a2b88ace8030000000000001976a9147c8d0d51b07812872049e60e65a28d1041affc1f88ace8030000000000001976a914494c42ae91ebb8d4df662b0c2c98acfcbf14aff388ac93070000000000001976a9149619d3a2c3669335175d6fbd1d785719418cd69588acef030000000000001976a91435aabdafdc475012b7e2b7ab42e8f0fd9e8b665588ac59da0000000000001976a914c05b882ce290b3c19fbb0fca21e416f204d855a188acf3030000000000001976a9146ccff9f5e40844b784f1a68269afe30f5ec84c5d88accb340d00000000001976a914baf2186a8228a9581e0af744e28424343c6a464d88ace9030000000000001976a914a9c3b08f698df167c352f56aad483c907a0e64f488ac61140000000000001976a914f391b03543456ca68f3953b5ef4883f1299b4a2c88ac44c10500000000001976a914e6631bf6d96f93db48fb51daeace803ad805c09788ace9030000000000001976a9148cac2669fc696f5fb39aa59360a2cd20a6daffac88ac49b00400000000001976a9142c16b8a63604c66aa51f47f499024e327657ab5388acd7d50100000000001976a914ca5b56f03f796f55583c7cdd612c02f8d232669388ac42050000000000001976a914175a6812dbf2a550b1bf0d21594b96f9daf60d7988ac15040000000000001976a9147422a7237bb0fa77691047abf930e0274d193fe788ace9030000000000001976a9141a32c1c07dd4f9c632ce6b43dd28c8b27a37d81588ace8030000000000001976a914d9433de1883950578e9a29013aedb1e66d900bdc88ac39190000000000001976a9149fcdbc118b0114d2cc086a75eb14d880e3e25a9e88ac55390200000000001976a914cccf036ec7ae05690461c9d1be10193b6427055588ac1d010000000000001976a9148578396af7a6783824ff680315cc0a1375d9586e88acb3090000000000001976a9147c63cace8600f5400c8678cb2c843400c0c8ac2788acc55d0000000000001976a9148bf6991866b525f36dda54f7ca393c3a56cfff7188acc9100b00000000001976a914af41bf9bbf9d345f6b7cb37958d4cf43e88b17ef88acda040000000000001976a914ad818fcb671cc5b85dc22056d97a9b31aede4f3288ace8030000000000001976a91403ae9f7e41baee27ab7e66a323e73ee6801b5e1688ac59040000000000001976a9149f19356274a53ffdfb755bd81d40a97fe79b5e9b88ac10340000000000001976a914504dff507fccce4005f2d374cbdb6d5d493ceda288ac00000000000100000001f1fdbfa55c7227d2d9f93b7c2b83596a8e336ced483c1616dd98e8a32054dc63060000006b4830450221009bb61b5ec65cbcee0705cf757eba43e1716bfebf3ef976a09ffc926edee9ce6c022060606f5b5e59a6210067633a1263c23156d426feb1912cea480789beef62568741210208132e357b0d061848e779700eae5d69e5240a2503dd753a00e6cb3a8a920255ffffffff06e8030000000000001976a9149cedb88029c24f8bb9824628dfa0a023c1db5edc88acb40a0000000000001976a914da9b117c6880799eb3a0d0ccca252d7f11be240588ac35290000000000001976a914a8f814d3e2a2112bfe2f158f0596314a4379d45088ac54600000000000001976a91476f6f9a9ede3b7e496c921e6730be0af8d3fdfda88ac0e040000000000001976a91419a4615e24931e0e3b25e150fb362b56c5f4e89688ac253e0000000000001976a91435f0dcc5f8c47821a9d24d456b09995981cdb03f88ac00000000';
const beef = sdk_1.Beef.fromString(beefHex);
const btx = beef.findTxid('5c574f48257202b9bff1b14baaa31cea24b9132555216900c277566d440250c5');
console.log(`
tx: '${sdk_1.Utils.toHex(btx === null || btx === void 0 ? void 0 : btx.rawTx)}'
${beef.toLogString()}
`);
});
test('5z send a wallet payment from myCtx to second wallet', async () => {
const tauriRootKey = '1363ef9b14531a52648e1e7e7f430a10ceda1df8d514a2a75d8404094f14a649';
const tauriIdentityKey = sdk_1.PrivateKey.fromHex(tauriRootKey).toPublicKey().toString();
const r = await createWalletPaymentAction({
toIdentityKey: tauriIdentityKey,
outputSatoshis: 1000 * 1000,
keyDeriver: myCtx.keyDeriver,
wallet: myCtx.wallet,
logResult: true
});
const toCtx = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: tauriRootKey,
chain: env.chain
});
const args = {
tx: sdk_1.Utils.toArray(r.atomicBEEF, 'hex'),
outputs: [
{
outputIndex: r.vout,
protocol: 'wallet payment',
paymentRemittance: {
derivationPrefix: r.derivationPrefix,
derivationSuffix: r.derivationSuffix,
senderIdentityKey: r.senderIdentityKey
}
}
],
description: 'from tone wallet'
};
const rw = await toCtx.wallet.internalizeAction(args);
expect(rw.accepted).toBe(true);
});
test('6 send a wallet payment from myCtx to second wallet', async () => {
var _a;
const r = await createWalletPaymentAction({
toIdentityKey: myIdentityKey2,
outputSatoshis: (0, index_all_1.randomBytes)(1)[0] + 10,
keyDeriver: myCtx.keyDeriver,
wallet: myCtx.wallet,
logResult: true
});
const toCtx = myCtx2;
const args = {
tx: sdk_1.Utils.toArray(r.atomicBEEF, 'hex'),
outputs: [
{
outputIndex: r.vout,
protocol: 'wallet payment',
paymentRemittance: {
derivationPrefix: r.derivationPrefix,
derivationSuffix: r.derivationSuffix,
senderIdentityKey: r.senderIdentityKey
}
}
],
description: 'from live wallet'
};
const rw = await toCtx.wallet.internalizeAction(args);
expect(rw.accepted).toBe(true);
const beef = sdk_1.Beef.fromString(r.atomicBEEF);
const btx = beef.txs.slice(-1)[0];
const txid = btx.txid;
const req = await index_all_1.EntityProvenTxReq.fromStorageTxid(stagingStorage, txid);
expect((_a = req === null || req === void 0 ? void 0 : req.notify.transactionIds) === null || _a === void 0 ? void 0 : _a.length).toBe(2);
});
test('6a help setup my own wallet', async () => {
const privKey = sdk_1.PrivateKey.fromRandom();
const identityKey = privKey.toPublicKey().toString();
const log = `
// Add the following to .env file:
MY_TEST_IDENTITY = '${identityKey}'
DEV_KEYS = '{
"${identityKey}": "${privKey.toString()}"
}'
`;
console.log(log);
});
test('6b run liveWallet Monitor once', async () => {
const liveCtx = ctxs[0];
await liveCtx.monitor.runOnce();
expect(1 === 1);
});
test('6c send a wallet payment from live to your own wallet', async () => {
const myIdentityKey = env.identityKey;
const myRootKeyHex = env.devKeys[myIdentityKey];
if (!myIdentityKey || !myRootKeyHex)
throw new index_all_1.sdk.WERR_INVALID_OPERATION(`Requires a .env file with MY_${env.chain.toUpperCase()}_IDENTITY and corresponding DEV_KEYS entries.`);
const toIdentityKey = '02947542cf31c8d91c303bba8f981ee9595c414e63c185d495a97c558aa7b2e522';
const r = createWalletPaymentOutput({
toIdentityKey,
fromRootKeyHex: myRootKeyHex,
logResult: true
});
console.log(`\n${JSON.stringify(r)}\n`);
});
test('6d make atomicBEEF for known txid', async () => {
const txid = '6b9e8ed767ed6e6366527ddf8707637f3aaee1093085985c1dd04f347a3c25be';
const beef = new sdk_1.Beef();
beef.mergeTxidOnly(txid);
console.log(`
BEEF for known txid ${txid}
${sdk_1.Utils.toHex(beef.toBinaryAtomic(txid))}
`);
});
test('6e make atomicBEEF for txid from staging-dojo', async () => {
const myIdentityKey = env.identityKey;
const myRootKeyHex = env.devKeys[myIdentityKey];
const txid = '6b9e8ed767ed6e6366527ddf8707637f3aaee1093085985c1dd04f347a3c25be';
const beef = await stagingStorage.getBeefForTransaction(txid, {});
console.log(`
${beef.toLogString()}
AtomicBEEF for known txid ${txid}
${sdk_1.Utils.toHex(beef.toBinaryAtomic(txid))}
`);
});
test('7 test two client wallets', async () => {
if (!myIdentityKey2)
return;
if (myCtx)
await myCtx.storage.destroy();
myCtx = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex,
chain: env.chain
});
{
const u1 = await myCtx.storage.findOrInsertUser(myIdentityKey);
}
if (myCtx)
await myCtx.storage.destroy();
if (myCtx2)
await myCtx2.storage.destroy();
myCtx2 = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex2,
chain: env.chain
});
{
const u2 = await myCtx2.storage.findOrInsertUser(myIdentityKey2);
}
if (myCtx)
await myCtx.storage.destroy();
if (myCtx2)
await myCtx2.storage.destroy();
myCtx = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex,
chain: env.chain
});
myCtx2 = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
rootKeyHex: myRootKeyHex2,
chain: env.chain
});
{
const u1 = await myCtx.storage.findOrInsertUser(myIdentityKey);
const u2 = await myCtx2.storage.findOrInsertUser(myIdentityKey2);
expect(u1.user.userId).not.toBe(u2.user.userId);
}
});
// End of describe
});
async function confirmSpendableOutputs(storage, services) {
var _a;
const invalidSpendableOutputs = [];
const users = await storage.findUsers({ partial: {} });
for (const { userId } of users) {
const defaultBasket = (0, index_all_1.verifyOne)(await storage.findOutputBaskets({ partial: { userId, name: 'default' } }));
const where = {
userId,
basketId: defaultBasket.basketId,
spendable: true
};
const outputs = await storage.findOutputs({ partial: where });
for (let i = outputs.length - 1; i >= 0; i--) {
const o = outputs[i];
const oid = (0, index_all_1.verifyId)(o.outputId);
if (o.spendable) {
let ok = false;
if (o.lockingScript && o.lockingScript.length > 0) {
const r = await services.getUtxoStatus((0, index_all_1.asString)(o.lockingScript), 'script');
if (r.status === 'success' && r.isUtxo && ((_a = r.details) === null || _a === void 0 ? void 0 : _a.length) > 0) {
const tx = await storage.findTransactionById(o.transactionId);
if (tx &&
tx.txid &&
r.details.some(d => d.txid === tx.txid && d.satoshis === o.satoshis && d.index === o.vout)) {
ok = true;
}
}
}
if (!ok) {
invalidSpendableOutputs.push(o);
}
}
}
}
return { invalidSpendableOutputs };
}
function createWalletPaymentOutput(args) {
const t = new index_all_1.ScriptTemplateBRC29({
derivationPrefix: (0, index_all_1.randomBytesBase64)(8),
derivationSuffix: (0, index_all_1.randomBytesBase64)(8),
keyDeriver: new sdk_1.CachedKeyDeriver(sdk_1.PrivateKey.fromString(args.fromRootKeyHex))
});
const lockingScript = t.lock(args.fromRootKeyHex, args.toIdentityKey);
const r = {
senderIdentityKey: t.params.keyDeriver.identityKey,
derivationPrefix: t.params.derivationPrefix,
derivationSuffix: t.params.derivationSuffix,
lockingScript: lockingScript.toHex()
};
if (args.logResult) {
console.log(`
// createWalletPaymentOutput
const r = {
senderIdentityKey: '${r.senderIdentityKey}',
derivationPrefix: '${r.derivationPrefix}',
derivationSuffix: '${r.derivationSuffix}'
lockingScript: '${r.lockingScript}'
}`);
}
return r;
}
async function createWalletPaymentAction(args) {
const { toIdentityKey, outputSatoshis, keyDeriver, wallet } = args;
const t = new index_all_1.ScriptTemplateBRC29({
derivationPrefix: (0, index_all_1.randomBytesBase64)(8),
derivationSuffix: (0, index_all_1.randomBytesBase64)(8),
keyDeriver
});
const createArgs = {
description: `pay ${args.toIdentityKey}`.slice(0, 50),
labels: ['wallet-payment'],
outputs: [
{
satoshis: outputSatoshis,
lockingScript: t.lock(keyDeriver.rootKey.toString(), toIdentityKey).toHex(),
outputDescription: `for ${args.toIdentityKey}`.slice(0, 50),
basket: 'wallet-paymnet',
tags: ['wp-out']
}
],
options: {
//acceptDelayedBroadcast: false,
randomizeOutputs: false,
signAndProcess: true
}
};
const cr = await wallet.createAction(createArgs);
const r = {
senderIdentityKey: keyDeriver.identityKey,
vout: 0,
txid: cr.txid,
derivationPrefix: t.params.derivationPrefix,
derivationSuffix: t.params.derivationSuffix,
atomicBEEF: sdk_1.Utils.toHex(cr.tx)
};
if (args.logResult) {
console.log(`
// createWalletPaymentAction
const r = {
senderIdentityKey: '${r.senderIdentityKey}',
vout: 0,
txid: '${r.txid}',
derivationPrefix: '${r.derivationPrefix}',
derivationSuffix: '${r.derivationSuffix}'
atomicBEEF: '${r.atomicBEEF}'
}`);
}
return r;
}
//# sourceMappingURL=walletLive.man.test.js.map