StricaHQ / typhonjs

Pure javascript Cardano wallet library
Apache License 2.0
61 stars 12 forks source link

[Help] Preparing transaction to send to submit-api #17

Closed Aervue closed 2 years ago

Aervue commented 2 years ago
import typhonJs from '@stricahq/typhonjs';
import bip32ed25519 from '@stricahq/bip32ed25519';
import { Encoder } from '@stricahq/cbors';
import { mnemonicToEntropy } from 'bip39';
import cip08 from '@stricahq/cip08';
import BigNumber from "bignumber.js";

const { Bip32PrivateKey } = bip32ed25519;
const { CoseSign1, createSigStructure } = cip08;

const protocolParams = {
  minFeeA: new BigNumber(44),
  minFeeB: new BigNumber(155381),
  stakeKeyDeposit: new BigNumber(2000000),
  lovelacePerUtxoWord: new BigNumber(34482),
  collateralPercent: new BigNumber(150),
  priceSteps: new BigNumber(0.0577),
  priceMem: new BigNumber(0.0000721),
  languageView: {
    PlutusScriptV1: {
      "sha2_256-memory-arguments": 4,
      "equalsString-cpu-arguments-constant": 1000,
      "cekDelayCost-exBudgetMemory": 100,
      "lessThanEqualsByteString-cpu-arguments-intercept": 103599,
      "divideInteger-memory-arguments-minimum": 1,
      "appendByteString-cpu-arguments-slope": 621,
      "blake2b-cpu-arguments-slope": 29175,
      "iData-cpu-arguments": 150000,
      "encodeUtf8-cpu-arguments-slope": 1000,
      "unBData-cpu-arguments": 150000,
      "multiplyInteger-cpu-arguments-intercept": 61516,
      "cekConstCost-exBudgetMemory": 100,
      "nullList-cpu-arguments": 150000,
      "equalsString-cpu-arguments-intercept": 150000,
      "trace-cpu-arguments": 150000,
      "mkNilData-memory-arguments": 32,
      "lengthOfByteString-cpu-arguments": 150000,
      "cekBuiltinCost-exBudgetCPU": 29773,
      "bData-cpu-arguments": 150000,
      "subtractInteger-cpu-arguments-slope": 0,
      "unIData-cpu-arguments": 150000,
      "consByteString-memory-arguments-intercept": 0,
      "divideInteger-memory-arguments-slope": 1,
      "divideInteger-cpu-arguments-model-arguments-slope": 118,
      "listData-cpu-arguments": 150000,
      "headList-cpu-arguments": 150000,
      "chooseData-memory-arguments": 32,
      "equalsInteger-cpu-arguments-intercept": 136542,
      "sha3_256-cpu-arguments-slope": 82363,
      "sliceByteString-cpu-arguments-slope": 5000,
      "unMapData-cpu-arguments": 150000,
      "lessThanInteger-cpu-arguments-intercept": 179690,
      "mkCons-cpu-arguments": 150000,
      "appendString-memory-arguments-intercept": 0,
      "modInteger-cpu-arguments-model-arguments-slope": 118,
      "ifThenElse-cpu-arguments": 1,
      "mkNilPairData-cpu-arguments": 150000,
      "lessThanEqualsInteger-cpu-arguments-intercept": 145276,
      "addInteger-memory-arguments-slope": 1,
      "chooseList-memory-arguments": 32,
      "constrData-memory-arguments": 32,
      "decodeUtf8-cpu-arguments-intercept": 150000,
      "equalsData-memory-arguments": 1,
      "subtractInteger-memory-arguments-slope": 1,
      "appendByteString-memory-arguments-intercept": 0,
      "lengthOfByteString-memory-arguments": 4,
      "headList-memory-arguments": 32,
      "listData-memory-arguments": 32,
      "consByteString-cpu-arguments-intercept": 150000,
      "unIData-memory-arguments": 32,
      "remainderInteger-memory-arguments-minimum": 1,
      "bData-memory-arguments": 32,
      "lessThanByteString-cpu-arguments-slope": 248,
      "encodeUtf8-memory-arguments-intercept": 0,
      "cekStartupCost-exBudgetCPU": 100,
      "multiplyInteger-memory-arguments-intercept": 0,
      "unListData-memory-arguments": 32,
      "remainderInteger-cpu-arguments-model-arguments-slope": 118,
      "cekVarCost-exBudgetCPU": 29773,
      "remainderInteger-memory-arguments-slope": 1,
      "cekForceCost-exBudgetCPU": 29773,
      "sha2_256-cpu-arguments-slope": 29175,
      "equalsInteger-memory-arguments": 1,
      "indexByteString-memory-arguments": 1,
      "addInteger-memory-arguments-intercept": 1,
      "chooseUnit-cpu-arguments": 150000,
      "sndPair-cpu-arguments": 150000,
      "cekLamCost-exBudgetCPU": 29773,
      "fstPair-cpu-arguments": 150000,
      "quotientInteger-memory-arguments-minimum": 1,
      "decodeUtf8-cpu-arguments-slope": 1000,
      "lessThanInteger-memory-arguments": 1,
      "lessThanEqualsInteger-cpu-arguments-slope": 1366,
      "fstPair-memory-arguments": 32,
      "modInteger-memory-arguments-intercept": 0,
      "unConstrData-cpu-arguments": 150000,
      "lessThanEqualsInteger-memory-arguments": 1,
      "chooseUnit-memory-arguments": 32,
      "sndPair-memory-arguments": 32,
      "addInteger-cpu-arguments-intercept": 197209,
      "decodeUtf8-memory-arguments-slope": 8,
      "equalsData-cpu-arguments-intercept": 150000,
      "mapData-cpu-arguments": 150000,
      "mkPairData-cpu-arguments": 150000,
      "quotientInteger-cpu-arguments-constant": 148000,
      "consByteString-memory-arguments-slope": 1,
      "cekVarCost-exBudgetMemory": 100,
      "indexByteString-cpu-arguments": 150000,
      "unListData-cpu-arguments": 150000,
      "equalsInteger-cpu-arguments-slope": 1326,
      "cekStartupCost-exBudgetMemory": 100,
      "subtractInteger-cpu-arguments-intercept": 197209,
      "divideInteger-cpu-arguments-model-arguments-intercept": 425507,
      "divideInteger-memory-arguments-intercept": 0,
      "cekForceCost-exBudgetMemory": 100,
      "blake2b-cpu-arguments-intercept": 2477736,
      "remainderInteger-cpu-arguments-constant": 148000,
      "tailList-cpu-arguments": 150000,
      "encodeUtf8-cpu-arguments-intercept": 150000,
      "equalsString-cpu-arguments-slope": 1000,
      "lessThanByteString-memory-arguments": 1,
      "multiplyInteger-cpu-arguments-slope": 11218,
      "appendByteString-cpu-arguments-intercept": 396231,
      "lessThanEqualsByteString-cpu-arguments-slope": 248,
      "modInteger-memory-arguments-slope": 1,
      "addInteger-cpu-arguments-slope": 0,
      "equalsData-cpu-arguments-slope": 10000,
      "decodeUtf8-memory-arguments-intercept": 0,
      "chooseList-cpu-arguments": 150000,
      "constrData-cpu-arguments": 150000,
      "equalsByteString-memory-arguments": 1,
      "cekApplyCost-exBudgetCPU": 29773,
      "quotientInteger-memory-arguments-slope": 1,
      "verifySignature-cpu-arguments-intercept": 3345831,
      "unMapData-memory-arguments": 32,
      "mkCons-memory-arguments": 32,
      "sliceByteString-memory-arguments-slope": 1,
      "sha3_256-memory-arguments": 4,
      "ifThenElse-memory-arguments": 1,
      "mkNilPairData-memory-arguments": 32,
      "equalsByteString-cpu-arguments-slope": 247,
      "appendString-cpu-arguments-intercept": 150000,
      "quotientInteger-cpu-arguments-model-arguments-slope": 118,
      "cekApplyCost-exBudgetMemory": 100,
      "equalsString-memory-arguments": 1,
      "multiplyInteger-memory-arguments-slope": 1,
      "cekBuiltinCost-exBudgetMemory": 100,
      "remainderInteger-memory-arguments-intercept": 0,
      "sha2_256-cpu-arguments-intercept": 2477736,
      "remainderInteger-cpu-arguments-model-arguments-intercept": 425507,
      "lessThanEqualsByteString-memory-arguments": 1,
      "tailList-memory-arguments": 32,
      "mkNilData-cpu-arguments": 150000,
      "chooseData-cpu-arguments": 150000,
      "unBData-memory-arguments": 32,
      "blake2b-memory-arguments": 4,
      "iData-memory-arguments": 32,
      "nullList-memory-arguments": 32,
      "cekDelayCost-exBudgetCPU": 29773,
      "subtractInteger-memory-arguments-intercept": 1,
      "lessThanByteString-cpu-arguments-intercept": 103599,
      "consByteString-cpu-arguments-slope": 1000,
      "appendByteString-memory-arguments-slope": 1,
      "trace-memory-arguments": 32,
      "divideInteger-cpu-arguments-constant": 148000,
      "cekConstCost-exBudgetCPU": 29773,
      "encodeUtf8-memory-arguments-slope": 8,
      "quotientInteger-cpu-arguments-model-arguments-intercept": 425507,
      "mapData-memory-arguments": 32,
      "appendString-cpu-arguments-slope": 1000,
      "modInteger-cpu-arguments-constant": 148000,
      "verifySignature-cpu-arguments-slope": 1,
      "unConstrData-memory-arguments": 32,
      "quotientInteger-memory-arguments-intercept": 0,
      "equalsByteString-cpu-arguments-constant": 150000,
      "sliceByteString-memory-arguments-intercept": 0,
      "mkPairData-memory-arguments": 32,
      "equalsByteString-cpu-arguments-intercept": 112536,
      "appendString-memory-arguments-slope": 1,
      "lessThanInteger-cpu-arguments-slope": 497,
      "modInteger-cpu-arguments-model-arguments-intercept": 425507,
      "modInteger-memory-arguments-minimum": 1,
      "sha3_256-cpu-arguments-intercept": 0,
      "verifySignature-memory-arguments": 1,
      "cekLamCost-exBudgetMemory": 100,
      "sliceByteString-cpu-arguments-intercept": 150000
    },
  },
}

