@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
797 lines • 37.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Wallet = void 0;
exports.throwDummyReviewActions = throwDummyReviewActions;
const sdk_1 = require("@bsv/sdk");
const acquireDirectCertificate_1 = require("./signer/methods/acquireDirectCertificate");
const proveCertificate_1 = require("./signer/methods/proveCertificate");
const createAction_1 = require("./signer/methods/createAction");
const signAction_1 = require("./signer/methods/signAction");
const internalizeAction_1 = require("./signer/methods/internalizeAction");
const WalletSettingsManager_1 = require("./WalletSettingsManager");
const identityUtils_1 = require("./utility/identityUtils");
const generateChange_1 = require("./storage/methods/generateChange");
const utilityHelpers_1 = require("./utility/utilityHelpers");
const ScriptTemplateBRC29_1 = require("./utility/ScriptTemplateBRC29");
const types_1 = require("./sdk/types");
const WERR_errors_1 = require("./sdk/WERR_errors");
const WalletError_1 = require("./sdk/WalletError");
function isWalletSigner(args) {
return args['isWalletSigner'];
}
class Wallet {
constructor(argsOrSigner, services, monitor, privilegedKeyManager, makeLogger) {
/**
* If true, signableTransactions will include sourceTransaction for each input,
* including those that do not require signature and those that were also contained
* in the inputBEEF.
*/
this.includeAllSourceTransactions = true;
/**
* If true, txids that are known to the wallet's party beef do not need to be returned from storage.
*/
this.autoKnownTxids = false;
/**
* If true, beefs returned to the user may contain txidOnly transactions.
*/
this.returnTxidOnly = false;
/**
* For repeatability testing, set to an array of random numbers from [0..1).
*/
this.randomVals = undefined;
/** 2-minute cache of queryOverlay() results keyed by normalized query */
this._overlayCache = new Map();
const args = !isWalletSigner(argsOrSigner)
? argsOrSigner
: {
chain: argsOrSigner.chain,
keyDeriver: argsOrSigner.keyDeriver,
storage: argsOrSigner.storage,
services,
monitor,
privilegedKeyManager,
makeLogger
};
if (args.storage._authId.identityKey != args.keyDeriver.identityKey)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('storage', `authenticated as the same identityKey (${args.storage._authId.identityKey}) as the keyDeriver (${args.keyDeriver.identityKey}).`);
this.settingsManager = args.settingsManager || new WalletSettingsManager_1.WalletSettingsManager(this);
this.chain = args.chain;
this.lookupResolver =
args.lookupResolver ||
new sdk_1.LookupResolver({
networkPreset: (0, utilityHelpers_1.toWalletNetwork)(this.chain)
});
this.keyDeriver = args.keyDeriver;
this.storage = args.storage;
this.proto = new sdk_1.ProtoWallet(args.keyDeriver);
this.services = args.services;
this.monitor = args.monitor;
this.privilegedKeyManager = args.privilegedKeyManager;
this.makeLogger = args.makeLogger;
this.identityKey = this.keyDeriver.identityKey;
this.pendingSignActions = {};
this.userParty = `user ${this.getClientChangeKeyPair().publicKey}`;
this.beef = new sdk_1.BeefParty([this.userParty]);
this.trustSelf = 'known';
if (this.services) {
this.storage.setServices(this.services);
}
}
async destroy() {
await this.storage.destroy();
if (this.privilegedKeyManager)
await this.privilegedKeyManager.destroyKey();
}
getClientChangeKeyPair() {
const kp = {
privateKey: this.keyDeriver.rootKey.toString(),
publicKey: this.keyDeriver.rootKey.toPublicKey().toString()
};
return kp;
}
async getIdentityKey() {
return (await this.getPublicKey({ identityKey: true })).publicKey;
}
getPublicKey(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.getPublicKey(args);
}
return this.proto.getPublicKey(args);
}
revealCounterpartyKeyLinkage(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.revealCounterpartyKeyLinkage(args);
}
return this.proto.revealCounterpartyKeyLinkage(args);
}
revealSpecificKeyLinkage(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.revealSpecificKeyLinkage(args);
}
return this.proto.revealSpecificKeyLinkage(args);
}
encrypt(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.encrypt(args);
}
return this.proto.encrypt(args);
}
decrypt(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.decrypt(args);
}
return this.proto.decrypt(args);
}
createHmac(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.createHmac(args);
}
return this.proto.createHmac(args);
}
verifyHmac(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.verifyHmac(args);
}
return this.proto.verifyHmac(args);
}
createSignature(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.createSignature(args);
}
return this.proto.createSignature(args);
}
verifySignature(args, originator) {
if (args.privileged) {
if (!this.privilegedKeyManager) {
throw new Error('Privileged operations require the Wallet to be configured with a privileged key manager.');
}
return this.privilegedKeyManager.verifySignature(args);
}
return this.proto.verifySignature(args);
}
getServices() {
if (!this.services)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('services', 'valid in constructor arguments to be retreived here.');
return this.services;
}
/**
* @returns the full list of txids whose validity this wallet claims to know.
*
* @param newKnownTxids Optional. Additional new txids known to be valid by the caller to be merged.
*/
getKnownTxids(newKnownTxids) {
if (newKnownTxids) {
for (const txid of newKnownTxids)
this.beef.mergeTxidOnly(txid);
}
const r = this.beef.sortTxs();
const knownTxids = r.valid;
return knownTxids;
}
getStorageIdentity() {
const s = this.storage.getSettings();
return {
storageIdentityKey: s.storageIdentityKey,
storageName: s.storageName
};
}
validateAuthAndArgs(args, validate, logger) {
const vargs = validate(args, logger);
const auth = { identityKey: this.identityKey };
return { vargs, auth };
}
//////////////////
// List Methods
//////////////////
async listActions(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateListActionsArgs);
const r = await this.storage.listActions(vargs);
return r;
}
get storageParty() {
return `storage ${this.getStorageIdentity().storageIdentityKey}`;
}
async listOutputs(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateListOutputsArgs);
if (this.autoKnownTxids && !vargs.knownTxids) {
vargs.knownTxids = this.getKnownTxids();
}
const r = await this.storage.listOutputs(vargs);
if (r.BEEF) {
this.beef.mergeBeefFromParty(this.storageParty, r.BEEF);
r.BEEF = this.verifyReturnedTxidOnlyBEEF(r.BEEF);
}
return r;
}
async listCertificates(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateListCertificatesArgs);
const r = await this.storage.listCertificates(vargs);
return r;
}
//////////////////
// Certificates
//////////////////
async acquireCertificate(args, originator) {
sdk_1.Validation.validateOriginator(originator);
if (args.acquisitionProtocol === 'direct') {
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateAcquireDirectCertificateArgs);
vargs.subject = (await this.getPublicKey({
identityKey: true,
privileged: args.privileged,
privilegedReason: args.privilegedReason
})).publicKey;
try {
// Confirm that the information received adds up to a usable certificate...
// TODO: Clean up MasterCertificate to support decrypt on instance
const cert = new sdk_1.MasterCertificate(vargs.type, vargs.serialNumber, vargs.subject, vargs.certifier, vargs.revocationOutpoint, vargs.fields, vargs.keyringForSubject, vargs.signature);
await cert.verify();
// Verify certificate details
await sdk_1.MasterCertificate.decryptFields(this, vargs.keyringForSubject, vargs.fields, vargs.certifier, vargs.privileged, vargs.privilegedReason);
}
catch (eu) {
const e = WalletError_1.WalletError.fromUnknown(eu);
throw new WERR_errors_1.WERR_INVALID_PARAMETER('args', `valid encrypted and signed certificate and keyring from revealer. ${e.name}: ${e.message}`);
}
const r = await (0, acquireDirectCertificate_1.acquireDirectCertificate)(this, auth, vargs);
return r;
}
if (args.acquisitionProtocol === 'issuance') {
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateAcquireIssuanceCertificateArgs);
// Create a random nonce that the server can verify
const clientNonce = await (0, sdk_1.createNonce)(this, vargs.certifier);
// TODO: Consider adding support to request certificates from a certifier before acquiring a certificate.
const authClient = new sdk_1.AuthFetch(this);
// Create a certificate master keyring
// The certifier is able to decrypt these fields as they are the counterparty
const { certificateFields, masterKeyring } = await sdk_1.MasterCertificate.createCertificateFields(this, vargs.certifier, vargs.fields);
// Make a Certificate Signing Request (CSR) to the certifier
const response = await authClient.fetch(`${vargs.certifierUrl}/signCertificate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientNonce,
type: vargs.type,
fields: certificateFields,
masterKeyring
})
});
if (response.headers.get('x-bsv-auth-identity-key') !== vargs.certifier) {
throw new Error(`Invalid certifier! Expected: ${vargs.certifier}, Received: ${response.headers.get('x-bsv-auth-identity-key')}`);
}
const { certificate, serverNonce } = await response.json();
// Validate the server response
if (!certificate) {
throw new Error('No certificate received from certifier!');
}
if (!serverNonce) {
throw new Error('No serverNonce received from certifier!');
}
const signedCertificate = new sdk_1.Certificate(certificate.type, certificate.serialNumber, certificate.subject, certificate.certifier, certificate.revocationOutpoint, certificate.fields, certificate.signature);
// Validate server nonce
await (0, sdk_1.verifyNonce)(serverNonce, this, vargs.certifier);
// Verify the server included our nonce
const { valid } = await this.verifyHmac({
hmac: sdk_1.Utils.toArray(signedCertificate.serialNumber, 'base64'),
data: sdk_1.Utils.toArray(clientNonce + serverNonce, 'base64'),
protocolID: [2, 'certificate issuance'],
keyID: serverNonce + clientNonce,
counterparty: vargs.certifier
});
if (!valid)
throw new Error('Invalid serialNumber');
// Validate the certificate received
if (signedCertificate.type !== vargs.type) {
throw new Error(`Invalid certificate type! Expected: ${vargs.type}, Received: ${signedCertificate.type}`);
}
if (signedCertificate.subject !== this.identityKey) {
throw new Error(`Invalid certificate subject! Expected: ${this.identityKey}, Received: ${signedCertificate.subject}`);
}
if (signedCertificate.certifier !== vargs.certifier) {
throw new Error(`Invalid certifier! Expected: ${vargs.certifier}, Received: ${signedCertificate.certifier}`);
}
if (!signedCertificate.revocationOutpoint) {
throw new Error(`Invalid revocationOutpoint!`);
}
if (Object.keys(signedCertificate.fields).length !== Object.keys(certificateFields).length) {
throw new Error(`Fields mismatch! Objects have different numbers of keys.`);
}
for (const field of Object.keys(certificateFields)) {
if (!(field in signedCertificate.fields)) {
throw new Error(`Missing field: ${field} in certificate.fields`);
}
if (signedCertificate.fields[field] !== certificateFields[field]) {
throw new Error(`Invalid field! Expected: ${certificateFields[field]}, Received: ${signedCertificate.fields[field]}`);
}
}
await signedCertificate.verify();
// Test decryption works
await sdk_1.MasterCertificate.decryptFields(this, masterKeyring, certificate.fields, vargs.certifier);
// Store the newly issued certificate
return await (0, acquireDirectCertificate_1.acquireDirectCertificate)(this, auth, {
...certificate,
keyringRevealer: 'certifier',
keyringForSubject: masterKeyring,
privileged: vargs.privileged
});
}
throw new WERR_errors_1.WERR_INVALID_PARAMETER('acquisitionProtocol', `valid.${args.acquisitionProtocol} is unrecognized.`);
}
async relinquishCertificate(args, originator) {
sdk_1.Validation.validateOriginator(originator);
this.validateAuthAndArgs(args, sdk_1.Validation.validateRelinquishCertificateArgs);
const r = await this.storage.relinquishCertificate(args);
return { relinquished: true };
}
async proveCertificate(args, originator) {
originator = sdk_1.Validation.validateOriginator(originator);
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateProveCertificateArgs);
const r = await (0, proveCertificate_1.proveCertificate)(this, auth, vargs);
return r;
}
async discoverByIdentityKey(args, originator) {
sdk_1.Validation.validateOriginator(originator);
this.validateAuthAndArgs(args, sdk_1.Validation.validateDiscoverByIdentityKeyArgs);
const TTL_MS = 2 * 60 * 1000;
const now = Date.now();
// --- trustSettings cache (2 minutes) ---
let trustSettings = this._trustSettingsCache && this._trustSettingsCache.expiresAt > now
? this._trustSettingsCache.trustSettings
: undefined;
if (!trustSettings) {
const settings = await this.settingsManager.get();
trustSettings = settings.trustSettings;
this._trustSettingsCache = { trustSettings, expiresAt: now + TTL_MS };
}
const certifiers = trustSettings.trustedCertifiers.map(c => c.identityKey).sort();
// --- queryOverlay cache (2 minutes) ---
const cacheKey = JSON.stringify({
fn: 'discoverByIdentityKey',
identityKey: args.identityKey,
certifiers
});
let cached = this._overlayCache.get(cacheKey);
if (!cached || cached.expiresAt <= now) {
const value = await (0, identityUtils_1.queryOverlay)({ identityKey: args.identityKey, certifiers }, this.lookupResolver);
cached = { value, expiresAt: now + TTL_MS };
this._overlayCache.set(cacheKey, cached);
}
if (!cached.value) {
return { totalCertificates: 0, certificates: [] };
}
return (0, identityUtils_1.transformVerifiableCertificatesWithTrust)(trustSettings, cached.value);
}
async discoverByAttributes(args, originator) {
sdk_1.Validation.validateOriginator(originator);
this.validateAuthAndArgs(args, sdk_1.Validation.validateDiscoverByAttributesArgs);
const TTL_MS = 2 * 60 * 1000;
const now = Date.now();
// --- trustSettings cache (2 minutes) ---
let trustSettings = this._trustSettingsCache && this._trustSettingsCache.expiresAt > now
? this._trustSettingsCache.trustSettings
: undefined;
if (!trustSettings) {
const settings = await this.settingsManager.get();
trustSettings = settings.trustSettings;
this._trustSettingsCache = { trustSettings, expiresAt: now + TTL_MS };
}
const certifiers = trustSettings.trustedCertifiers.map(c => c.identityKey).sort();
// Normalize attributes for a stable cache key.
// If attributes is an object, sort its top-level keys; if it's an array, sort a shallow copy.
let attributesKey = args.attributes;
if (args.attributes && typeof args.attributes === 'object') {
const keys = Object.keys(args.attributes).sort();
attributesKey = JSON.stringify(args.attributes, keys);
}
// --- queryOverlay cache (2 minutes) ---
const cacheKey = JSON.stringify({
fn: 'discoverByAttributes',
attributes: attributesKey,
certifiers
});
let cached = this._overlayCache.get(cacheKey);
if (!cached || cached.expiresAt <= now) {
const value = await (0, identityUtils_1.queryOverlay)({ attributes: args.attributes, certifiers }, this.lookupResolver);
cached = { value, expiresAt: now + TTL_MS };
this._overlayCache.set(cacheKey, cached);
}
if (!cached.value) {
return { totalCertificates: 0, certificates: [] };
}
return (0, identityUtils_1.transformVerifiableCertificatesWithTrust)(trustSettings, cached.value);
}
verifyReturnedTxidOnly(beef, knownTxids) {
if (this.returnTxidOnly)
return beef;
const onlyTxids = beef.txs.filter(btx => btx.isTxidOnly).map(btx => btx.txid);
for (const txid of onlyTxids) {
if (knownTxids && knownTxids.indexOf(txid) >= 0)
continue;
const btx = beef.findTxid(txid);
const tx = this.beef.findAtomicTransaction(txid);
if (!tx)
throw new WERR_errors_1.WERR_INTERNAL(`unable to merge txid ${txid} into beef`);
beef.mergeTransaction(tx);
}
for (const btx of beef.txs) {
if (knownTxids && knownTxids.indexOf(btx.txid) >= 0)
continue;
if (btx.isTxidOnly)
throw new WERR_errors_1.WERR_INTERNAL(`remaining txidOnly ${btx.txid} is not known`);
}
return beef;
}
verifyReturnedTxidOnlyAtomicBEEF(beef, knownTxids) {
if (this.returnTxidOnly)
return beef;
const b = sdk_1.Beef.fromBinary(beef);
if (!b.atomicTxid)
throw new WERR_errors_1.WERR_INTERNAL();
return this.verifyReturnedTxidOnly(b, knownTxids).toBinaryAtomic(b.atomicTxid);
}
verifyReturnedTxidOnlyBEEF(beef) {
if (this.returnTxidOnly)
return beef;
const b = sdk_1.Beef.fromBinary(beef);
return this.verifyReturnedTxidOnly(b).toBinary();
}
logMakeLogger(method, args) {
var _a;
const logger = (_a = this.makeLogger) === null || _a === void 0 ? void 0 : _a.call(this, args['log']);
this.logMethodStart(method, logger);
return logger;
}
logMethodStart(method, logger) {
logger === null || logger === void 0 ? void 0 : logger.group(`Wallet ${method}`);
}
logResult(r, logger) {
var _a;
if (!logger)
return;
logger.groupEnd();
r['log'] = (_a = logger.flush) === null || _a === void 0 ? void 0 : _a.call(logger);
}
logWalletError(eu, logger) {
var _a;
if (!logger)
return;
logger.error('WalletError:', WalletError_1.WalletError.unknownToJson(eu));
(_a = logger.flush) === null || _a === void 0 ? void 0 : _a.call(logger);
}
//////////////////
// Actions
//////////////////
async createAction(args, originator) {
var _a;
var _b;
const logger = this.logMakeLogger(`createAction`, args);
try {
sdk_1.Validation.validateOriginator(originator);
if (!args.options)
args.options = {};
(_b = args.options).trustSelf || (_b.trustSelf = this.trustSelf);
if (this.autoKnownTxids && !args.options.knownTxids) {
args.options.knownTxids = this.getKnownTxids(args.options.knownTxids);
}
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateCreateActionArgs, logger);
logger === null || logger === void 0 ? void 0 : logger.log('validated args');
vargs.includeAllSourceTransactions = this.includeAllSourceTransactions;
if (this.randomVals && this.randomVals.length > 1) {
vargs.randomVals = [...this.randomVals];
}
const r = await (0, createAction_1.createAction)(this, auth, vargs);
logger === null || logger === void 0 ? void 0 : logger.log('action created');
if (r.tx) {
this.beef.mergeBeefFromParty(this.storageParty, r.tx);
}
if (r.tx) {
r.tx = this.verifyReturnedTxidOnlyAtomicBEEF(r.tx, (_a = args.options) === null || _a === void 0 ? void 0 : _a.knownTxids);
logger === null || logger === void 0 ? void 0 : logger.log('verify returned AtomicBEEF');
}
if (!vargs.isDelayed)
throwIfAnyUnsuccessfulCreateActions(r);
this.logResult(r, logger);
return r;
}
catch (eu) {
this.logWalletError(eu, logger);
throw eu;
}
}
async signAction(args, originator) {
var _a;
sdk_1.Validation.validateOriginator(originator);
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateSignActionArgs);
// createAction options are merged with undefined signAction options before validation...
const r = await (0, signAction_1.signAction)(this, auth, args);
if (!vargs.isDelayed)
throwIfAnyUnsuccessfulSignActions(r);
const prior = this.pendingSignActions[args.reference];
if (r.tx)
r.tx = this.verifyReturnedTxidOnlyAtomicBEEF(r.tx, (_a = prior.args.options) === null || _a === void 0 ? void 0 : _a.knownTxids);
return r;
}
async internalizeAction(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { auth, vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateInternalizeActionArgs);
if (vargs.labels.indexOf(types_1.specOpThrowReviewActions) >= 0)
throwDummyReviewActions();
const r = await (0, internalizeAction_1.internalizeAction)(this, auth, args);
throwIfUnsuccessfulInternalizeAction(r);
return r;
}
async abortAction(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { auth } = this.validateAuthAndArgs(args, sdk_1.Validation.validateAbortActionArgs);
const r = await this.storage.abortAction(args);
return r;
}
async relinquishOutput(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateRelinquishOutputArgs);
const r = await this.storage.relinquishOutput(args);
return { relinquished: true };
}
async isAuthenticated(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const r = {
authenticated: true
};
return r;
}
async waitForAuthentication(args, originator) {
sdk_1.Validation.validateOriginator(originator);
return { authenticated: true };
}
async getHeight(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const height = await this.getServices().getHeight();
return { height };
}
async getHeaderForHeight(args, originator) {
sdk_1.Validation.validateOriginator(originator);
const serializedHeader = await this.getServices().getHeaderForHeight(args.height);
return { header: sdk_1.Utils.toHex(serializedHeader) };
}
async getNetwork(args, originator) {
sdk_1.Validation.validateOriginator(originator);
return { network: (0, utilityHelpers_1.toWalletNetwork)(this.chain) };
}
async getVersion(args, originator) {
sdk_1.Validation.validateOriginator(originator);
return { version: 'wallet-brc100-1.0.0' };
}
/**
* Transfer all possible satoshis held by this wallet to `toWallet`.
*
* @param toWallet wallet which will receive this wallet's satoshis.
*/
async sweepTo(toWallet) {
const derivationPrefix = (0, utilityHelpers_1.randomBytesBase64)(8);
const derivationSuffix = (0, utilityHelpers_1.randomBytesBase64)(8);
const keyDeriver = this.keyDeriver;
const t = new ScriptTemplateBRC29_1.ScriptTemplateBRC29({
derivationPrefix,
derivationSuffix,
keyDeriver
});
const label = 'sweep';
const satoshis = generateChange_1.maxPossibleSatoshis;
const car = await this.createAction({
outputs: [
{
lockingScript: t.lock(keyDeriver.rootKey.toString(), toWallet.identityKey).toHex(),
satoshis,
outputDescription: label,
tags: ['relinquish'],
customInstructions: JSON.stringify({
derivationPrefix,
derivationSuffix,
type: 'BRC29'
})
}
],
options: {
randomizeOutputs: false,
acceptDelayedBroadcast: false
},
labels: [label],
description: label
});
const iar = await toWallet.internalizeAction({
tx: car.tx,
outputs: [
{
outputIndex: 0,
protocol: 'wallet payment',
paymentRemittance: {
derivationPrefix,
derivationSuffix,
senderIdentityKey: this.identityKey
}
}
],
description: label,
labels: [label]
});
}
/**
* Uses `listOutputs` to iterate over chunks of up to 1000 outputs to
* compute the sum of output satoshis.
*
* @param {string} basket - Optional. Defaults to 'default', the wallet change basket.
* @returns {WalletBalance} total sum of output satoshis and utxo details (satoshis and outpoints)
*/
async balanceAndUtxos(basket = 'default') {
const r = { total: 0, utxos: [] };
let offset = 0;
for (;;) {
const change = await this.listOutputs({
basket,
limit: 1000,
offset
});
if (change.totalOutputs === 0)
break;
for (const o of change.outputs) {
r.total += o.satoshis;
r.utxos.push({ satoshis: o.satoshis, outpoint: o.outpoint });
}
offset += change.outputs.length;
}
return r;
}
/**
* Uses `listOutputs` special operation to compute the total value (of satoshis) for
* all spendable outputs in the 'default' basket.
*
* @returns {number} sum of output satoshis
*/
async balance() {
const args = {
basket: types_1.specOpWalletBalance
};
const r = await this.listOutputs(args);
return r.totalOutputs;
}
/**
* Uses `listOutputs` special operation to review the spendability via `Services` of
* outputs currently considered spendable. Returns the outputs that fail to verify.
*
* Ignores the `limit` and `offset` properties.
*
* @param all Defaults to false. If false, only change outputs ('default' basket) are reviewed. If true, all spendable outputs are reviewed.
* @param release Defaults to false. If true, sets outputs that fail to verify to un-spendable (spendable: false)
* @param optionalArgs Optional. Additional tags will constrain the outputs processed.
* @returns outputs which are/where considered spendable but currently fail to verify as spendable.
*/
async reviewSpendableOutputs(all = false, release = false, optionalArgs) {
const args = {
...(optionalArgs || {}),
basket: types_1.specOpInvalidChange
};
args.tags || (args.tags = []);
if (all)
args.tags.push('all');
if (release)
args.tags.push('release');
const r = await this.listOutputs(args);
return r;
}
/**
* Uses `listOutputs` special operation to update the 'default' basket's automatic
* change generation parameters.
*
* @param count target number of change UTXOs to maintain.
* @param satoshis target value for new change outputs.
*/
async setWalletChangeParams(count, satoshis) {
const args = {
basket: types_1.specOpSetWalletChangeParams,
tags: [count.toString(), satoshis.toString()]
};
await this.listOutputs(args);
}
/**
* Uses `listActions` special operation to return only actions with status 'nosend'.
*
* @param abort Defaults to false. If true, runs `abortAction` on each 'nosend' action.
* @returns {ListActionsResult} start `listActions` result restricted to 'nosend' (or 'failed' if aborted) actions.
*/
async listNoSendActions(args, abort = false) {
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateListActionsArgs);
vargs.labels.push(types_1.specOpNoSendActions);
if (abort)
vargs.labels.push('abort');
const r = await this.storage.listActions(vargs);
return r;
}
/**
* Uses `listActions` special operation to return only actions with status 'failed'.
*
* @param unfail Defaults to false. If true, queues the action for attempted recovery.
* @returns {ListActionsResult} start `listActions` result restricted to 'failed' status actions.
*/
async listFailedActions(args, unfail = false) {
const { vargs } = this.validateAuthAndArgs(args, sdk_1.Validation.validateListActionsArgs);
vargs.labels.push(types_1.specOpFailedActions);
if (unfail)
vargs.labels.push('unfail');
const r = await this.storage.listActions(vargs);
return r;
}
}
exports.Wallet = Wallet;
function throwIfAnyUnsuccessfulCreateActions(r) {
const ndrs = r.notDelayedResults;
const swrs = r.sendWithResults;
if (!ndrs || !swrs || swrs.every(r => r.status === 'unproven'))
return;
throw new WERR_errors_1.WERR_REVIEW_ACTIONS(ndrs, swrs, r.txid, r.tx, r.noSendChange);
}
function throwIfAnyUnsuccessfulSignActions(r) {
const ndrs = r.notDelayedResults;
const swrs = r.sendWithResults;
if (!ndrs || !swrs || swrs.every(r => r.status === 'unproven'))
return;
throw new WERR_errors_1.WERR_REVIEW_ACTIONS(ndrs, swrs, r.txid, r.tx);
}
function throwIfUnsuccessfulInternalizeAction(r) {
const ndrs = r.notDelayedResults;
const swrs = r.sendWithResults;
if (!ndrs || !swrs || swrs.every(r => r.status === 'unproven'))
return;
throw new WERR_errors_1.WERR_REVIEW_ACTIONS(ndrs, swrs, r.txid);
}
/**
* Throws a WERR_REVIEW_ACTIONS with a full set of properties to test data formats and propagation.
*/
function throwDummyReviewActions() {
const b58Beef = 'gno9MC7VXii1KoCkc2nsVyYJpqzN3dhBzYATETJcys62emMKfpBof4R7GozwYEaSapUtnNvqQ57aaYYjm3U2dv9eUJ1sV46boHkQgppYmAz9YH8FdZduV8aJayPViaKcyPmbDhEw6UW8TM5iFZLXNs7HBnJHUKCeTdNK4FUEL7vAugxAV9WUUZ43BZjJk2SmSeps9TCXjt1Ci9fKWp3d9QSoYvTpxwzyUFHjRKtbUgwq55ZfkBp5bV2Bpz9qSuKywKewW7Hh4S1nCUScwwzpKDozb3zic1V9p2k8rQxoPsRxjUJ8bjhNDdsN8d7KukFuc3n47fXzdWttvnxwsujLJRGnQbgJuknQqx3KLf5kJXHzwjG6TzigZk2t24qeB6d3hbYiaDr2fFkUJBL3tukTHhfNkQYRXuz3kucVDzvejHyqJaF51mXG8BjMN5aQj91ZJXCaPVqkMWCzmvyaqmXMdRiJdSAynhXbQK91xf6RwdNhz1tg5f9B6oJJMhsi9UYSVymmax8VLKD9AKzBCBDcfyD83m3jyS1VgKGZn3SkQmr6bsoWq88L3GsMnnmYUGogvdAYarTqg3pzkjCMxHzmJBMN6ofnUk8c1sRTXQue7BbyUaN5uZu3KW6CmFsEfpuqVvnqFW93TU1jrPP2S8yz8AexAnARPCKE8Yz7RfVaT6RCavwQKL3u5iookwRWEZXW1QWmM37yJWHD87SjVynyg327a1CLwcBxmE2CB48QeNVGyQki4CTQMqw2o8TMhDPJej1g68oniAjBcxBLSCs7KGvK3k7AfrHbCMULX9CTibYhCjdFjbsbBoocqJpxxcvkMo1fEEiAzZuiBVZQDYktDdTVbhKHvYkW25HcYX75NJrpNAhm7AjFeKLzEVxqAQkMfvTufpESNRZF4kQqg2Rg8h2ajcKTd5cpEPwXCrZLHm4EaZEmZVbg3QNfGhn7BJu1bHMtLqPD4y8eJxm2uGrW6saf6qKYmmu64F8A667NbD4yskPRQ1S863VzwGpxxmgLc1Ta3R46jEqsAoRDoZVUaCgBBZG3Yg1CTgi1EVBMXU7qvY4n3h8o2FLCEMWY4KadnV3iD4FbcdCmg4yxBosNAZgbPjhgGjCimjh4YsLd9zymGLmivmz2ZBg5m3xaiXT9NN81X9C1JUujd';
const beef = sdk_1.Beef.fromBinary(sdk_1.Utils.fromBase58(b58Beef));
const btx = beef.txs.slice(-1)[0];
const txid = btx.txid;
console.log('Throwing dummy WERR_REVIEW_ACTIONS');
throw new WERR_errors_1.WERR_REVIEW_ACTIONS([
{
txid, // only care that it is syntactically a txid
status: 'doubleSpend',
competingTxs: [txid], // a txid in the beef
competingBeef: beef.toBinary()
}
], [
{
txid,
status: 'failed'
}
], txid, beef.toBinaryAtomic(txid), [`${txid}.0`]);
}
//# sourceMappingURL=Wallet.js.map