buildAndSign method
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);
}