const words = 'squirrel razor tank unusual notable exhaust weird cherry cage rigid tattoo luggage filter bean pepper cream option cause media deputy cluster mixture exotic access';
const entropy = mnemonicToEntropy(words);
const rootKey = await Bip32PrivateKey.fromEntropy(Buffer.from(entropy, "hex"));

const HARDENED = 2147483648
const accountKey = rootKey
  .derive(1852 + HARDENED) // purpose
  .derive(1815 + HARDENED) // coin type
  .derive(0 + HARDENED) // account index
  .derive(0) // chain
  .derive(0) // payment key index // Change this to generate other addresses
  .toPrivateKey();

const stakeKey = rootKey
  .derive(1852 + HARDENED) // purpose
  .derive(1815 + HARDENED) // coin type
  .derive(0 + HARDENED) // account index
  .derive(2) // chain
  .derive(0) // payment key index
  .toPrivateKey();

const paymentCred0 = {
  hash: accountKey.toPublicKey().hash().toString("hex"),
  type: typhonJs.types.HashType.ADDRESS,
};
const paymentCred1 = {
  hash: stakeKey.toPublicKey().hash().toString("hex"),
  type: typhonJs.types.HashType.ADDRESS,
};

const payAddress = new typhonJs.address.BaseAddress(
  typhonJs.types.NetworkId.MAINNET,
  paymentCred0,
  paymentCred1
);

