Emurgo / cardano-serialization-lib

This is a library, written in Rust, for serialization & deserialization of data structures used in Cardano's Haskell implementation of Alonzo along with useful utility functions.
Other
230 stars 124 forks source link

Unable to deregistration stake raw transaction #651

Closed vladcorn closed 8 months ago

vladcorn commented 9 months ago

Hi, i have some issue with input and outputs.

async createDeactivationTransaction(
    data: CreateDeactivationTransactionRequest,
  ): Promise<CreateDeactivationTransactionResponse> {
    const txBuilder = await this.createBasicTransaction();
    let address = data?.paymentUserAddress;
    let stakeAddress = data?.stakeUserAddress;

    if (stakeAddress && !address) {
      try {
        const addresses = await this.blockfrostClient.accountsAddresses(stakeAddress);
        address = addresses[0].address;
      } catch (error) {
        if (error instanceof BlockfrostServerError && error.status_code === 400) {
          throw new RpcException({
            code: status.INVALID_ARGUMENT,
            message: error,
          });
        }
        throw new RpcException({
          code: status.INTERNAL,
          error,
        });
      }
    }

    if (address && !stakeAddress) {
      try {
        const addresses = await this.blockfrostClient.addresses(address);
        stakeAddress = addresses.stake_address;
      } catch (error) {
        if (error instanceof BlockfrostServerError && error.status_code === 400) {
          throw new RpcException({
            code: status.INVALID_ARGUMENT,
            message: error,
          });
        }
        throw new RpcException({
          code: status.INTERNAL,
          error,
        });
      }
    }

    const accountDelegation = await this.blockfrostClient.accountsDelegationsAll(stakeAddress);
    const { wasmWalletAddress, baseWalletAddress } = await this.createAddresses(address);

    const walletInfo = await this.blockfrostClient.addresses(address);

    const stakeCredentials = baseWalletAddress.stake_cred();
    const certificates = Certificates.new();
    const stakeDeRegistration = StakeDeregistration.new(stakeCredentials);
    const stakeDeRegistrationCertificate = Certificate.new_stake_deregistration(stakeDeRegistration);
    certificates.add(stakeDeRegistrationCertificate);
    txBuilder.set_certs(certificates);

    const utxo = await this.blockfrostClient.addressesUtxosAll(address);
    const walletBalance = BigNum.from_str(walletInfo.amount.find((a) => a.unit === 'lovelace')?.quantity);

    const txInput =
      utxo.find((u) => u.tx_hash === accountDelegation.find((ad) => ad.tx_hash === u.tx_hash)?.tx_hash) ||
      accountDelegation[accountDelegation.length - 1];
    if (!txInput) {
      this.logger.warn(`Failed to find staking tx hash for ${address}`);
      throw new RpcException({
        code: status.NOT_FOUND,
        message: 'Failed to find staking tx hash',
      });
    }

    const amount = Array.isArray(txInput.amount) ? txInput.amount[0].quantity : txInput.amount;
    txBuilder.add_key_input(
      baseWalletAddress.payment_cred().to_keyhash(),
      TransactionInput.new(TransactionHash.from_bytes(Buffer.from(txInput.tx_hash, 'hex')), 0),
      Value.new(BigNum.from_str(amount)),
    );

    const fee = txBuilder.min_fee().less_than(BigNum.from_str('171661')) ? BigNum.from_str('171661') : txBuilder.min_fee();

    // Add an output to the tx
    // WalletBalance - fee
    txBuilder.add_output(
      TransactionOutput.new(wasmWalletAddress, Value.new(walletBalance.checked_sub(fee.checked_add(BigNum.from_str('2000000'))))),
    );

    // Calculate the min fee required and send any change to an address
    txBuilder.add_change_if_needed(wasmWalletAddress);

    return {
      hash: txBuilder.build_tx().to_hex(),
    };
  }

After submitting i have an issue

