0

I have built the following transaction to register a stake pool:

{
  "txHash": "0f874a3cb0ba57ed68a79601781eb9e29666f7f2f0e72ab4ed1f3cc9fcea7dee",
  "ttl": 60310548,
  "txSize": 386,
  "inputs": [
    {
      "txHash": "ca0bca365de069364e4c74b818d8a1c4faebbb095e05635fbab5b1033ede3270",
      "index": 0
    }
  ],
  "outputs": [
    {
      "outputIndex": 0,
      "address": "addr_test1qpypxf9jma2lkmzwn6nhypmynqpmus7x0ekwp2jfdrxqgm8vecdcssszlmywlyn7h0nkqp9jyk8p7dhjecglhu9m75xq4mwl9v",
      "policyId": "ada",
      "assetName": "ada",
      "quantity": "499827547"
    }
  ],
  "metadata": {},
  "outputTotals": [
    {
      "policyId": "ada",
      "assetName": "ada",
      "quantity": "499827547"
    }
  ],
  "fee": "172453",
  "outputAda": "499827547",
  "inputTotalEstimation": "500000000"
}

The transaction has the stake pool certificate attached to it, that's why it consumes an additional 500 ADA for the deposit.

The problem I have is I'm getting the following error when trying to submit it to the blockchain:

The sum of inputs, outputs and fee is not zero. (ValueNotConservedError)

    [
      {
        "valueNotConserved": {
          "consumed": {
            "coins": 1000000000,
            "assets": {}
          },
          "produced": {
            "coins": 500000000,
            "assets": {}
          }
        }
      }
    ]

The input UTxO has a value of 1000 ADA, which is more than enough to cover the stake pool deposit fee. And, in addition to this, if I do...

output + fee + deposit = 499827547 + 172453 + 500000000 = 1000000000

...I get the value of the input of the transaction (so nothing's being lost in theory).

What am I doing wrong?

1 Answer 1

0

This is the code to build a transaction to register a Stake Pool using cardano-serialization-lib:

export const submitRegistrationTransaction = async (
  mnemonic, 
  poolCertificate
) => {

  //const privateKey = await getPrivateKeyFromEntropy(entropy);
  const entropy = mnemonicToEntropy(mnemonic);

  const rootKey = CardanoWasm.Bip32PrivateKey.from_bip39_entropy(
    Buffer.from(entropy, 'hex'),
    Buffer.from(''),
  );
  
  const accountKey = rootKey
    .derive(harden(1852)) // purpose
    .derive(harden(1815)) // coin type
    .derive(harden(0)); // account #0

  const utxoPubKey = accountKey
    .derive(0) // external
    .derive(0)
    .to_public();

  const stakeKey = accountKey
    .derive(2) // chimeric
    .derive(0)
    .to_public();

  const baseAddr = CardanoWasm.BaseAddress.new(
    CardanoWasm.NetworkInfo.testnet().network_id(),
    CardanoWasm.StakeCredential.from_keyhash(utxoPubKey.to_raw_key().hash()),
    CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash()),
  );

  const stakeAddr = CardanoWasm.RewardAddress.new(
    CardanoWasm.NetworkInfo.testnet().network_id(),
    CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash())
  )

  const privateKey = deriveSpendPrivateKey(accountKey, 0).to_raw_key();
  const stakingPrivateKey = deriveStakePrivateKey(accountKey, 0).to_raw_key();
  const fromAddress = baseAddr.to_address().to_bech32("addr_test");

  const params = await getParameters();

  const linearFee = CardanoWasm.LinearFee.new(
    CardanoWasm.BigNum.from_str(params.data.genesis.shelley.protocolParams.minFeeA.toString()),
    CardanoWasm.BigNum.from_str(params.data.genesis.shelley.protocolParams.minFeeB.toString())
  );
  
  const txBuilderConfig = CardanoWasm.TransactionBuilderConfigBuilder.new()
    .fee_algo(linearFee)
    .pool_deposit(CardanoWasm.BigNum.from_str(params.data.genesis.shelley.protocolParams.poolDeposit.toString()))
    .key_deposit(CardanoWasm.BigNum.from_str(params.data.genesis.shelley.protocolParams.keyDeposit.toString()))
    .max_value_size(params.data.genesis.alonzo.maxValueSize)
    .max_tx_size(params.data.genesis.shelley.protocolParams.maxTxSize)
    .coins_per_utxo_word(CardanoWasm.BigNum.from_str(params.data.genesis.alonzo.lovelacePerUTxOWord.toString()))
    .build();

  // instantiate the tx builder with the Cardano protocol parameters - these may change later on
  const txBuilder = CardanoWasm.TransactionBuilder.new(txBuilderConfig);

  const unspentOutputs = await getTxUnspentOutputs(fromAddress);
   
  // pointer address
  const shelleyChangeAddress = CardanoWasm.Address.from_bech32(fromAddress);

  // set the time to live - the absolute slot value before the tx becomes invalid
  const slot = await getSlotNo();
  txBuilder.set_ttl(slot.data.cardano.tip.slotNo + 10000);

  const certificates = CardanoWasm.Certificates.new();
  certificates.add(poolCertificate);
  txBuilder.set_certs(certificates);

  // add a keyhash input - for ADA held in a Shelley-era normal address (Base, Enterprise, Pointer)
  txBuilder.add_inputs_from(unspentOutputs, 2); // LargestFirstMultiAsset

  // calculate the min fee required and send any change to an address
  txBuilder.add_change_if_needed(shelleyChangeAddress)

  // once the transaction is ready, we build it to get the tx body without witnesses
  const txBody = txBuilder.build()
  const txHash = CardanoWasm.hash_transaction(txBody);
  const witnesses = CardanoWasm.TransactionWitnessSet.new();
  const vkeyWitnesses = CardanoWasm.Vkeywitnesses.new();
  const vkeyWitness1 = CardanoWasm.make_vkey_witness(txHash, privateKey);
  //const vkeyWitness2 = CardanoWasm.make_vkey_witness(txHash, stakingPrivateKey);

  vkeyWitnesses.add(vkeyWitness1);

  witnesses.set_vkeys(vkeyWitnesses);
  witnesses.set_native_scripts(CardanoWasm.NativeScripts.new());
  
  // create the finalized transaction with witnesses
  const transaction = CardanoWasm.Transaction.new(
    txBody,
    witnesses,
    undefined, // transaction metadata
  );

  return toHexString(transaction.to_bytes());

}

