buildAndSign method

Future<Result<ShelleyTransaction, String>> buildAndSign({
  1. bool mustBalance = true,
})

Automates building a valid, signed transaction inlcuding checking required inputs, calculating ttl, fee and change.

Coin selection must be done externally and assigned to the 'inputs' property. The fee is automaticly calculated and adjusted based on the final transaction size. If no outputs are supplied, a toAddress and value are required instead. An unused changeAddress should be supplied weather it's needed or not. Bip32KeyPair is required for signing the transaction and supplying the public key to the witness. The same instance of BlockchainAdapter must be supplied that read the blockchain balances as it will contain the cached Utx0s needed to calculate the input amounts.

TODO handle edge case where selectd coins have to be changed based on fee adjustment.

Implementation

Future<Result<ShelleyTransaction, String>> buildAndSign(
    {bool mustBalance = true}) async {
  final dataCheck = _checkContraints();
  if (dataCheck.isErr()) return Err(dataCheck.unwrapErr());
  //calculate time to live if not supplied
  if (_ttl == 0) {
    final result = await calculateTimeToLive();
    if (result.isErr()) {
      return Err(result.unwrapErr());
    }
    _ttl = result.unwrap();
  }
  if (_inputs.isEmpty) return Err("inputs are empty");
  //convert value into spend output if not zero
  if (_value.coin > coinZero && _toAddress != null) {
    ShelleyTransactionOutput spendOutput = ShelleyTransactionOutput(
        address: _toAddress!.toBech32(), value: _value);
    _outputs.add(spendOutput);
  }
  var body = _buildBody();
  //adjust change to balance transaction
  final balanceResult = body.balancedOutputsWithChange(
      changeAddress: _changeAddress!, cache: _blockchainAdapter!, fee: _fee);
  if (balanceResult.isErr()) return Err(balanceResult.unwrapErr());
  _outputs = balanceResult.unwrap();
  //build the complete signed transaction so we can calculate a more accurate fee
  body = _buildBody();
  Map<ShelleyAddress, Bip32KeyPair> utxoKeyPairs = _loadUtxosAndTheirKeys();
  if (utxoKeyPairs.isEmpty) {
    return Err("no UTxOs found in transaction");
  }
  _witnessSet = _sign(
      body: body,
      keyPairSet: utxoKeyPairs.values.toSet(),
      fakeSignature: true);
  var tx = ShelleyTransaction(
      body: body, witnessSet: _witnessSet, metadata: _metadata);
  _fee = calculateMinFee(tx: tx, minFee: _minFee);
  //rebalance change to fit the new fee
  final balanceResult2 = body.balancedOutputsWithChange(
      changeAddress: _changeAddress!, cache: _blockchainAdapter!, fee: _fee);
  if (balanceResult2.isErr()) return Err(balanceResult2.unwrapErr());
  _outputs = balanceResult2.unwrap();
  body = _buildBody();
  //re-sign to capture changes
  _witnessSet = _sign(body: body, keyPairSet: utxoKeyPairs.values.toSet());
  tx = ShelleyTransaction(
      body: body, witnessSet: _witnessSet, metadata: _metadata);
  if (mustBalance) {
    final balancedResult =
        tx.body.transactionIsBalanced(cache: _blockchainAdapter!, fee: _fee);
    if (balancedResult.isErr()) return Err(balancedResult.unwrapErr());
  }
  return Ok(tx);
}