diff --git a/web/cashtab/src/utils/__mocks__/mockTxBuilderObj.js b/web/cashtab/src/utils/__mocks__/mockTxBuilderObj.js --- a/web/cashtab/src/utils/__mocks__/mockTxBuilderObj.js +++ b/web/cashtab/src/utils/__mocks__/mockTxBuilderObj.js @@ -474,3 +474,103 @@ bip68: {}, p2shInput: false, }; + +export const mockCreateTokenOutputsTxBuilderObj = { + transaction: { + prevTxMap: {}, + network: { + hashGenesisBlock: + '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + port: 8333, + portRpc: 8332, + protocol: { + magic: 3652501241, + }, + seedsDns: [ + 'seed.bitcoinabc.org', + 'seed-abc.bitcoinforks.org', + 'btccash-seeder.bitcoinunlimited.info', + 'seed.bitprim.org', + 'seed.deadalnix.me', + 'seeder.criptolayer.net', + ], + versions: { + bip32: { + private: 76066276, + public: 76067358, + }, + bip44: 145, + private: 128, + public: 0, + scripthash: 5, + messagePrefix: '\u0018BitcoinCash Signed Message:\n', + }, + name: 'BitcoinCash', + per1: 100000000, + unit: 'BCH', + testnet: false, + messagePrefix: '\u0018BitcoinCash Signed Message:\n', + bip32: { + public: 76067358, + private: 76066276, + }, + pubKeyHash: 0, + scriptHash: 5, + wif: 128, + dustThreshold: null, + }, + maximumFeeRate: 2500, + inputs: [], + bitcoinCash: true, + tx: { + version: 2, + locktime: 0, + ins: [], + outs: [ + { + script: { + type: 'Buffer', + data: [ + 106, 4, 83, 76, 80, 0, 1, 1, 7, 71, 69, 78, 69, 83, + 73, 83, 4, 67, 85, 84, 84, 23, 67, 97, 115, 104, + 116, 97, 98, 32, 85, 110, 105, 116, 32, 84, 101, + 115, 116, 32, 84, 111, 107, 101, 110, 23, 104, 116, + 116, 112, 115, 58, 47, 47, 99, 97, 115, 104, 116, + 97, 98, 97, 112, 112, 46, 99, 111, 109, 47, 76, 0, + 1, 2, 76, 0, 8, 0, 0, 0, 0, 0, 0, 39, 16, + ], + }, + value: 0, + }, + { + script: { + type: 'Buffer', + data: [ + 118, 169, 20, 184, 35, 97, 197, 133, 31, 78, 196, + 139, 153, 81, 117, 162, 225, 195, 100, 99, 56, 224, + 118, 136, 172, + ], + }, + value: 546, + }, + ], + }, + }, + DEFAULT_SEQUENCE: 4294967295, + hashTypes: { + SIGHASH_ALL: 1, + SIGHASH_NONE: 2, + SIGHASH_SINGLE: 3, + SIGHASH_ANYONECANPAY: 128, + SIGHASH_BITCOINCASH_BIP143: 64, + ADVANCED_TRANSACTION_MARKER: 0, + ADVANCED_TRANSACTION_FLAG: 1, + }, + signatureAlgorithms: { + ECDSA: 0, + SCHNORR: 1, + }, + bip66: {}, + bip68: {}, + p2shInput: false, +}; diff --git a/web/cashtab/src/utils/__tests__/cashMethods.test.js b/web/cashtab/src/utils/__tests__/cashMethods.test.js --- a/web/cashtab/src/utils/__tests__/cashMethods.test.js +++ b/web/cashtab/src/utils/__tests__/cashMethods.test.js @@ -25,6 +25,7 @@ getWalletBalanceFromUtxos, signUtxosByAddress, getUtxoWif, + generateTokenTxOutput, } from 'utils/cashMethods'; import { currency } from 'components/Common/Ticker'; import { @@ -88,6 +89,7 @@ import { mockOneToOneSendXecTxBuilderObj, mockOneToManySendXecTxBuilderObj, + mockCreateTokenOutputsTxBuilderObj, } from '../__mocks__/mockTxBuilderObj'; import { mockSingleInputUtxo, @@ -95,6 +97,7 @@ mockSingleOutput, mockMultipleOutputs, } from '../__mocks__/mockTxBuilderData'; +import createTokenMock from '../../hooks/__mocks__/createToken'; it(`signUtxosByAddress() successfully returns a txBuilder object for a one to one XEC tx`, () => { const BCH = new BCHJS(); @@ -505,6 +508,27 @@ expect(thrownError.message).toStrictEqual('Invalid OP RETURN script input'); }); +it(`generateTokenTxOutput() returns a valid object for a valid create token tx`, async () => { + const BCH = new BCHJS(); + let txBuilder = new BCH.TransactionBuilder(); + const { configObj, wallet } = createTokenMock; + const tokenCreatorXecAddress = wallet.Path1899.cashAddress; + + const tokenOutputObj = generateTokenTxOutput( + BCH, + txBuilder, + 'GENESIS', + tokenCreatorXecAddress, + null, // optional, for SEND or BURN amount + new BigNumber(500), // remainder XEC value + configObj, + ); + + expect(tokenOutputObj.toString()).toStrictEqual( + mockCreateTokenOutputsTxBuilderObj.toString(), + ); +}); + it(`generateTxInput() returns an input object for a valid one to one XEC tx`, async () => { const BCH = new BCHJS(); const isOneToMany = false; diff --git a/web/cashtab/src/utils/cashMethods.js b/web/cashtab/src/utils/cashMethods.js --- a/web/cashtab/src/utils/cashMethods.js +++ b/web/cashtab/src/utils/cashMethods.js @@ -40,6 +40,56 @@ return txBuilder; }; +export const generateTokenTxOutput = ( + BCH, + txBuilder, + tokenAction, + xecReceiverAddress, + totalTokenInputUtxoValue, // optional - send or burn tx only + remainderXecValue = new BigNumber(0), // optional - only if > dust + tokenConfigObj = {}, // optional - genesis only +) => { + try { + if (!BCH || !tokenAction || !xecReceiverAddress || !txBuilder) { + throw new Error('Invalid token tx output parameter'); + } + + let script; + if (tokenAction === 'GENESIS') { + script = BCH.SLP.TokenType1.generateGenesisOpReturn(tokenConfigObj); + } else if (tokenAction === 'SEND') { + // part 2 to generate opreturn script for send tx + } else if (tokenAction === 'BURN') { + // part 2 to generate opreturn script for burn tx + } else { + throw new Error('Invalid token transaction type'); + } + + // OP_RETURN needs to be the first output in the transaction. + txBuilder.addOutput(script, 0); + + // add XEC dust output to send genesis tokens to own address + txBuilder.addOutput(xecReceiverAddress, parseInt(currency.etokenSats)); + + // part 2 to add output for: + // - token send/burns + // - token change + + // Send xec change to own address + if (remainderXecValue.gte(new BigNumber(currency.dustSats))) { + txBuilder.addOutput( + xecReceiverAddress, + parseInt(remainderXecValue), + ); + } + } catch (err) { + console.log(`generateTokenTxOutput() error: ` + err); + throw err; + } + + return txBuilder; +}; + export const generateTxInput = ( BCH, isOneToMany,