Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dogecoin integration #1990

Closed
james-triangle opened this issue Oct 9, 2023 · 29 comments
Closed

Dogecoin integration #1990

james-triangle opened this issue Oct 9, 2023 · 29 comments

Comments

@james-triangle
Copy link

Hi, is there an example on how to use bitcoinjs-lib to create a transaction on the dogecoin network?

@junderw
Copy link
Member

junderw commented Oct 9, 2023

Swapping out the network should work for most transactions, but unfortunately Doge has a maximum limit much higher than 53 bits, so some transactions will throw errors with number overflows.

This especially happens often when trying to spend a utxo you received from an exchange withdrawal. Since the change can easily get larger than 53 bits and PSBT needs to parse the whole parent transaction.

If BitcoinJS supported native JS BigInt, then we would be able to fully support Doge.

@junderw junderw closed this as completed Oct 9, 2023
@james-triangle
Copy link
Author

james-triangle commented Oct 10, 2023

Hi @junderw,
I am taking a look at bitcoinjs-lib's 1-to-1 transaction example and see that it uses PSBT. From my understanding, since dogecoin doesn't use this and TransactionBuilder is replaced by PSBT, is there a different recommended direction? Where in this example would I be able to change the network?

@junderw
Copy link
Member

junderw commented Oct 10, 2023

PSBT is merely a transaction builder. It is network agnostic. You can use it with Dogecoin (and in fact I personally know of many implementations that do)

When creating a new PSBT you can pass in an options argument which has a network key that you can replace with Doge network info.

ie.

const psbt = new Psbt({ network: doge });

Where the variable doge contains the network object format defined in network.ts

@junderw
Copy link
Member

junderw commented Oct 10, 2023

As you can see here, using dogecoin network parameters will allow the test to work.

const dogecoin = { // <-------- ADD THIS
    messagePrefix: '\x19Dogecoin Signed Message:\n',
    bech32: 'doge',
    bip32: {
        public: 0x02facafd,
        private: 0x02fac398,
    },
    pubKeyHash: 0x1e,
    scriptHash: 0x16,
    wif: 0x9e,
}

const alice = ECPair.fromWIF(
  // Changed the WIF key format to match Doge
  'QUJJhNSzNZvb57bnACAp76p8rEd6vP4RnLTC7qwKRMEq3VG61eUB',
  dogecoin, // <-------- ADD THIS
);
const psbt = new bitcoin.Psbt({ network: dogecoin }); // <-------- ADD THIS
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0); // These are defaults. This line is not needed.
psbt.addInput({
  // if hash is string, txid, if hash is Buffer, is reversed compared to txid
  hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
  index: 0,
  sequence: 0xffffffff, // These are defaults. This line is not needed.

  // non-segwit inputs now require passing the whole previous tx as Buffer
  nonWitnessUtxo: Buffer.from(
    '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
      '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
      'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
      '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
      '631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
      // value in satoshis (Int64LE) = 0x015f90 = 90000
      '905f010000000000' +
      // scriptPubkey length
      '19' +
      // scriptPubkey
      '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' +
      // locktime
      '00000000',
    'hex',
  ),

  // // If this input was segwit, instead of nonWitnessUtxo, you would add
  // // a witnessUtxo as follows. The scriptPubkey and the value only are needed.
  // witnessUtxo: {
  //   script: Buffer.from(
  //     '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac',
  //     'hex',
  //   ),
  //   value: 90000,
  // },

  // Not featured here:
  //   redeemScript. A Buffer of the redeemScript for P2SH
  //   witnessScript. A Buffer of the witnessScript for P2WSH
});
psbt.addOutput({
  // Changed the address to match dogecoin format
  address: 'DPZSrvbCvBiAVAX6GpSBw8patJ7K6NaFa4',
  value: 80000,
});
psbt.signInput(0, alice);
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
assert.strictEqual(
  psbt.extractTransaction().toHex(),
  '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
    'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
    'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
    '9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
    'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
    '08a22724efa6f6a07b0ec4c79aa88ac00000000',
);

@james-triangle
Copy link
Author

@junderw I am using my own signer to sign all my utxo inputs using psbt.signAllInputsAsync. But I am getting an error called Error: No inputs were signed. Does this mean there is something wrong with building my transaction?

@junderw
Copy link
Member

junderw commented Oct 11, 2023

That has nothing to do with the network parameters.

It means that the Psbt couldn't find your signer's publickey (or its pubkeyhash) anywhere in the inputs.

Please make sure you have all the information needed in your inputs and also that you are using the correct key.

@james-triangle
Copy link
Author

@junderw


const psbt = new bitcoinjs.Psbt({ network: dogetestnetwork });
psbt.addInputs(
  utxos.map((utxo: any) => ({
    hash: utxo.mined.tx_id,
    index: utxo.mined.index,
    witnessUtxo: { script: Buffer.from(utxo.mined.meta.script, "hex"), value: utxo.value },
  }))
);

