Closed bakon11 closed 2 years ago
Not sure I follow the multi assets not included part but I just got add_inputs_from working a simple transaction with just an ADA value, heres some code if it helps:
async function getParameters() {
const id = '<your blockfrost id>';
const result = await axios.get(
"https://cardano-testnet.blockfrost.io/api/v0/epochs/latest/parameters",
{
headers: {
project_id: id,
},
}
);
return {
linearFee: {
minFeeA: result.data.min_fee_a.toString(),
minFeeB: result.data.min_fee_b.toString(),
},
poolDeposit: result.data.pool_deposit,
keyDeposit: result.data.key_deposit,
coinsPerUtxoWord: result.data.coins_per_utxo_word,
maxValSize: result.data.max_val_size,
priceMem: result.data.price_mem,
priceStep: result.data.price_step,
maxTxSize: parseInt(result.data.max_tx_size),
};
}
// account object from ccvault wallet connector
const account = await window.cardano.ccvault.enable();
// receiving address
const paymentAddress = "<address to send ADA to>";
// change address
const address = await account.getChangeAddress();
const changeAddress = Cardano.Address.from_bytes(
Buffer.from(address, "hex")
).to_bech32();
// parameters for config
const protocolParameters = await this.getParameters();
// config
const txConfig = Cardano.TransactionBuilderConfigBuilder.new()
.coins_per_utxo_word(
Cardano.BigNum.from_str(protocolParameters.coinsPerUtxoWord)
)
.fee_algo(
Cardano.LinearFee.new(
Cardano.BigNum.from_str(protocolParameters.linearFee.minFeeA),
Cardano.BigNum.from_str(protocolParameters.linearFee.minFeeB)
)
)
.key_deposit(Cardano.BigNum.from_str(protocolParameters.keyDeposit))
.pool_deposit(Cardano.BigNum.from_str(protocolParameters.poolDeposit))
.max_tx_size(protocolParameters.maxTxSize)
.max_value_size(protocolParameters.maxValSize)
.prefer_pure_change(true)
.build();
// builder
const txBuilder = Cardano.TransactionBuilder.new(txConfig);
// outputs
txBuilder.add_output(
Cardano.TransactionOutputBuilder.new()
.with_address(Cardano.Address.from_bech32(paymentAddress))
.next()
.with_value(Cardano.Value.new(Cardano.BigNum.from_str(sendAmount)))
.build()
);
// convert utxos from wallet connector
const utxosFromWalletConnector = (await account.getUtxos()).map((utxo) =>
Cardano.TransactionUnspentOutput.from_bytes(Buffer.from(utxo, "hex"))
);
// create TransactionUnspentOutputs for 'add_inputs_from' function
const utxoOutputs = Cardano.TransactionUnspentOutputs.new();
utxosFromWalletConnector.map((currentUtxo) => {
utxoOutputs.add(currentUtxo);
});
// inputs with coin selection
// 0 for LargestFirst, 1 RandomImprove 2,3 Mutli asset
txBuilder.add_inputs_from(utxoOutputs, 0);
txBuilder.add_change_if_needed(Cardano.Address.from_bech32(changeAddress));
const txBody = txBuilder.build();
const transaction = Cardano.Transaction.new(
txBuilder.build(),
Cardano.TransactionWitnessSet.new()
);
const witness = await account.signTx(
Buffer.from(transaction.to_bytes(), "hex").toString("hex")
);
const signedTx = Cardano.Transaction.new(
txBody,
Cardano.TransactionWitnessSet.from_bytes(Buffer.from(witness, "hex")),
undefined // transaction metadata
);
const txHash = await account.submitTx(
Buffer.from(signedTx.to_bytes()).toString("hex")
);
The utxo being passed here from the wallet collector: is it just the txix
or is it the the whole txix#index
?
// convert utxos from wallet connector
const utxosFromWalletConnector = (await account.getUtxos()).map((utxo) =>
Cardano.TransactionUnspentOutput.from_bytes(Buffer.from(utxo, "hex"))
);
The wallet connector returns a hex encoded bytes string of a TransactionUnspentOutput theres probably better way to go from that to the array of TransactionUnspentOutputs required by add_imputs_from but it got the job done
So I pass my utxos as an array of objects like so: [{ txix: string, txIndex: number, inputValue: string }]
When I get to Convert provided UTXOs
which in your example it is convert utxos from wallet connector
I get the following error: Deserialization failed in TransactionUnspentOutput because: No variant matched
As you can see I am only passing the txix without the index, I'm wondering if that's what my issue is. Maybe I should just create a normal array out of it txix&index
?
// Set all provided UTXOs as inputs and their values and indexes
const utxoOutputs = CardanoWasm.TransactionUnspentOutputs.new();
if ( JSON.parse(utxos).length > 0 ){
console.log("Convert provided UTXOs");
const utxosFromWallet = JSON.parse(utxos).map(( utxo: any ) => {
console.log( "adding input: " + utxo.txix )
CardanoWasm.TransactionUnspentOutput.from_bytes(Buffer.from(utxo.txix, "hex"))
});
// create TransactionUnspentOutputs for 'add_inputs_from' function
utxosFromWallet.map(( currentUtxo: any ) => {
utxoOutputs.add(currentUtxo);
});
};
in this issue
https://github.com/Emurgo/cardano-serialization-lib/issues/285
@vsubhuman has an example with the txix and index to create an input which I think you can then use to construct a TransactionUnspentOutput something like
CardanoWasm.TransactionUnspentOutput.new(
CardanoWasm.TransactionInput.new(
CardanoWasm.TransactionHash.from_bytes(
Buffer.from(txix, "hex")
), // tx hash
txixIndex, // index
),
CardanoWasm.TransactionOutputBuilder.new()
.with_address(CardanoWasm.Address.from_bech32("addr1vyy6nhfyks7wdu3dudslys37v252w2nwhv0fw2nfawemmnqs6l44z").unwrap())
.next().unwrap()
.with_value(&value)
.build().unwrap()
)
thats allot of copy pasta not something I actually ran so buyer beware ;)
If you search the library there are some tests that have examples of how they go together that I used
So I have it working when providing multiple UTXOs with just lovelace in them where it'll calculate everything.
The issue I'm running into, is when I try to spend a UTXO with multiple assets in it and I'm only sending one of those assets to another address and keeping the other two.
I get a unbalanced UTXO error
, if I just have a single output for the asset I'm trying to send and was wanting to use add_inputs_from
to auto detect the left over assets to create the change
outputs automatically.
Not entirely sure I follow, haven't worked with tx sending other assets but off the top of my head you need to build the tx for multi asset as well as setting a MutliAsset output for whatever asset you're sending, I've seen a couple issues here with multi asset examples but didn't really note them since that wasn't what I was doing at the time. If you go towards the end of tx_builders.rs in the rust/src file of the library you'll find this test that might help, but again not something I've done
#[test]
fn tx_builder_cip2_largest_first_multiasset() {
// we have a = 0 so we know adding inputs/outputs doesn't change the fee so we can analyze more
let linear_fee = LinearFee::new(&to_bignum(0), &to_bignum(0));
let mut tx_builder = create_tx_builder_with_fee(&create_linear_fee(0, 0));
let pid1 = PolicyID::from([1u8; 28]);
let pid2 = PolicyID::from([2u8; 28]);
let asset_name1 = AssetName::new(vec![1u8; 8]).unwrap();
let asset_name2 = AssetName::new(vec![2u8; 11]).unwrap();
let asset_name3 = AssetName::new(vec![3u8; 9]).unwrap();
let mut output_value = Value::new(&to_bignum(415));
let mut output_ma = MultiAsset::new();
output_ma.set_asset(&pid1, &asset_name1, to_bignum(5));
output_ma.set_asset(&pid1, &asset_name2, to_bignum(1));
output_ma.set_asset(&pid2, &asset_name2, to_bignum(2));
output_ma.set_asset(&pid2, &asset_name3, to_bignum(4));
output_value.set_multiasset(&output_ma);
tx_builder.add_output(&TransactionOutput::new(
&Address::from_bech32("addr1vyy6nhfyks7wdu3dudslys37v252w2nwhv0fw2nfawemmnqs6l44z").unwrap(),
&output_value
)).unwrap();
let mut available_inputs = TransactionUnspentOutputs::new();
// should not be taken
available_inputs.add(&make_input(0u8, Value::new(&to_bignum(150))));
// should not be taken
let mut input1 = make_input(1u8, Value::new(&to_bignum(200)));
let mut ma1 = MultiAsset::new();
ma1.set_asset(&pid1, &asset_name1, to_bignum(10));
ma1.set_asset(&pid1, &asset_name2, to_bignum(1));
ma1.set_asset(&pid2, &asset_name2, to_bignum(2));
input1.output.amount.set_multiasset(&ma1);
available_inputs.add(&input1);
// taken first to satisfy pid1:asset_name1 (but also satisfies pid2:asset_name3)
let mut input2 = make_input(2u8, Value::new(&to_bignum(10)));
let mut ma2 = MultiAsset::new();
ma2.set_asset(&pid1, &asset_name1, to_bignum(20));
ma2.set_asset(&pid2, &asset_name3, to_bignum(4));
input2.output.amount.set_multiasset(&ma2);
available_inputs.add(&input2);
// taken second to satisfy pid1:asset_name2 (but also satisfies pid2:asset_name1)
let mut input3 = make_input(3u8, Value::new(&to_bignum(50)));
let mut ma3 = MultiAsset::new();
ma3.set_asset(&pid2, &asset_name1, to_bignum(5));
ma3.set_asset(&pid1, &asset_name2, to_bignum(15));
input3.output.amount.multiasset = Some(ma3);
available_inputs.add(&input3);
// should not be taken either
let mut input4 = make_input(4u8, Value::new(&to_bignum(10)));
let mut ma4 = MultiAsset::new();
ma4.set_asset(&pid1, &asset_name1, to_bignum(10));
ma4.set_asset(&pid1, &asset_name2, to_bignum(10));
input4.output.amount.multiasset = Some(ma4);
available_inputs.add(&input4);
// taken third to satisfy pid2:asset_name_2
let mut input5 = make_input(5u8, Value::new(&to_bignum(10)));
let mut ma5 = MultiAsset::new();
ma5.set_asset(&pid1, &asset_name2, to_bignum(10));
ma5.set_asset(&pid2, &asset_name2, to_bignum(3));
input5.output.amount.multiasset = Some(ma5);
available_inputs.add(&input5);
// should be taken to get enough ADA
let input6 = make_input(6u8, Value::new(&to_bignum(400)));
available_inputs.add(&input6);
// should not be taken
available_inputs.add(&make_input(7u8, Value::new(&to_bignum(100))));
tx_builder.add_inputs_from(&available_inputs, CoinSelectionStrategyCIP2::LargestFirstMultiAsset).unwrap();
let change_addr = ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho").unwrap().to_address();
let change_added = tx_builder.add_change_if_needed(&change_addr).unwrap();
assert!(change_added);
let tx = tx_builder.build().unwrap();
assert_eq!(2, tx.outputs().len());
assert_eq!(4, tx.inputs().len());
// check order expected per-asset
assert_eq!(2u8, tx.inputs().get(0).transaction_id().0[0]);
assert_eq!(3u8, tx.inputs().get(1).transaction_id().0[0]);
assert_eq!(5u8, tx.inputs().get(2).transaction_id().0[0]);
assert_eq!(6u8, tx.inputs().get(3).transaction_id().0[0]);
let change = tx.outputs().get(1).amount;
assert_eq!(from_bignum(&change.coin), 55);
let change_ma = change.multiasset().unwrap();
assert_eq!(15, from_bignum(&change_ma.get_asset(&pid1, &asset_name1)));
assert_eq!(24, from_bignum(&change_ma.get_asset(&pid1, &asset_name2)));
assert_eq!(1, from_bignum(&change_ma.get_asset(&pid2, &asset_name2)));
assert_eq!(0, from_bignum(&change_ma.get_asset(&pid2, &asset_name3)));
let expected_input = input2.output.amount
.checked_add(&input3.output.amount)
.unwrap()
.checked_add(&input5.output.amount)
.unwrap()
.checked_add(&input6.output.amount)
.unwrap();
let expected_change = expected_input.checked_sub(&output_value).unwrap();
assert_eq!(expected_change, change);
}
So I have it working when providing multiple UTXOs with just lovelace in them where it'll calculate everything.
The issue I'm running into, is when I try to spend a UTXO with multiple assets in it and I'm only sending one of those assets to another address and keeping the other two.
I get a
unbalanced UTXO error
, if I just have a single output for the asset I'm trying to send and was wanting to useadd_inputs_from
to auto detect the left over assets to create thechange
outputs automatically.
Hey, can you give me some insight on how you did this? I cant quite figure it out.
So I removed the section of the code where I send assets since its not 100% complete yet. But in the example below if you provide the amount of ADA you want to send for the outputValue
param and enough UTXOs. So if you want to send 50ada and have two UTXOs with 30ada each, using add_change_if_needed
it will calculate the change UTXO automatically.
/*
utxoKey, // is the utxo key derived from the account prv key
accountKeyPrv
.derive(0) // 0 external || 1 change || 2 stake key
.derive(index) // index
utxos, // [{ txix: string, txIndex: number, inputValue: string }]
assets, // [{ policyID: string, assetName: string, assetAmount: string }]
metadata, // [{ label: string, metadata: { sendfrom: "cardano box!!!!", msg: "Testing auto change calculation" } }]
outputAddress, // address you're sending assets or lovelace to
outputValue, // how much lovelace are you sending
changeAddress, // where unused assets or lovelace from UTXO should go to
*/
import CardanoWasm = require('@emurgo/cardano-serialization-lib-nodejs')
const genTx = async ( utxoKey: any, utxos: string, assets:string, metadata: string, outputAddress: string, outputValue: string, changeAddress: string, txTTL: number ) => {
let includeMeta = 0;
try{
// instantiate the tx builder with the Cardano protocol parameters - these may change later on
const txBuilder = await CardanoWasm.TransactionBuilder.new(
CardanoWasm.TransactionBuilderConfigBuilder.new()
.fee_algo( CardanoWasm.LinearFee.new(CardanoWasm.BigNum.from_str('44'),CardanoWasm.BigNum.from_str('155381')))
.pool_deposit(CardanoWasm.BigNum.from_str('500000000'),)
.key_deposit( CardanoWasm.BigNum.from_str('2000000'),)
.coins_per_utxo_word(CardanoWasm.BigNum.from_str('34482'))
.max_value_size(5000)
.max_tx_size(16384)
.build()
);
// Output for ADA being send, Assets will use min required coin change will be calculated after all inputs are passed
console.log( "adding Ada spent output");
txBuilder.add_output(
CardanoWasm.TransactionOutputBuilder.new()
.with_address( CardanoWasm.Address.from_bech32(outputAddress) )
.next()
.with_value( CardanoWasm.Value.new( CardanoWasm.BigNum.from_str(outputValue)))
.build()
);
// METADATA
if( JSON.parse(metadata).length > 0 ){
// MetaData stuff
console.log('adding meta');
const generalTxMeta = CardanoWasm.GeneralTransactionMetadata.new()
const auxData = CardanoWasm.AuxiliaryData.new();
JSON.parse(metadata).map(( meta: any) =>
generalTxMeta.insert(
CardanoWasm.BigNum.from_str( meta.label ),
CardanoWasm.encode_json_str_to_metadatum(
JSON.stringify(meta.metadata),
0
)
)
);
await auxData.set_metadata(generalTxMeta);
await txBuilder.set_auxiliary_data(auxData);
includeMeta = 1;
};
// set all provided UTXOs as inputs and their values and indexes
if ( JSON.parse(utxos).length > 0 ){
console.log("adding inputs");
JSON.parse(utxos).map(( utxo: any ) => {
console.log( "adding input: " + utxo.txix )
// set utxo input map the array
txBuilder.add_input(
CardanoWasm.Address.from_bech32(changeAddress),
CardanoWasm.TransactionInput.new(
CardanoWasm.TransactionHash.from_bytes(
Buffer.from(utxo.txix, "hex")
), // tx hash
utxo.txIndex, // index
),
CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(utxo.inputValue))
);
});
};
// set the time to live - the absolute slot value before the tx becomes invalid
console.log("setting ttl");
await txBuilder.set_ttl(txTTL);
// calculate the min fee required and send any change to an address
console.log("setting change");
await txBuilder.add_change_if_needed( CardanoWasm.Address.from_bech32(changeAddress) );
// once the transaction is ready, we build it to get the tx body without witnesses
console.log("Building and singing TX");
const newTX = await txBuilder.build_tx();
const txHash = await CardanoWasm.hash_transaction(newTX.body());
// add keyhash witnesses
const witnesses = await CardanoWasm.TransactionWitnessSet.new();
const vkeyWitnesses = await CardanoWasm.Vkeywitnesses.new();
const vkeyWitness = await await CardanoWasm.make_vkey_witness(txHash, utxoKey.to_raw_key());
await vkeyWitnesses.add(vkeyWitness);
await witnesses.set_vkeys(vkeyWitnesses);
// create the finalized transaction with witnesses
const transaction = await CardanoWasm.Transaction.new(
newTX.body(),
witnesses,
includeMeta == 1 ? newTX.auxiliary_data() : undefined, //metadata
);
const txHex = await Buffer.from(transaction.to_bytes()).toString("hex");
console.log(txHex);
return(txHex);
}catch(error){
console.log( error );
return( error );
};
};
Awesome thanks! Just what I needed, got it working now. I appreciate your help.
Have you attempted to submit the tx made from this code? Not able to generate a "valid" transaction to submit on the cli.
getting an OutsideValidityIntervalUTxO
error
Have a quick question on how to properly use
add_inputs_from
. Would greatly appreciate a example for UTXO with assets.Main problem I am having, is if I have a UTXO with multiple assets in it and I am only transferring a single asset out of it.
Just using
add_change_if_needed
will not take the not transferred assets into consideration.I started writing a function that will check which assets from the UTXO have been provided as an output and then generate the outputs for the unsent assets and send them to the change address.
But I was wondering if I can just use
add_inputs_from
and provide the UTXO that hold the said assets above and calculates the ones that aren't being transferred automatically to provide them toadd_change_if_needed
Thank you.