Jump to content
hexripple

Signing multiple transactions offline

Recommended Posts

Hi I was dabbling with signing transactions offline, and I came across the following code

 

const txJSON = {
        "TransactionType": "Payment",
        "Account": fromAddress,
        "Amount": amount,
        "Destination": toAddress,
        "Fee": feeVal,
        "Sequence": parseInt(sequenceVal, 10),  
        "DestinationTag": dTag,
      };

let signedTx = null;

const { signedTransaction } = api.sign(JSON.stringify(txJSON), privateKey);
signedTx = signedTransaction;

After reading the ripple docs, I got to know that feeVal and SequenceVal are not necessary for signing the transaction, and the server inputs these two omitted values on its own, or we can input fee using fee mult max and fee div max.

 

Now I want to know how to sign this transaction offline. How to include fee as fee mult max and fee div max?
Can I simply omit sequence field from the txJSON and make it work?

Should I include the option 'offline' into the request to sign the transaction?

const { signedTransaction } = api.sign(JSON.stringify(txJSON), privateKey, offline: 'true');

How can I sign multiple transactions? It says I cannot sign multiple transactions unless I wait for the earlier transaction to be submitted. But I feel that would be really cumbersome if one has to go to the airgap again and again to sign and then submit the transaction. Any workarounds for this. Can I call the sequence value again and again for different transaction before I go and get them signed on the airgap?

 

Do I need to feed into the value using a nonce generator?

 

Any help would be great.

Edited by hexripple

Share this post


Link to post
Share on other sites

To sign a transaction offline, you need to provide the correct Sequence (sequence number) and Fee (transaction cost) values.

You can't use fee_mult_max / fee_div_max because those are just setting limits on what the auto-provided Fee value would be. Since the XRP Ledger is still operating below capacity, 10 drops is enough for almost any transaction. (See Special Transaction Costs for a list of exceptions.) You could provide a higher value just in case something happens and the ledger gets really slammed and you don't want to wait. But basically, just hard-code a Fee that seems reasonable to you. The Fee should be written as an integer number of drops in string format (this is the rippled API format).

The Sequence number depends on your account. To sign offline, you have to know your account's current Sequence number. It's just a positive integer, nothing special about it. You can look it up with getAccountInfo if you don't know what your account's current Sequence number is; it won't change unless you send a transaction. (When your account is first created, the Sequence number starts at 1. This will change if/when DeletableAccounts gets enabled, so brand-new accounts' Sequence numbers will start at the current ledger index instead.) Anyway, whatever your account's current Sequence number is, your first transaction uses the same Sequence number. Then the next transaction should use a Sequence number that's 1 higher, and the transaction after that uses a Sequence number that's 1 higher than that.

So putting that together, here's your code, modified:

let seqNum = 5; // whatever your account's current Sequence number is

const txJSON1 = {
        "TransactionType": "Payment",
        "Account": fromAddress,
        "Amount": amount,
        "Destination": toAddress,
        "Fee": "10", // This minimum Fee is enough for the vast majority of cases
        "Sequence": seqNum,  
        "DestinationTag": dTag,
      };

const { signedTransaction1 } = api.sign(JSON.stringify(txJSON1), privateKey);

seqNum += 1; // Increase sequence number

const txJSON2 = {
        "TransactionType": "Payment",
        "Account": fromAddress,
        "Amount": amount,
        "Destination": toAddress,
        "Fee": "10",
        "Sequence": seqNum,  
        "DestinationTag": dTag,
      };

const { signedTransaction2 } = api.sign(JSON.stringify(txJSON2), privateKey);

You can repeat that any number of times and then submit all the transactions at the same time.

If for some reason one of the transactions doesn't get accepted into a ledger, then all the ones after it won't get accepted either. (This is one of the reasons that transactions fail with "tec" codes and get included in ledgers rather than just being forgotten about when they fail.) Feel free to submit the same signed transaction multiple times, by the way, in case something seems off. It won't get processed more than once. If it really comes down to it—maybe you signed transactions with sequence numbers 11 through 13 but transaction 11 is malformed or something—you can go back to your offline machine and sign just a replacement transaction that uses sequence 11, submit that one, and then your previously-signed transactions 12 and 13 should be good to go. (See "About Canceling a Transaction")

Hope that helps!

Share this post


Link to post
Share on other sites

Thanks, although I had figured out the signing of an offline transaction this really cleared some details about multiple transactions.

One more doubt @mDuo13, once I sign the transaction, I get the response in the form of a BLOB, which I see is a hexadecimal representation of the binary transaction. Is there anyway to get back trx_json instead of trx_blob when I sign the transaction. My application requires that I submit this transaction in JSON for verification purposes.

 

Thanks a lot!!

Share this post


Link to post
Share on other sites

I think ripple-lib on its own doesn't have an easy way to do that. That would be a good idea for a feature request! I think it shouldn't be too hard to implement, because the library internally has the ability to deserialize transaction blobs and the rippled equivalent API already returns both the JSON and binary formats.

That said, you can decode the binary pretty easily by using ripple-binary-codec, which is one of ripple-lib's dependencies. For example:

const binarycodec = require("ripple-binary-codec")

const tx_binary = "120007220000000024000195F964400000170A53AC2065D5460561E"+
                  "C9DE000000000000000000000000000494C53000000000092D70596"+
                  "8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210"+
                  "28472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F"+
                  "418D6A71667447304502202ABE08D5E78D1E74A4C18F2714F64E87B"+
                  "8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C059"+
                  "1CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C811439408"+
                  "A69F0895E62149CFCC006FB89FA7D1E6E5D"

const decoded_json = binarycodec.decode(tx_binary)
console.log(decoded_json)

// {
//   TransactionType: 'OfferCreate',
//   Flags: 0,
//   Sequence: 103929,
//   TakerPays: '98957503520',
//   TakerGets: {
//     value: '1694.768',
//     currency: 'ILS',
//     issuer: 'rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9'
//   },
//   Fee: '10',
//   SigningPubKey: '028472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F418D6A7166',
//   TxnSignature: '304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C0591CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C',
//   Account: 'raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3'
// }

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...