Plutonomicon / cardano-transaction-lib

A Purescript library for building smart contract transactions on Cardano
https://plutonomicon.github.io/cardano-transaction-lib/
MIT License
93 stars 50 forks source link

submit transaction with plutus script and datum using this library #4

Closed Benjmhart closed 2 years ago

nrutledge commented 2 years ago

Heads up. If the transaction is signed by Nami, apparently it loses any attached datums.

A workaround for various Nami issues that was used successfully before was to perform the following steps:

  1. Generate a raw transaction.
  2. Sign the transaction using a backend wallet.
  3. Sign the same transaction using Nami.
  4. Construct a new transaction taking the necessary parts from the Nami tx (vkey witnesses) and the other parts from the backend signed tx.

Below is an example (that may or may not work) which can be used for reference. Hopefully this can be adapted so that it works entirely on the frontend (either by keeping intermediate copies of the TX and reconstituting any lost details after Nami signing, or by constructing the necessary info with CSL).

const signTx = async (backendSignedTxHex) => {
  // Rebuild transaction to correct encoding discrepancy (may no longer be necessary)
  const backendSignedTx = csl.Transaction.from_bytes(hexToBytes(backendSignedTxHex));
  const fixedTxHex = bytesToHex(backendSignedTx.to_bytes());

  // Sign transaction with wallet to get witness set
  const walletWitnessSetHex = await window.cardano.signTx(fixedTxHex, true);
  const walletWitnessSet = csl.TransactionWitnessSet.from_bytes(hexToBytes(walletWitnessSetHex));

  // The witness set from Nami wallet will be missing required details. We now need to build a
  // new witness set that includes the scripts, data and redeemers from the backend transaction
  // and the vkey witnesses from the Nami signed transaction.

  const newWitnessSet = csl.TransactionWitnessSet.new();
  const vkeysWitnesses = csl.Vkeywitnesses.new();

  for (let i = 0; i < walletWitnessSet.vkeys()!.len(); i++) {
    vkeysWitnesses.add(walletWitnessSet.vkeys()!.get(i));
  }

  newWitnessSet.set_vkeys(vkeysWitnesses);

  if (backendSignedTx.witness_set().plutus_scripts()) {
    newWitnessSet.set_plutus_scripts(
      csl.PlutusScripts.from_bytes(backendSignedTx.witness_set().plutus_scripts()!.to_bytes())
    );
  }

  if (backendSignedTx.witness_set().plutus_data()) {
    newWitnessSet.set_plutus_data(
      csl.PlutusList.from_bytes(backendSignedTx.witness_set().plutus_data()!.to_bytes())
    );
  }

  if (backendSignedTx.witness_set().redeemers()) {
    newWitnessSet.set_redeemers(
      csl.Redeemers.from_bytes(backendSignedTx.witness_set().redeemers()!.to_bytes())
    );
  }

  // Create a new tx from the original body and the new witness set.
  const newTx = csl.Transaction.new(
    backendSignedTx.body(),
    newWitnessSet,
    backendSignedTx.auxiliary_data()
  );

  // Return transaction in hex encoded format required to submit with Nami.
  return bytesToHex(newTx.to_bytes());
};
Benjmhart commented 2 years ago

script inputs appear not to be supported in Cardano-serialization-lib.

the current reccomendation is to vendor this code: https://github.com/Berry-Pool/spacebudz/tree/main/src/cardano/market/custom_modules/%40emurgo

Benjmhart commented 2 years ago

blocked on https://github.com/Plutonomicon/cardano-browser-tx/issues/2

@mailtodanish - right now you would have to calculate a datum by hand and make sure it serializes to datum format. https://github.com/Plutonomicon/cardano-browser-tx/issues/25

we're working on adding transaction and datum queries to ogmios/ogmios-like services this library will integrate with to capture previous datum and help provide the capability to create output datum.

mailtodanish commented 2 years ago

@Benjmhart we are trying to sign CLI tx using NAMI

signCLITx(cborHex) {
    if (!this.isEnabled()) throw ERROR.NOT_CONNECTED;

    // Get network id
    let networkId = await this.getNetworkId();

    const myAddress = await this.getHexAddress();

    // Load tx cbor
    const txCli = this.S.Transaction.from_bytes(Buffer.from(cborHex, "hex"));

    // Get tx body
    const txBody = txCli.body();

    // Get tx witness-set
    const witnessSet = txCli.witness_set();

    // clear vkeys from witness set
    witnessSet.vkeys()?.free();

    /*after clearing vkeys for the witness-set we can re-assemble the transcation using tx-body
    and clean witness set and set required signature to the wallet */

    //create required signature
    const paymentKeyHash = this.S.BaseAddress.from_address(
      this.S.Address.from_bytes(Buffer.from(myAddress, "hex"))
    )
      .payment_cred()
      .to_keyhash();
    const requiredSigners = this.S.Ed25519KeyHashes.new();
    requiredSigners.add(paymentKeyHash);

    // set newly created required signer to tx body
    txBody.set_required_signers(requiredSigners);

    // re-assemble transaction
    const tx = this.S.Transaction.new(txBody, witnessSet);

    //encode tx
    const encodedTx = Buffer.from(tx.to_bytes()).toString("hex");

    // sign tx using nami wallet
    const encodedTxVkeyWitnesses = await this.Nami.signTx(encodedTx, true);

    // decode witness-set produced by signature
    const txVkeyWitnesses = this.S.TransactionWitnessSet.from_bytes(
      Buffer.from(encodedTxVkeyWitnesses, "hex")
    );

    //set vkeys to our tx from decoded witness-set
    witnessSet.set_vkeys(txVkeyWitnesses.vkeys());

    //re-assemble signed tx
    const txSigned = this.S.Transaction.new(
      tx.body(),
      witnessSet,
      tx.auxiliary_data()
    );

    //encode signed tx
    const encodedSignedTx = Buffer.from(txSigned.to_bytes()).toString("hex");

    console.log("Full Tx Size", txSigned.to_bytes().length);
    // submit the tx
    const txHash = await window.cardano.submitTx(encodedSignedTx);

    console.log(txHash);

    return txhash;
  }

Below Error { "code": 2, "info": "Wallet could not send the tx.", "message": "\"transaction submit error ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [UtxowFailure (MissingRequiredDatums (fromList [SafeHash \\"03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314\\"]) (fromList [SafeHash \\"ccb127aef877cfc0abeb3633875fec21dfd357a2319808a634e9c7cd1164b4a2\\"])),UtxowFailure (NonOutputSupplimentaryDatums (fromList [SafeHash \\"ccb127aef877cfc0abeb3633875fec21dfd357a2319808a634e9c7cd1164b4a2\\"]) (fromList [])),UtxowFailure (PPViewHashesDontMatch (SJust (SafeHash \\"d67a0c3e121f47c7442d593b23595f90822a06ec0269d3d679a3ea041c6f057b\\")) (SJust (SafeHash \\"10fbd8511d4b934e8f2e4c59359269aff62621f11ebe28ff8d634a51851e7df0\\")))])\"" }

ngua commented 2 years ago

We can try a few different scripts for this, increasing in complexity (e.g. start with AlwaysSucceeds and go from there). Some scripts we could consider: https://github.com/input-output-hk/Alonzo-testnet/tree/main/resources/plutus-scripts

ngua commented 2 years ago

Achieved during Seabug deployment :)