psbt.addOutput({ address: to, value: _amount });

await psbt.signAllInputsAsync({
  publicKey: publicKey!,
  sign: async (buffer: Buffer) => {
    return (await signer(buffer)).slice(0, -1);
  },
});

if I am building my transaction with segwit, where should i be adding my publickey in the inputs?

@junderw
Copy link
Member

junderw commented Oct 11, 2023

If it's P2WPKH then the pubkey hash is inside witnessUtxo.script.

You most likely are using the wrong key to sign.

@junderw
Copy link
Member

junderw commented Oct 11, 2023

No. Both of them don't require any redeemScript or witnessScript, so the Psbt code will recognize either one.

If you can post your pre-signed PSBT and also the hex of your public key, I can help you figure out what's wrong.

@james-triangle
Copy link
Author

james-triangle commented Oct 11, 2023

@junderw

bitcoinjs.payments.p2wpkh({
  pubkey: publicKey,
  network: dogecoin,
})

is this the correct way to create a dogecoin address via p2wpkh? I am using your network configuration and getting invalid addresses

@james-triangle
Copy link
Author

@junderw
this is my psbt.toHex():
70736274ff01005502000000019fcf7c700f0a1db47a0765f1c83c7efe7e4f72f3e109e17e418516dac61daf4d0000000000ffffffff0100e1f505000000001976a914165e09e241ac9c3a7eabdeead1ecdf24b4af356188ac0000000000010122ff5378e5020000001976a9147529837332d466f8a60791c6540a49998728c5d188ac0000
publickey:
029a19d240fb1e46e6edd50ca31ff7500a00d9e0e8aa5c12cd9bbbba233e3bdc9d

@junderw
Copy link
Member

junderw commented Oct 11, 2023

Looks like the pubkey matches the input.

signAllInputsAsync squashes any errors into "couldn't sign" so maybe try signing with

signInputAsync(0, {
  publicKey: publicKey!,
  sign: async (buffer: Buffer) => {
    return (await signer(buffer)).slice(0, -1);
  },
})

@junderw
Copy link
Member

junderw commented Oct 11, 2023

Your signer function might be throwing an exception.

@james-triangle
Copy link
Author

I am getting a Error: Input #0 has witnessUtxo but non-segwit script: 76a9147529837332d466f8a60791c6540a49998728c5d188ac . Did I configure my input incorrectly?

@junderw
Copy link
Member

junderw commented Oct 11, 2023

Oh yeah. You need the full parent transaction, turn it into a Buffer and add it as nonWitnessUtxo instead of witnessUtxo.

@junderw
Copy link
Member

junderw commented Oct 11, 2023

Also, you are spending 124 DOGE and only sending 1 DOGE, so the fee will be huge.

Since it's testnet, you could just skip the fee checks on extractTransaction, but in prod you definitely don't want to skip the fee checks... or you can set a higher fee check threshold (PsbtOptions has an optional parameter)

@james-triangle
Copy link
Author

@junderw thanks for the heads up!
Is it possible to create a P2WPKH on the dogenetwork so that we can use witnessUtxo?

bitcoinjs.payments.p2wpkh({
  pubkey: publicKey,
  network: dogecoin,
})

I am using your dogecoin network configuration and it creates an invalid wallet address

@james-triangle
Copy link
Author

In your original example for the nonWitnessUtxo parameter, how did you come up with the raw transaction in hex, specifically the first 5 hashes?

nonWitnessUtxo: Buffer.from(
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
  '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
  'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
  '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
  '631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
  // value in satoshis (Int64LE) = 0x015f90 = 90000
  '905f010000000000' +
  // scriptPubkey length
  '19' +
  // scriptPubkey
  '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' +
  // locktime
  '00000000',
'hex',
),

@junderw
Copy link
Member

junderw commented Oct 12, 2023

Just grab the full parent transaction from whatever API that you got your UTXO information from.

It's just one big hex string.

@james-triangle
Copy link
Author

Since I am getting UTXO info from blockdaemon's API should I pass the whole object as a buffer, ie: Buffer.from(JSON.stringify(response.data[0])) ?

@junderw
Copy link
Member

junderw commented Oct 12, 2023

Using the native endpoint: https://docs.blockdaemon.com/reference/how-to-access-dogecoin-api

You can access getrawtransaction https://docs.blockdaemon.com/reference/supported-dogecoin-methods (verbose should be false since you only need the transaction hex string.

The tx hash you would use to query is utxo.mined.tx_id

@james-triangle
Copy link
Author

