Hi. I’ve been trying to submit a serialized transaction to the testnet using Blockfrost’s Submit function (both programatically in a web app using their .NET API and manually through curl) and keep getting this error:
“transaction read error RawCborDecodeError [DecoderErrorDeserialiseFailure “Byron Tx” (DeserialiseFailure 0 “expected list len”),DecoderErrorDeserialiseFailure “Shelley Tx” (DeserialiseFailure 0 “expected list len or indef”),DecoderErrorDeserialiseFailure “Shelley Tx” (DeserialiseFailure 0 “expected list len or indef”),DecoderErrorDeserialiseFailure “Shelley Tx” (DeserialiseFailure 0 “expected list len or indef”),DecoderErrorDeserialiseFailure “Shelley Tx” (DeserialiseFailure 0 “expected list len or indef”)]”
I’ve been able to successfully submit the very same transactions using the cardano-cli submit function without issue. Documentation seems sparse and the error is vague (to me at least). Does anyone have experience using the API submit function successfully?
Okay, I just got one to submit. Rolled my own http request because blockfrost sdk was failing too.
I changed the request.Content to use ByteArrayContent
You might compare what you’re doing to what I’ve got here so we can figure yours out too. I did try ByteArrayContent previously but I think I didn’t have the content-type set properly at that time or something. Either way, it went through finally.
Another potential problem. In the CardanoSharp github docs, it shows an example of simple transaction to send 25 out 100 ADA.
Initially, I had the amount in the transaction as ADA (Like they’re showing in the example) but have instead changed it to lovelace.
Thanks much. I will be revisiting on the weekend and will be trying a couple of things. But this definitely provides some insight. Will post results here.
I’m not sure about js right now but I have been playing with the C# Blockfrost API some more. I built a mint on demand, proof-of-concept. Checks a set of addresses from a SQL database for utxos, if found it constructs a transaction that empties the pay-in address by using a collateral wallet address to pay the fees, mints 2 NFTs and sends 2 ADA with it back to the payer, and the remainder to my “Cold Wallet Payout” address.
Since I found it difficult to find full examples, I’m leaving this here for anyone to reference. Sorry if it’s a bit messy, I over commented lol
For reference, this is from a Quartz .NET Background Job that runs on a timer.
public class MintingJob : IJob
{
private readonly UnitOfWork _unitOfWork;
// Blockfrost API
private readonly ICardanoService _blockfrostService;
public MintingJob()
{
_unitOfWork = new UnitOfWork();
_blockfrostService = new ServiceCollection()
.AddBlockfrost(ConfigurationManager.AppSettings.Get("BlockfrostNetwork").ToString(), ConfigurationManager.AppSettings.Get("BlockfrostKey").ToString())
.BuildServiceProvider()
.GetRequiredService<ICardanoService>();
}
public async Task Execute(IJobExecutionContext context)
{
try
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }, TransactionScopeAsyncFlowOption.Enabled))
{
// Get active pay-in addresses
var activeAddresses = _unitOfWork.CardanoAddressRepository.GetMany(x => x.IsActive == true);
if (activeAddresses != null && activeAddresses.Count() > 0)
{
// Get our collateral wallet so we can use it to pay the fees, so the pay-in address can be fully emptied (Assumption: there is only one address in collateral table)
var colWallet = _unitOfWork.CollateralWalletRepository.GetFirst(x => !string.IsNullOrEmpty(x.Address));
var colUtxos = await _blockfrostService.Addresses.GetUtxosAsync(colWallet.Address);
// Loop addresses and check utxos
foreach (var a in activeAddresses)
{
var utxos = await _blockfrostService.Addresses.GetUtxosAsync(a.Address);
if (utxos != null && utxos.Any())
{
ulong utxoBalance = 0, colUtxoBalance = 0;
// Get the total lovelace balance across all utxos
utxoBalance = Convert.ToUInt64(utxos.Sum(m => m.Amount.Sum(x => int.Parse(x.Quantity))));
colUtxoBalance = Convert.ToUInt64(colUtxos.Sum(m => m.Amount.Sum(x => int.Parse(x.Quantity))));
// Amount to send back with NFTs
ulong sendBackAmount = 2000000;
// We will be sending this much back to our collateral wallet to prevent it from draining too fast
ulong colBalancerAmount = 180000;
// Construct private keys from DB - Low priority keys (use once, discard after an hour)
PrivateKey rootKey = new PrivateKey(a.PrivateKey, a.ChainCode);
Address myAddr = new Address(a.Address);
PrivateKey colKey = new PrivateKey(colWallet.PrivateKey, colWallet.ChainCode);
Address colAddr = new Address(colWallet.Address);
// We have to get this from the utxo details
Address theirAddr = null;
// Our cold storage address for all payouts
Address coldKeyPayoutAddr = new Address(ConfigurationManager.AppSettings.Get("ColdStoragePayoutAddress").ToString());
// Get the utxo details - Since this is a use once and discard address, we are assuming there will only be 1 utxo - Probably a bad assumption for production
var utxoDetails = await _blockfrostService.Transactions.GetUtxosAsync(utxos[0].TxHash);
if (utxoDetails != null)
{
// Get the input address - Again assumptions are made that the first input is the correct address - Probably a bad assumption for production
theirAddr = new Address(utxoDetails.Inputs[0].Address);
// Will need the current protocol parameters to successfully calculate fees
var protocol = await _blockfrostService.Epochs.GetLatestParametersAsync();
if (protocol != null)
{
// Query slot tip
var tip = await _blockfrostService.Blocks.GetLatestAsync();
if (tip != null)
{
// Derive down to our Account Node
var accountNode = rootKey.Derive()
.Derive(PurposeType.Shelley)
.Derive(CoinType.Ada)
.Derive(0);
var colAccountNode = colKey.Derive()
.Derive(PurposeType.Shelley)
.Derive(CoinType.Ada)
.Derive(0);
// Deriving our Payment Node
// note: We did not derive down to the index.
var paymentNode = accountNode
.Derive(RoleType.ExternalChain);
var colPaymentNode = colAccountNode
.Derive(RoleType.ExternalChain);
// Start building transaction
var transactionBody = TransactionBodyBuilder.Create
.SetTtl(Convert.ToUInt32(tip.Slot + 1000))
.SetFee(0);
// Add all utxos in the temp address to the transaction
foreach(var u in utxos)
{
transactionBody.AddInput(u.TxHash, Convert.ToUInt32(u.TxIndex));
}
// Add utxos from out collateral wallet
foreach (var u in colUtxos)
{
transactionBody.AddInput(u.TxHash, Convert.ToUInt32(u.TxIndex));
}
// Generate a Key Pair for your new Policy
var keyPair = KeyPair.GenerateKeyPair();
var policySkey = keyPair.PrivateKey;
var policyVkey = keyPair.PublicKey;
var policyKeyHash = HashUtility.Blake2b244(policyVkey.Key);
// Create a Policy Script with a type of Script All
var policyScriptBuilder = ScriptAllBuilder.Create
.SetScript(NativeScriptBuilder.Create.SetKeyHash(policyKeyHash));
// Derive Sender Keys
var senderKeys = paymentNode.Derive(0);
var colKeys = colPaymentNode.Derive(0);
// Add witnesses
var witnesses = TransactionWitnessSetBuilder.Create
.AddVKeyWitness(senderKeys.PublicKey, senderKeys.PrivateKey)
.AddVKeyWitness(colKeys.PublicKey, colKeys.PrivateKey)
.AddVKeyWitness(policyVkey, policySkey)
.SetNativeScript(policyScriptBuilder);
var policyScript = policyScriptBuilder.Build();
// Generate the Policy Id
var policyId = policyScript.GetPolicyId();
// Create the AWESOME Token
string tokenName = "AWESOME";
uint tokenQuantity = 1;
var tokenAssets = TokenBundleBuilder.Create
.AddToken(policyId, (tokenName + "1").ToBytes(), tokenQuantity)
.AddToken(policyId, (tokenName + "2").ToBytes(), tokenQuantity);
// Build Metadata and Add to Transaction
var auxData = AuxiliaryDataBuilder.Create
.AddMetadata(1234, new { name = "AWESOME MESSAGE" });
// Add outputs
transactionBody
.AddOutput(theirAddr, sendBackAmount, tokenAssets)
.AddOutput(coldKeyPayoutAddr, (utxoBalance - sendBackAmount) - colBalancerAmount)
.AddOutput(colAddr, colUtxoBalance + colBalancerAmount)
.SetMint(tokenAssets);
// Create a Transaction
var transaction = TransactionBuilder.Create
.SetBody(transactionBody)
.SetWitnesses(witnesses)
.SetAuxData(auxData)
.Build();
// Calculate Fee
var fee = transaction.CalculateFee(Convert.ToUInt32(protocol.MinFeeA), Convert.ToUInt32(protocol.MinFeeB));
// Update Fee and Rebuild
transactionBody.SetFee(fee);
transaction = TransactionBuilder.Create
.SetBody(transactionBody)
.SetWitnesses(witnesses)
.SetAuxData(auxData)
.Build();
transaction.TransactionBody.TransactionOutputs.Last().Value.Coin -= fee;
var signedTx = transaction.Serialize();
var response = await _blockfrostService.Transactions.PostTxSubmitAsync(new MemoryStream(signedTx));
if (response != null)
{
// Save transaction hash to DB
}
}
}
}
}
if (a.Created <= DateTime.UtcNow.AddHours(-1))
{
a.IsActive = false;
_unitOfWork.CardanoAddressRepository.Update(a);
}
}
_unitOfWork.Save();
scope.Complete();
}
}
}
catch (Exception ex)
{
throw new JobExecutionException(ex);
}
}
}
Taking a look at the error stack RawCborDecodeError is a pretty good hint. Since you can use the identically CBOR serialized transaction with Cardano CLI successfully this likely means there is a bug in the blockfrost wrapper API rather then the transaction format itself. If you can make the blockfrost submit directly with curl or postman then perhaps open a new issue on Issues · blockfrost/blockfrost-dotnet · GitHub as it sounds like a .NET bug.