// Got the info from koios api but it needed a few transformations so I hard-coded it here for simplicity's sake
const inputs = [
  {
    txId: 'aa6e3c206a746d54f01c1a5784af38c6ffde22d31df1cb1df23966a82caad6e7',
    index: 0,
    amount: new BigNumber(3000000),
    tokens: [],
    address: payAddress
  },
  {
    txId: "2f4d55ff9676ebe3d5d1289d7abdede35e030a4eb5d553145f2cb8c7a24587b8",
    index: 0,
    amount: new BigNumber(5000000),
    tokens: [],
    address: payAddress
  }
]

// Test wallet address on mainnet
const outputs = [
  {
    amount: new BigNumber(1200000),
    address: typhonJs.utils.getAddressFromBech32('addr1qyfcdgkt8jzx58uncjhjrea5zl5xjhpuyygtpfzjqrsycaefmps0ynd4j00zed247hmtzdkhjqs6c9myph4dtcwuz3vsst3qjv'),
    tokens: []
  }
]

// Generate the payment transaction
const tx = new typhonJs.Transaction({ protocolParams }).paymentTransaction({
  inputs: inputs,
  outputs: outputs,
  changeAddress: payAddress,
  ttl: 700000
})

const txHash = tx.getTransactionHash();
const requiredSignatures = tx.getRequiredWitnesses();