This is how you create the stake pool certificate:

export const createStakePoolCertificate = (
  mnemonic, 
  stakePoolName, 
  relayIP, 
  relayPort, 
  pledge, 
  cost,
  poolMarginNumerator,
  poolMarginDenominator
) => {

  const entropy = mnemonicToEntropy(mnemonic);

  const rootKey = CardanoWasm.Bip32PrivateKey.from_bip39_entropy(
    Buffer.from(entropy, 'hex'),
    Buffer.from(''),
  );

  const accountKey = rootKey
    .derive(harden(1852)) // purpose
    .derive(harden(1815)) // coin type
    .derive(harden(0)); // account #0

  const utxoPubKey = accountKey
    .derive(0) // external
    .derive(0)
    .to_public();

  const stakeKey = accountKey
    .derive(2) // chimeric
    .derive(0)
    .to_public();

  const baseAddr = CardanoWasm.BaseAddress.new(
    CardanoWasm.NetworkInfo.testnet().network_id(),
    CardanoWasm.StakeCredential.from_keyhash(utxoPubKey.to_raw_key().hash()),
    CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash()),
  );

  const stakeAddr = CardanoWasm.RewardAddress.new(
    CardanoWasm.NetworkInfo.testnet().network_id(),
    CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash())
  )

  const privateKey = deriveSpendPrivateKey(accountKey, 0).to_raw_key();

  // Declare variables for pool params
  const operatorKeyHash = utxoPubKey.to_raw_key().hash().to_bech32('ed25519_pk');

  const nodeKeyGenKES = cardanocliJs.nodeKeyGenKES(stakePoolName);
  const nodeKeyGen = cardanocliJs.nodeKeyGen(stakePoolName);
  const nodeOperationalCert = cardanocliJs.nodeIssueOpCert(stakePoolName);
  const vrfKeyAccount = cardanocliJs.nodeKeyGenVRF(stakePoolName);

  //const kesKeyFile = readFileSync(nodeKeyGenKES.vkey, 'utf-8');
  //console.log(CardanoWasm.KESSignature.from_bytes(Buffer.from(JSON.parse(kesKeyFile).cborHex), "hex"));
  const vrfKeyFile = readFileSync(vrfKeyAccount.vkey, 'utf-8');
  //const vrfKeyHash = CardanoWasm.VRFKeyHash.from_hex(JSON.parse(vrfKeyFile).cborHex.substring(4));
  const vrfKeyHash = CardanoWasm.VRFKeyHash.from_bytes(fromHex(JSON.parse(vrfKeyFile).cborHex.substring(4)));
   
  const poolMargin = CardanoWasm.UnitInterval.new(
    CardanoWasm.BigNum.from_str(poolMarginNumerator), // numerator
    CardanoWasm.BigNum.from_str(poolMarginDenominator)  // denominator
  );

  const poolOwner = CardanoWasm.Ed25519KeyHashes.new();
  poolOwner.add(CardanoWasm.Ed25519KeyHash.from_bech32(operatorKeyHash));

  const relays = CardanoWasm.Relays.new();
  const relay = CardanoWasm.Relay.new_single_host_addr(
    CardanoWasm.SingleHostAddr.new(
      relayPort,
      CardanoWasm.Ipv4.new(new Uint8Array(relayIP.split('.').map((segment) => Number.parseInt(segment, 10))))
    )
  );
  relays.add(relay);

  // Prepare pool params
  const poolParams = CardanoWasm.PoolParams.new(
    CardanoWasm.Ed25519KeyHash.from_bech32(operatorKeyHash),
    vrfKeyHash,
    CardanoWasm.BigNum.from_str(pledge),
    CardanoWasm.BigNum.from_str(cost),
    poolMargin,
    stakeAddr,
    poolOwner,
    relays
  );

  // Create Pool Registration
  const poolRegistration = CardanoWasm.PoolRegistration.new(poolParams);
  console.log(poolRegistration);
  // Generate Pool Certificate
  const poolCertificate = CardanoWasm.Certificate.new_pool_registration(poolRegistration)
  return poolCertificate;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.