All files / src/signer/methods internalizeAction.ts

83.33% Statements 30/36
50% Branches 6/12
100% Functions 4/4
84.84% Lines 28/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 10957x 57x                                                                     57x           8x   8x 8x   8x 12x   12x 6x 6x         8x   8x     6x 6x 6x   6x   6x 6x 6x                     8x 8x 8x   8x 8x 8x   8x                             8x        
import { Beef, InternalizeActionArgs, InternalizeActionResult, InternalizeOutput, P2PKH, WalletProtocol } from '@bsv/sdk'
import { sdk } from "../../index.client";
import { WalletSigner } from "../WalletSigner";
 
/**
 * 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.
 * 
 * 
 * @param ninja 
 * @param vargs 
 * @param originator 
 * @returns 
 */
export async function internalizeAction(
  signer: WalletSigner,
  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) {
    Iif (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 signer.storage.internalizeAction(args)
 
  return r
 
  function setupWalletPaymentForOutput(o: InternalizeOutput, dargs: sdk.ValidInternalizeActionArgs) {
    const p = o.paymentRemittance
    const output = tx.outputs[o.outputIndex]
    Iif (!p) throw new sdk.WERR_INVALID_PARAMETER('paymentRemitance', `valid for protocol ${o.protocol}`);
 
    const keyID = `${p.derivationPrefix} ${p.derivationSuffix}`
 
    const privKey = signer.keyDeriver!.derivePrivateKey(brc29ProtocolID, keyID, p.senderIdentityKey)
    const expectedLockScript = new P2PKH().lock(privKey.toAddress())
    Iif (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);
    const txValid = await ab.verify(await signer.getServices().getChainTracker(), false);
    Iif (!txValid || !ab.atomicTxid)
      throw new sdk.WERR_INVALID_PARAMETER('tx', 'valid AtomicBEEF');
    const txid = ab.atomicTxid;
    const btx = ab.findTxid(txid);
    Iif (!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 }
  }
}