// `accountPrivateKey` derive using @stricahq/bip32ed25519
for (const [, bipPath] of requiredSignatures) {
  const privateKey = rootKey.derive(0).derive(0).toPrivateKey();
  const witness = {
    publicKey: privateKey.toPublicKey().toBytes(),
    signature: privateKey.sign(txHash),
  };
  tx.addWitness(witness);
}

console.log(tx)

// THIS IS WHERE IT GETS MESSED UP I BELIEVE

let txCbor = Encoder.encode(tx).toString("hex")

const data = {
  addressBuffer: Buffer.from(
    paymentCred0.hash,
    "hex"
  ),
  publicKeyBuffer: Buffer.from(
    payAddress.addressHex,
    "hex"
  ),
  signature: Buffer.from(txCbor, "hex"),
};

const protectedMap = new Map();
// Set protected headers as per CIP08
// Set Algorthm used by Cardano keys
protectedMap.set(1, -8);
// Set PublicKey
protectedMap.set(4, data.publicKeyBuffer);
// Set Address
protectedMap.set("address", data.addressBuffer);

const coseSign1Builder = new CoseSign1({
  protectedMap,
  unProtectedMap: new Map(),
  payload: Buffer.from(""),
  hashPayload: false,
});

const coseSign1 = coseSign1Builder.buildMessage(data.signature);

// console.log(txCbor)
console.log(coseSign1.toString("hex"))
// console.log(payAddress.addressBytes)

console.log(tx) outputs:

Transaction {
  inputs: [
    {
      txId: 'aa6e3c206a746d54f01c1a5784af38c6ffde22d31df1cb1df23966a82caad6e7',
      index: 0,
      amount: [BigNumber],
      tokens: [],
      address: [BaseAddress]
    }
  ],
  outputs: [
    { amount: [BigNumber], address: [BaseAddress], tokens: [] },
    { address: [BaseAddress], amount: [BigNumber], tokens: [] }
  ],
  certificates: [],
  withdrawals: [],
  requiredWitnesses: Map(1) {
    '3b64f00fc61bc8abe86830e3a04b711d5aa322b9e7020240e087857e' => undefined
  },
  requiredNativeScriptWitnesses: Map(0) {},
  fee: BigNumber { s: 1, e: 5, c: [ 168141 ] },
  witnesses: [
    {
      publicKey: <Buffer 15 08 40 4d 21 9c 65 f5 34 f0 f9 69 c4 10 54 06 b4 73 5e 6a e4 da a3 69 4b 3c 20 72 b4 5d 94 46>,    
      signature: <Buffer 86 0b dd 3c 9b de ff 86 05 e1 0a 78 59 2d 8c 88 c9 1d c6 0b 81 ac 37 a0 f8 e3 47 96 af d8 03 19 1e 14 1c fe ec ab 94 4c b3 81 16 33 e1 d3 b2 cf 16 73 ... 14 more bytes>
    }
  ],
  plutusScriptMap: Map(0) {},
  nativeScriptList: [],
  collaterals: [],
  requiredSigners: Map(0) {},
  plutusDataList: [],
  _isPlutusTransaction: false,
  mints: [],
  _protocolParams: {
    minFeeA: BigNumber { s: 1, e: 1, c: [Array] },
    minFeeB: BigNumber { s: 1, e: 5, c: [Array] },
    stakeKeyDeposit: BigNumber { s: 1, e: 6, c: [Array] },
    lovelacePerUtxoWord: BigNumber { s: 1, e: 4, c: [Array] },
    collateralPercent: BigNumber { s: 1, e: 2, c: [Array] },
    priceSteps: BigNumber { s: 1, e: -2, c: [Array] },
    priceMem: BigNumber { s: 1, e: -5, c: [Array] },
    languageView: { PlutusScriptV1: [Object] }
  },
  ttl: 700000
}

