Blockfrost API Submit function

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?

2 Likes

Getting the same response right now. Did you ever solve this?

I’ll update if I figure it out.

Unfortunately not yet.

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
callingpostcbor
postCBOR

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.
lovelace

1 Like

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.

Hi @deardisaster I am on this issue as well. Is there a way to solve this using js ?

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);
        }
    }
}
1 Like

Thanks so much for posting this here. Will have a look and I’m sure this will help solve the issue I’ve been having.

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.

Hi @deardisaster, Do you know any way to do this on python?