BlockfrostServerError: "transaction submit error ShelleyTxValidationError ShelleyBasedEraBabbage (ApplyTxError [UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (ValueNotConservedUTxO (Value 2000000 (fromList [])) (Value 9993743887 (fromList []))))),UtxowFailure (UtxoFailure (FromAlonzoUtxoFail (BadInputsUTxO (fromList [TxIn (TxId {_unTxId = SafeHash \"c5666c061ac6fc50ed180199b69e3a40544e6ba1550289b0aadb4b887cc2a3da\"}) (TxIx 0)]))))])"
vladcorn commented 9 months ago

this code for preprod env. Also i check with new address, where i stake and unstake works fine. but if stake has been delegated and you use ADA after it, this code doesn't work. Any thoughts?

vladcorn commented 8 months ago
async createDeactivationTransaction(
    data: CreateDeactivationTransactionRequest,
  ): Promise<CreateDeactivationTransactionResponse> {
    const txBuilder = await this.createBasicTransaction();
    let address = data?.paymentUserAddress;
    let stakeAddress = data?.stakeUserAddress;

    if (stakeAddress && !address) {
      try {
        const addresses = await this.blockfrostClient.accountsAddresses(stakeAddress);
        address = addresses[0].address;
      } catch (error) {
        if (error instanceof BlockfrostServerError && error.status_code === 400) {
          throw new RpcException({
            code: status.INVALID_ARGUMENT,
            message: error,
          });
        }
        throw new RpcException({
          code: status.INTERNAL,
          error,
        });
      }
    }

    if (address && !stakeAddress) {
      try {
        const addresses = await this.blockfrostClient.addresses(address);
        stakeAddress = addresses.stake_address;
      } catch (error) {
        if (error instanceof BlockfrostServerError && error.status_code === 400) {
          throw new RpcException({
            code: status.INVALID_ARGUMENT,
            message: error,
          });
        }
        throw new RpcException({
          code: status.INTERNAL,
          error,
        });
      }
    }

    const { wasmWalletAddress, baseWalletAddress } = await this.createAddresses(address);

    const stakeCredentials = baseWalletAddress.stake_cred();
    const certificates = Certificates.new();
    const stakeDeRegistration = StakeDeregistration.new(stakeCredentials);
    const stakeDeRegistrationCertificate = Certificate.new_stake_deregistration(stakeDeRegistration);
    certificates.add(stakeDeRegistrationCertificate);
    txBuilder.set_certs(certificates);

    let utxo: Awaited<ReturnType<BlockFrostAPI['addressesUtxos']>> = [];
    try {
      utxo = await this.blockfrostClient.addressesUtxos(address);
    } catch (error) {
      if (error instanceof BlockfrostServerError && error.status_code === 404) {
        throw new RpcException(`You should send ADA to ${address} to have enough funds to sent a transaction`);
      } else {
        throw new RpcException({
          code: status.INTERNAL,
          error,
        });
      }
    }

    // Filter out multi asset utxo to keep this simple
    const lovelaceUtxos = utxo.filter((u) => !u.amount.find((a) => a.unit !== 'lovelace'));

    const txInput = lovelaceUtxos[0];
    if (!txInput) {
      this.logger.warn(`Failed to find staking tx hash for ${address}`);
      throw new RpcException({
        code: status.NOT_FOUND,
        message: 'Failed to find staking tx hash',
      });
    }

    const txAmount = txInput.amount.find((tx) => tx.unit === 'lovelace').quantity;

    txBuilder.add_input(
      wasmWalletAddress,
      TransactionInput.new(TransactionHash.from_hex(txInput.tx_hash), txInput.output_index),
      Value.new(BigNum.from_str(txAmount)),
    );

    // Calculate the min fee required and send any change to an address
    txBuilder.add_change_if_needed(wasmWalletAddress);

    return {
      hash: txBuilder.build_tx().to_hex(),
    };
  }

fixed with this