All files / src/signer/methods internalizeActionSdk.ts

7.69% Statements 3/39
0% Branches 0/15
0% Functions 0/4
8.33% Lines 3/36

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 109 110 111 112 113 11428x 28x                                                                     28x                                                                                                                                                          
import * as bsv from '@bsv/sdk'
import { sdk } from "../..";
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 internalizeActionSdk(signer: WalletSigner, vargs: sdk.ValidInternalizeActionArgs, originator?: sdk.OriginatorDomainNameStringUnder250Bytes)
: Promise<sdk.InternalizeActionResult> {
 
  const { ab, tx, txid } = await validateAtomicBeef();
  const brc29ProtocolID: sdk.WalletProtocol = [2, '3241645161d8']
 
  const sargs: sdk.StorageInternalizeActionArgs = {
    ...vargs,
    commonDerivationPrefix: undefined
  }
 
  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, sargs); break;
      case 'wallet payment': setupWalletPaymentForOutput(o, sargs); break;
      default: throw new sdk.WERR_INTERNAL(`unexpected protocol ${o.protocol}`)
    }
  }
 
  const r: sdk.InternalizeActionResult = await signer.storage.internalizeActionSdk(sargs, originator)
 
  return r
 
  function setupWalletPaymentForOutput(o: sdk.InternalizeOutput, dargs: sdk.StorageInternalizeActionArgs) {
    const p = o.paymentRemittance
    const output = tx.outputs[o.outputIndex]
    Iif (!p) throw new sdk.WERR_INVALID_PARAMETER('paymentRemitance', `valid for protocol ${o.protocol}`);
    Iif (dargs.commonDerivationPrefix && dargs.commonDerivationPrefix !== p.derivationPrefix)
      throw new sdk.WERR_INVALID_PARAMETER('paymentRemitance', `the same derivationPrefix ${dargs.commonDerivationPrefix} vs ${p.derivationPrefix}`);
 
    const keyID = `${dargs.commonDerivationPrefix} ${p.derivationSuffix}`
    const forSelf = false
 
    const privKey = signer.keyDeriver!.derivePrivateKey(brc29ProtocolID, keyID, p.senderIdentityKey)
    const expectedLockScript = new bsv.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: sdk.InternalizeOutput, dargs: sdk.StorageInternalizeActionArgs) {
    /*
    No additional validations...
    */
  }
 
  async function validateAtomicBeef() {
    const ab = bsv.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 }
  }
}