startFusion.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /* Import modules. */
  2. import { encryptForPubkey } from '@nexajs/crypto'
  3. import { randomOutputsForTier } from '@nexajs/privacy'
  4. import { binToHex } from '@nexajs/utils'
  5. import BCHJS from '@psf/bch-js'
  6. /* Initialize BCHJS. */
  7. const bchjs = new BCHJS()
  8. export default async function () {
  9. console.log('Starting fusion...')
  10. /* Initialize locals. */
  11. let bestTiers
  12. let blindComponents
  13. let components
  14. let cipherTokens
  15. let clubWallet
  16. let fusionInputs
  17. let inputAmount
  18. let publicKey
  19. let rawTx
  20. let inputs
  21. let outputs
  22. let response
  23. let tierid
  24. let tierScale
  25. const feeOffset = 1034//10034
  26. const maxOutputCount = 17
  27. /* Clone fusion inputs. */
  28. fusionInputs = []
  29. Object.keys(this.fusionInputs).forEach(_outpoint => {
  30. fusionInputs.push(this.fusionInputs[_outpoint])
  31. })
  32. const tierScales = [
  33. 10000, 12000, 15000, 18000, 22000, 27000, 33000, 39000, 47000, 56000, 68000, 82000,
  34. 100000, 120000, 150000, 180000, 220000, 270000, 330000, 390000, 470000, 560000, 680000, 820000,
  35. 1000000, 1200000, 1500000, 1800000, 2200000, 2700000, 3300000, 3900000, 4700000, 5600000, 6800000, 8200000,
  36. 10000000, 12000000, 15000000, 18000000, 22000000, 27000000, 33000000, 39000000, 47000000, 56000000, 68000000, 82000000,
  37. 100000000, 120000000, 150000000, 180000000, 220000000, 270000000, 330000000, 390000000, 470000000, 560000000, 680000000, 820000000,
  38. 1000000000, 1200000000, 1500000000, 1800000000, 2200000000, 2700000000, 3300000000, 3900000000, 4700000000, 5600000000, 6800000000, 8200000000,
  39. ]
  40. // for (let i = 0; i < fusionInputs.length; i++) {
  41. // inputAmount =
  42. /* Set input amount. */
  43. inputAmount = fusionInputs.reduce(
  44. (acc, input) => acc + input.value, 0
  45. )
  46. console.log('INPUT AMOUNT', fusionInputs.length, inputAmount)
  47. /* Handle ALL tier scales. */
  48. tierScales.forEach(_tierScale => {
  49. try {
  50. /* Request (random) outputs. */
  51. response = randomOutputsForTier(
  52. inputAmount,
  53. _tierScale,
  54. feeOffset,
  55. maxOutputCount,
  56. )
  57. /* Validate tier outputs. */
  58. if (response && response.length > 1) {
  59. console.log('TIER', _tierScale, response)
  60. /* Test for the best tiers. */
  61. if (typeof bestTiers === 'undefined' || response.length > bestTiers?.outputs.length) {
  62. const numOutputs = response.length
  63. console.log('NUM OUTPUTS', numOutputs)
  64. // FIXME THE RESULTING BYTE COUNTS ARE WRONG!!
  65. // THIS FUNCTION MAY BE OUTDATED!!
  66. const fee = bchjs.BitcoinCash
  67. .getByteCount({ P2PKH: fusionInputs.length }, { P2PKH: numOutputs })
  68. // const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 })
  69. console.log('FEE', fee)
  70. const safeFee = (fee * 2) + (fee * Math.random())
  71. console.log('SAFE FEE', safeFee)
  72. const feeOffset = Math.ceil(safeFee / numOutputs)
  73. console.log('FEE OFFSET', feeOffset)
  74. const outputs = response.map(_outputValue => {
  75. return {
  76. address: this.getFusionAddress(),
  77. value: (_outputValue - feeOffset),
  78. // value: (_outputValue - fee),
  79. }
  80. })
  81. bestTiers = {
  82. tierid: _tierScale,
  83. outputs,
  84. }
  85. }
  86. }
  87. } catch (err) {
  88. // console.error(err)
  89. }
  90. })
  91. console.log('BEST TIERS', bestTiers)
  92. // return
  93. /* Initialize components. */
  94. // NOTE: Automatically clone + add ALL fusion inputs.
  95. components = [ ...fusionInputs ]
  96. /* Sanitize (cloned input) components. */
  97. Object.keys(components).forEach(_outpoint => {
  98. const component = components[_outpoint]
  99. /* Delete WIF. */
  100. delete component.wif
  101. })
  102. /* Set tier ID. */
  103. tierid = bestTiers.tierid
  104. /* Add best tiers to components. */
  105. // NOTE: Best tiers index is derived from inputs index.
  106. for (let i = 0; i < bestTiers.outputs.length; i++) {
  107. /* Set tier. */
  108. const tier = bestTiers.outputs[i]
  109. console.log('TIER', tier)
  110. /* Add (output) components. */
  111. components.push(tier)
  112. }
  113. /* Validate components. */
  114. if (components.length === 0) {
  115. throw new Error('Oops! You MUST provide components to the Club.')
  116. }
  117. /* Prepare components for encryption. */
  118. components = JSON.stringify(components)
  119. // console.log('FUSION (components)', components)
  120. // TODO Handle any filtering required BEFORE submitting for fusion.
  121. clubWallet = await $fetch('/api/wallet')
  122. .catch(err => console.error(err))
  123. // console.log('CLUB WALLET', clubWallet)
  124. // FIXME Retrieve public key from a "public" endpoint.
  125. publicKey = clubWallet.publicKey
  126. // console.log('CLUB PUBLIC KEY', publicKey)
  127. /* Generate blind components. */
  128. blindComponents = encryptForPubkey(publicKey, components)
  129. // console.log('BLINDED COMPONENTS', blindComponents)
  130. const body = {
  131. authid: binToHex(this.wallet.publicKey),
  132. actionid: 'submit-components',
  133. tierid,
  134. components: blindComponents,
  135. }
  136. // console.log('BODY', body)
  137. response = await $fetch('/v1', {
  138. method: 'POST',
  139. body,
  140. })
  141. .catch(err => console.error(err))
  142. console.log('RESPONSE', response)
  143. }