wallet-storage
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
106 lines (90 loc) • 4.57 kB
text/typescript
import { Beef, InternalizeActionArgs, InternalizeActionResult, InternalizeOutput, P2PKH, WalletProtocol } from '@bsv/sdk'
import { sdk, Wallet } from "../../index.client";
/**
* Internalize Action allows a wallet to take ownership of outputs in a pre-existing transaction.
* The transaction may, or may not already be known to both the storage and user.
*
* Two types of outputs are handled: "wallet payments" and "basket insertions".
*
* A "basket insertion" output is considered a custom output and has no effect on the wallet's "balance".
*
* A "wallet payment" adds an outputs value to the wallet's change "balance". These outputs are assigned to the "default" basket.
*
* Processing starts with simple validation and then checks for a pre-existing transaction.
* If the transaction is already known to the user, then the outputs are reviewed against the existing outputs treatment,
* and merge rules are added to the arguments passed to the storage layer.
* The existing transaction must be in the 'unproven' or 'completed' status. Any other status is an error.
*
* When the transaction already exists, the description is updated. The isOutgoing sense is not changed.
*
* "basket insertion" Merge Rules:
* 1. The "default" basket may not be specified as the insertion basket.
* 2. A change output in the "default" basket may not be target of an insertion into a different basket.
* 3. These baskets do not affect the wallet's balance and are typed "custom".
*
* "wallet payment" Merge Rules:
* 1. Targetting an existing change "default" basket output results in a no-op. No error. No alterations made.
* 2. Targetting a previously "custom" non-change output converts it into a change output. This alters the transaction's `amount`, and the wallet balance.
*
*/
export async function internalizeAction(
wallet: Wallet,
auth: sdk.AuthId,
args: InternalizeActionArgs
)
: Promise<InternalizeActionResult> {
const vargs = sdk.validateInternalizeActionArgs(args)
const { ab, tx, txid } = await validateAtomicBeef();
const brc29ProtocolID: WalletProtocol = [2, '3241645161d8']
for (const o of vargs.outputs) {
if (o.outputIndex < 0 || o.outputIndex >= tx.outputs.length)
throw new sdk.WERR_INVALID_PARAMETER('outputIndex', `a valid output index in range 0 to ${tx.outputs.length - 1}`);
switch (o.protocol) {
case 'basket insertion': setupBasketInsertionForOutput(o, vargs); break;
case 'wallet payment': setupWalletPaymentForOutput(o, vargs); break;
default: throw new sdk.WERR_INTERNAL(`unexpected protocol ${o.protocol}`)
}
}
const r: InternalizeActionResult = await wallet.storage.internalizeAction(args)
return r
function setupWalletPaymentForOutput(o: InternalizeOutput, dargs: sdk.ValidInternalizeActionArgs) {
const p = o.paymentRemittance
const output = tx.outputs[o.outputIndex]
if (!p) throw new sdk.WERR_INVALID_PARAMETER('paymentRemitance', `valid for protocol ${o.protocol}`);
const keyID = `${p.derivationPrefix} ${p.derivationSuffix}`
const privKey = wallet.keyDeriver!.derivePrivateKey(brc29ProtocolID, keyID, p.senderIdentityKey)
const expectedLockScript = new P2PKH().lock(privKey.toAddress())
if (output.lockingScript.toHex() !== expectedLockScript.toHex())
throw new sdk.WERR_INVALID_PARAMETER('paymentRemitance', `locked by script conforming to BRC-29`);
}
function setupBasketInsertionForOutput(o: InternalizeOutput, dargs: sdk.ValidInternalizeActionArgs) {
/*
No additional validations...
*/
}
async function validateAtomicBeef() {
const ab = Beef.fromBinary(vargs.tx);
// TODO: Add support for known txids...
const txValid = await ab.verify(await wallet.getServices().getChainTracker(), false);
if (!txValid || !ab.atomicTxid)
throw new sdk.WERR_INVALID_PARAMETER('tx', 'valid AtomicBEEF');
const txid = ab.atomicTxid;
const btx = ab.findTxid(txid);
if (!btx)
throw new sdk.WERR_INVALID_PARAMETER('tx', `valid AtomicBEEF with newest txid of ${txid}`);
const tx = btx.tx;
/*
for (const i of tx.inputs) {
if (!i.sourceTXID)
throw new sdk.WERR_INTERNAL('beef Transactions must have sourceTXIDs')
if (!i.sourceTransaction) {
const btx = ab.findTxid(i.sourceTXID)
if (!btx)
throw new sdk.WERR_INVALID_PARAMETER('tx', `valid AtomicBEEF and contain input transaction with txid ${i.sourceTXID}`);
i.sourceTransaction = btx.tx
}
}
*/
return { ab, tx, txid }
}
}