Hey, I'm trying to prepare a transaction to be able to send it to submit-api but I'm a bit lost in what I need to do at the end, could you point me in the right direction? (yes I know the seed is supposed to be hidden but I don't mind it)

Thank you

ashisherc commented 2 years ago

after you add signatures to the transaction do,

tx.buildTransaction()

it will get you the final txHash and the cbor hex to be submitted to the submit-api

Aervue commented 2 years ago

Ok so I removed the following:

// THIS IS WHERE IT GETS MESSED UP I BELIEVE

let txCbor = Encoder.encode(tx).toString("hex")

const data = {
  addressBuffer: Buffer.from(
    paymentCred0.hash,
    "hex"
  ),
  publicKeyBuffer: Buffer.from(
    payAddress.addressHex,
    "hex"
  ),
  signature: Buffer.from(txCbor, "hex"),
};

const protectedMap = new Map();
// Set protected headers as per CIP08
// Set Algorthm used by Cardano keys
protectedMap.set(1, -8);
// Set PublicKey
protectedMap.set(4, data.publicKeyBuffer);
// Set Address
protectedMap.set("address", data.addressBuffer);

const coseSign1Builder = new CoseSign1({
  protectedMap,
  unProtectedMap: new Map(),
  payload: Buffer.from(""),
  hashPayload: false,
});

const coseSign1 = coseSign1Builder.buildMessage(data.signature);

// console.log(txCbor)
console.log(coseSign1.toString("hex"))
// console.log(payAddress.addressBytes)

and replaced them with:

const txFinal = tx.buildTransaction()
// tx.buildTransaction()
console.log(txFinal)

const headers = {
  "Content-Type": "application/cbor"
}

const apiAddress = "https://api.koios.rest/api/v0/submittx"

async function postTx() {
  try {
    const response = await axios.post(apiAddress, txFinal, { headers });
    console.log(response);
    // return response.data
  } catch (error) {
    console.error(error);
  }
}

postTx()

finalTx looks like:

{
  hash: '9be6847a14d94f544114a53391870d9ee430a2aea3ca4f86a41b5dd345a685ba',
  payload: '84a40081825820aa6e3c206a746d54f01c1a5784af38c6ffde22d31df1cb1df23966a82caad6e7000182825839011386a2cb3c846a1f93c4af21e7b417e8695c3c2110b0a45200e04c7729d860f24db593de2cb555f5f6b136d79021ac17640dead5e1dc14591a00124f80825839013b64f00fc61bc8abe86830e3a04b711d5aa322b9e7020240e087857e735b09b8ad8a36226c92a6d4801e3eef6710e3eec708f867ef5786551a0018e673021a000290cd031a000aae60a100818258201508404d219c65f534f0f969c4105406b4735e6ae4daa3694b3c2072b45d94465840860bdd3c9bdeff8605e10a78592d8c88c91dc60b81ac37a0f8e34796afd803191e141cfeecab944cb3811633e1d3b2cf167397729f81a21be1b26fd9b6f2950bf5f6'
}

The response from the koios api is:

'transaction read error RawCborDecodeError [DecoderErrorDeserialiseFailure "Byron Tx" (DeserialiseFailure 0 "expected list len"),DecoderErrorDeserialiseFailure "Shelley Tx" (DeserialiseFailure 0 "expected list len or indef"),DecoderErrorDeserialiseFailure "Shelley Tx" (DeserialiseFailure 0 "expected list len or indef"),DecoderErrorDeserialiseFailure "Shelley Tx" (DeserialiseFailure 0 "expected list len or indef"),DecoderErrorDeserialiseFailure "Shelley Tx" (DeserialiseFailure 0 "expected list len or indef")]'

Assuming txFinal is signed and final I should retrace my steps and look for any mistakes? Everything else seems fine?

Appreciate it

ashisherc commented 2 years ago

you should be submitting only the payload to the API, txFinal.payload

Aervue commented 2 years ago

you should be submitting only the payload to the API, txFinal.payload

Still not working but I am getting closer, I changed the following (besides only posting txFinal.payload and adjusting TTL)

  const privateKey = rootKey.derive(0).derive(0).toPrivateKey();

to

   const privateKey = accountKey;

Since I already derived it fully.

Currently looking at protocolParams since there are 3 values I do not know how you got them because they are not in genesis file:

  minFeeA: new BigNumber(44),
  minFeeB: new BigNumber(155381),
  stakeKeyDeposit: new BigNumber(2000000)
ashisherc commented 2 years ago

You will get the proto params using the cardano-li query

Aervue commented 2 years ago

You will get the proto params using the cardano-li query

That is what I've been doing correct.

However they differ a little, if we remove the "PlutusV1" category from each, we can see in protocolParams (this package):

{
  minFeeA: new BigNumber(44),
  minFeeB: new BigNumber(155381),
  stakeKeyDeposit: new BigNumber(2000000),
  lovelacePerUtxoWord: new BigNumber(34482),
  collateralPercent: new BigNumber(150),
  priceSteps: new BigNumber(0.0577),
  priceMem: new BigNumber(0.0000721),
  }

and in the json from cardano-cli:

  {
    "lovelacePerUTxOWord": 34482,
  "executionPrices": {
    "prSteps": {
      "numerator": 721,
      "denominator": 10000000
    },
    "prMem": {
      "numerator": 577,
      "denominator": 10000
    }
  },
  "maxTxExUnits": {
    "exUnitsMem": 10000000,
    "exUnitsSteps": 10000000000
  },
  "maxBlockExUnits": {
    "exUnitsMem": 50000000,
    "exUnitsSteps": 40000000000
  },
  "maxValueSize": 5000,
  "collateralPercentage": 150,
  "maxCollateralInputs": 3,
  }

So I do not know how to extract minFeeA / minFeeB / stakeKeyDeposit from the cardano-cli json.

Aervue commented 2 years ago

Is CIP08 required to sign txFinal?

ashisherc commented 2 years ago

Nope, cip08 is for a different use case

Aervue commented 2 years ago

So I had to just put the cbor inside:

{
    "type": "Tx AlonzoEra",
    "description": "",
    "cborHex": "84a40081825820aa6e3c206a746d54f01c1a5784af38c6ffde22d31df1cb1df23966a82caad6e7000182825839011386a2cb3c846a1f93c4af21e7b417e8695c3c2110b0a45200e04c7729d860f24db593de2cb555f5f6b136d79021ac17640dead5e1dc14591a00124f80825839013b64f00fc61bc8abe86830e3a04b711d5aa322b9e7020240e087857e735b09b8ad8a36226c92a6d4801e3eef6710e3eec708f867ef5786551a0018e673021a000290cd031a040a6ac7a10081825820fefff5e86c41d9e08ee8d133fad8b3582bf216f62339adf1f29345fab4417a6e58400ff81c3fb6ef3d654f0f2b063906db2efac2c9b1eddc8d37bfe4c046024b65826dcad268b00178e7c4f200dccaaef9098f1e4745d919fac08ab92742594ef70af5f6"
}

Also the values from the json were there I was just looking at another genesis file from another wallet. I was just looking at all the wrong places. Thank you for the support I am now on the right track.