Changeset View
Changeset View
Standalone View
Standalone View
cashtab/src/utils/transactions.js
import { currency } from 'components/Common/Ticker'; | import { currency } from 'components/Common/Ticker'; | ||||
import { | import { | ||||
fromXecToSatoshis, | fromXecToSatoshis, | ||||
isValidStoredWallet, | isValidStoredWallet, | ||||
parseXecSendValue, | parseXecSendValue, | ||||
generateOpReturnScript, | generateOpReturnScript, | ||||
generateTxInput, | generateTxInput, | ||||
generateTxOutput, | generateTxOutput, | ||||
generateTokenTxInput, | generateTokenTxInput, | ||||
generateTokenTxOutput, | generateTokenTxOutput, | ||||
signAndBuildTx, | signAndBuildTx, | ||||
getChangeAddressFromInputUtxos, | getChangeAddressFromInputUtxos, | ||||
toHash160, | toHash160, | ||||
getMessageByteSize, | getMessageByteSize, | ||||
generateAliasOpReturnScript, | |||||
fromSatoshisToXec, | |||||
} from 'utils/cashMethods'; | } from 'utils/cashMethods'; | ||||
import ecies from 'ecies-lite'; | import ecies from 'ecies-lite'; | ||||
import * as utxolib from '@bitgo/utxo-lib'; | import * as utxolib from '@bitgo/utxo-lib'; | ||||
const SEND_XEC_ERRORS = { | const SEND_XEC_ERRORS = { | ||||
INSUFFICIENT_FUNDS: 0, | INSUFFICIENT_FUNDS: 0, | ||||
NETWORK_ERROR: 1, | NETWORK_ERROR: 1, | ||||
INSUFFICIENT_PRIORITY: 66, // ~insufficient fee | INSUFFICIENT_PRIORITY: 66, // ~insufficient fee | ||||
▲ Show 20 Lines • Show All 300 Lines • ▼ Show 20 Lines | ) => { | ||||
throw new Error( | throw new Error( | ||||
'Cannot send an encrypted message to a wallet with no outgoing transactions in the last 20 txs', | 'Cannot send an encrypted message to a wallet with no outgoing transactions in the last 20 txs', | ||||
); | ); | ||||
}; | }; | ||||
export const registerNewAlias = async ( | export const registerNewAlias = async ( | ||||
chronik, | chronik, | ||||
wallet, | wallet, | ||||
utxos, | |||||
feeInSatsPerByte, | feeInSatsPerByte, | ||||
aliasName, | aliasName, | ||||
registrationFee, | aliasAddress, | ||||
registrationFeeSats, | |||||
) => { | ) => { | ||||
try { | try { | ||||
// Instantiate new txBuilder | |||||
let txBuilder = utxolib.bitgo.createTransactionBuilderForNetwork( | let txBuilder = utxolib.bitgo.createTransactionBuilderForNetwork( | ||||
utxolib.networks.ecash, | utxolib.networks.ecash, | ||||
); | ); | ||||
const satoshisToSend = fromXecToSatoshis(registrationFee); | // Create this transaction with nonSlpUtxos | ||||
const utxos = wallet.state.nonSlpUtxos; | |||||
// Throw validation error if fromXecToSatoshis returns false | |||||
if (!satoshisToSend) { | |||||
throw new Error(`Invalid alias registration fee`); | |||||
} | |||||
// Start of building the OP_RETURN output. | // Build opReturnData for alias tx per spec | ||||
// only build the OP_RETURN output if the user supplied it | const opReturnData = generateAliasOpReturnScript( | ||||
if ( | |||||
aliasName && | |||||
typeof aliasName !== 'undefined' && | |||||
aliasName.trim() !== '' | |||||
) { | |||||
const opReturnData = generateOpReturnScript( | |||||
aliasName, | aliasName, | ||||
false, // encryption use | aliasAddress, | ||||
false, // airdrop use | |||||
null, // airdrop use | |||||
null, // encrypted use | |||||
true, // alias registration flag | |||||
); | ); | ||||
// Add opReturn as output with 0 satoshis of XEC spent | |||||
txBuilder.addOutput(opReturnData, 0); | txBuilder.addOutput(opReturnData, 0); | ||||
} | |||||
// generate the tx inputs and add to txBuilder instance | // generate the tx inputs and add to txBuilder instance | ||||
// returns the updated txBuilder, txFee, totalInputUtxoValue and inputUtxos | // returns the updated txBuilder, txFee, totalInputUtxoValue and inputUtxos | ||||
// Note that txBuilder is intentionally modified by this call | |||||
let txInputObj = generateTxInput( | let txInputObj = generateTxInput( | ||||
false, // not one to many | false, // not a one to many tx | ||||
utxos, | utxos, | ||||
txBuilder, | txBuilder, | ||||
null, // one to many array | null, // no one-to-many array | ||||
satoshisToSend, | registrationFeeSats, | ||||
feeInSatsPerByte, | feeInSatsPerByte, | ||||
); | ); | ||||
// Get the change address | |||||
const changeAddress = getChangeAddressFromInputUtxos( | const changeAddress = getChangeAddressFromInputUtxos( | ||||
txInputObj.inputUtxos, | txInputObj.inputUtxos, | ||||
wallet, | wallet, | ||||
); | ); | ||||
txBuilder = txInputObj.txBuilder; // update the local txBuilder with the generated tx inputs | |||||
// generate the tx outputs and add to txBuilder instance | // generate the tx outputs and add to txBuilder instance | ||||
// returns the updated txBuilder | // returns the updated txBuilder | ||||
const txOutputObj = generateTxOutput( | txBuilder = generateTxOutput( | ||||
false, // not one to many | false, // not a one-to-many tx | ||||
registrationFee, | fromSatoshisToXec(registrationFeeSats), // TODO fix this oversight param req in generateTxOutput | ||||
satoshisToSend, | registrationFeeSats, | ||||
txInputObj.totalInputUtxoValue, | txInputObj.totalInputUtxoValue, | ||||
currency.aliasSettings.aliasPaymentAddress, | currency.aliasSettings.aliasPaymentAddress, | ||||
null, // one to many address array | null, // no one-to-many address array | ||||
changeAddress, | changeAddress, | ||||
txInputObj.txFee, | txInputObj.txFee, | ||||
txBuilder, | txBuilder, | ||||
); | ); | ||||
txBuilder = txOutputObj; // update the local txBuilder with the generated tx outputs | |||||
// sign the collated inputUtxos and build the raw tx hex | // sign the collated inputUtxos and build the raw tx hex | ||||
// returns the raw tx hex string | // returns the raw tx hex string | ||||
const rawTxHex = signAndBuildTx( | const rawTxHex = signAndBuildTx( | ||||
txInputObj.inputUtxos, | txInputObj.inputUtxos, | ||||
txBuilder, | txBuilder, | ||||
wallet, | wallet, | ||||
); | ); | ||||
// Broadcast transaction to the network via the chronik client | // Broadcast transaction to the network via the chronik client | ||||
// sample chronik.broadcastTx() response: | // sample chronik.broadcastTx() response: | ||||
// {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} | // {"txid":"0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19"} | ||||
let broadcastResponse; | let broadcastResponse; | ||||
try { | try { | ||||
broadcastResponse = await chronik.broadcastTx(rawTxHex); | broadcastResponse = await chronik.broadcastTx(rawTxHex); | ||||
if (!broadcastResponse) { | if (!broadcastResponse) { | ||||
throw new Error('Empty chronik broadcast response'); | throw new Error('Empty chronik broadcast response'); | ||||
} | } | ||||
} catch (err) { | } catch (err) { | ||||
console.log('Error broadcasting tx to chronik client'); | console.log('Error broadcasting tx to chronik client'); | ||||
throw err; | throw err; | ||||
} | } | ||||
const explorerLink = `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; | |||||
// return the explorer link for the broadcasted tx | // return the explorer link for the broadcasted tx | ||||
return `${currency.blockExplorerUrl}/tx/${broadcastResponse.txid}`; | return { explorerLink, txid: broadcastResponse.txid, rawTxHex }; | ||||
} catch (err) { | } catch (err) { | ||||
if (err.error === 'insufficient priority (code 66)') { | if (err.error === 'insufficient priority (code 66)') { | ||||
err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; | err.code = SEND_XEC_ERRORS.INSUFFICIENT_PRIORITY; | ||||
} else if (err.error === 'txn-mempool-conflict (code 18)') { | } else if (err.error === 'txn-mempool-conflict (code 18)') { | ||||
err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; | err.code = SEND_XEC_ERRORS.DOUBLE_SPENDING; | ||||
} else if (err.error === 'Network Error') { | } else if (err.error === 'Network Error') { | ||||
err.code = SEND_XEC_ERRORS.NETWORK_ERROR; | err.code = SEND_XEC_ERRORS.NETWORK_ERROR; | ||||
} else if ( | } else if ( | ||||
▲ Show 20 Lines • Show All 187 Lines • Show Last 20 Lines |