The native endpoint direction work!
I think I have all the piece together, is the following a valid signed dogecoin transaction ready to get broadcasted?
02000000019fcf7c700f0a1db47a0765f1c83c7efe7e4f72f3e109e17e418516dac61daf4d000000006a47304402207391b4f8c1c69642399f2e45117b662d964d1f585eb3731205746a66941139cd02207f519a1eb98c62e2faf0766101b6671704b06ea0d8840e0fd9c7e815bf9b45960121029a19d240fb1e46e6edd50ca31ff7500a00d9e0e8aa5c12cd9bbbba233e3bdc9dffffffff0200e1f505000000001976a914165e09e241ac9c3a7eabdeead1ecdf24b4af356188ac407282df020000001976a9147529837332d466f8a60791c6540a49998728c5d188ac00000000

@junderw
Copy link
Member

junderw commented Oct 12, 2023

Try broadcasting it using native endpoint sendrawtransaction with the parameter being the hex transaction.

maxfeerate value default for BTC is 10k sat/byte fee rate, but I think your transaction is well below that + Doge might have that default higher.

@james-triangle
Copy link
Author

I tried both blockdaemon's universal and native endpoints but got an invalid transaction hash: 380f849118ba42f3a8fe6b217542ebc64260924ca8a6cd81337fbcfe6714b040 but no error. Could something be failing silently?

@james-triangle
Copy link
Author

this is also my decoded transaction

{
  "result": {
    "txid": "380f849118ba42f3a8fe6b217542ebc64260924ca8a6cd81337fbcfe6714b040",
    "hash": "380f849118ba42f3a8fe6b217542ebc64260924ca8a6cd81337fbcfe6714b040",
    "size": 225,
    "vsize": 225,
    "version": 2,
    "locktime": 0,
    "vin": [
      {
        "txid": "4daf1dc6da1685417ee109e1f3724f7efe7e3cc8f165077ab41d0a0f707ccf9f",
        "vout": 0,
        "scriptSig": {
          "asm": "304402207391b4f8c1c69642399f2e45117b662d964d1f585eb3731205746a66941139cd02207f519a1eb98c62e2faf0766101b6671704b06ea0d8840e0fd9c7e815bf9b4596[ALL] 029a19d240fb1e46e6edd50ca31ff7500a00d9e0e8aa5c12cd9bbbba233e3bdc9d",
          "hex": "47304402207391b4f8c1c69642399f2e45117b662d964d1f585eb3731205746a66941139cd02207f519a1eb98c62e2faf0766101b6671704b06ea0d8840e0fd9c7e815bf9b45960121029a19d240fb1e46e6edd50ca31ff7500a00d9e0e8aa5c12cd9bbbba233e3bdc9d"
        },
        "sequence": 4294967295
      }
    ],
    "vout": [
      {
        "value": 1,
        "n": 0,
        "scriptPubKey": {
          "asm": "OP_DUP OP_HASH160 165e09e241ac9c3a7eabdeead1ecdf24b4af3561 OP_EQUALVERIFY OP_CHECKSIG",
          "hex": "76a914165e09e241ac9c3a7eabdeead1ecdf24b4af356188ac",
          "reqSigs": 1,
          "type": "pubkeyhash",
          "addresses": [
            "nWERnQeazmrSDaQsxBF1nmhMBar9XLr4dH"
          ]
        }
      },
      {
        "value": 123.39802688,
        "n": 1,
        "scriptPubKey": {
          "asm": "OP_DUP OP_HASH160 7529837332d466f8a60791c6540a49998728c5d1 OP_EQUALVERIFY OP_CHECKSIG",
          "hex": "76a9147529837332d466f8a60791c6540a49998728c5d188ac",
          "reqSigs": 1,
          "type": "pubkeyhash",
          "addresses": [
            "nesf483aQXohdqKNya7kNzVGtXbapQL53X"
          ]
        }
      }
    ]
  },
  "error": null,
  "id": 1
}

@junderw
Copy link
Member

junderw commented Oct 13, 2023

Your transaction is valid, but the fees are too low.

You paid 191 sats in fees, but the minimum fee for DOGE (and probably its testnet too) is much higher than that.

Your transaction is 225 bytes and you're paying less than that. 0.85 sats/byte

I think 100 sat/byte is the minimum fee to broadcast for doge. So maybe 22500 would work.

Maybe a little more since signatures can vary by a byte or two in length.

Try 23000 for fees.

@james-triangle
Copy link
Author

I'm running into a 258: txn-mempool-conflict error after increasing my fees. Is this a problem with the network?

@junderw
Copy link
Member

junderw commented Oct 13, 2023

That error occurs when the mempool of the node you are trying to broadcast to has another transaction that already spends the same UTXO in one of its inputs.

I'm not sure why that would happen. testnet is weird in Bitcoin, but I'm sure it would be even more weird for dogecoin testnet.

Maybe ask blockdaemon support. I can't really help you from here.

@james-triangle
Copy link
Author

@junderw thanks for everything so far. I was able to properly send a transaction on the dogecoin testnet!
Curious to know, what would be the best way to calculate the appropriate gas fee? I know that blockdaemon has this estimation endpoint but this seems different to your sat/byte calculation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants