Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/common/plugin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ export interface CoinInfo {
*/
sighash?: number

/**
* Optional transaction locktime value for currencies that use nLockTime as
* an opt-in replay-protection mechanism.
*/
replayProtectionLocktime?: number

/**
* A function to be passed to AltcoinJS `signInput` method. This is used to
* get the input hash for the signature algorithm before signing the input.
Expand Down
3 changes: 3 additions & 0 deletions src/common/utxobased/info/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { info as badcoin } from './badcoin'
import { info as bitcoin } from './bitcoin'
import { info as bitcoincash } from './bitcoincash'
import { info as bitcoincashtestnet } from './bitcoincashtestnet'
import { info as bitcoinecash } from './bitcoinecash'
import { info as bitcoingold } from './bitcoingold'
import { info as bitcoingoldtestnet } from './bitcoingoldtestnet'
import { info as bitcoinsv } from './bitcoinsv'
Expand All @@ -27,6 +28,7 @@ export { info as badcoin } from './badcoin'
export { info as bitcoin } from './bitcoin'
export { info as bitcoincash } from './bitcoincash'
export { info as bitcoincashtestnet } from './bitcoincashtestnet'
export { info as bitcoinecash } from './bitcoinecash'
export { info as bitcoingold } from './bitcoingold'
export { info as bitcoingoldtestnet } from './bitcoingoldtestnet'
export { info as bitcoinsv } from './bitcoinsv'
Expand All @@ -53,6 +55,7 @@ export const all = [
bitcoin,
bitcoincash,
bitcoincashtestnet,
bitcoinecash,
bitcoingold,
bitcoingoldtestnet,
bitcoinsv,
Expand Down
2 changes: 1 addition & 1 deletion src/common/utxobased/info/bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const engineInfo: EngineInfo = {
}
],
formats: ['bip49', 'bip84', 'bip44', 'bip32'],
forks: ['bitcoincash', 'bitcoingold'],
forks: ['bitcoincash', 'bitcoingold', 'bitcoinecash'],
gapLimit: 25,
feeUpdateInterval: 60000,
mempoolSpaceFeeInfoServer: 'https://mempool.space/api/v1/fees/recommended',
Expand Down
89 changes: 89 additions & 0 deletions src/common/utxobased/info/bitcoinecash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { EdgeCurrencyInfo } from 'edge-core-js/types'

import { CoinInfo, EngineInfo, PluginInfo } from '../../plugin/types'
import { maximumFeeRateCalculator } from '../../plugin/util/maximumFeeRateCalculator'
import {
legacyMemoInfo,
utxoCustomFeeTemplate,
utxoMemoOptions
} from './commonInfo'

const currencyInfo: EdgeCurrencyInfo = {
assetDisplayName: 'eCash',
canReplaceByFee: true,
chainDisplayName: 'eCash',
currencyCode: 'ECX',
customFeeTemplate: utxoCustomFeeTemplate,
memoOptions: utxoMemoOptions,
pluginId: 'bitcoinecash',
walletType: 'wallet:bitcoinecash',

// Explorers:
blockExplorer: 'https://explorer.ecash.com/block/%s',
addressExplorer: 'https://explorer.ecash.com/address/%s',
transactionExplorer: 'https://explorer.ecash.com/tx/%s',

denominations: [
{ name: 'ECX', multiplier: '100000000', symbol: 'e' },
{ name: 'sats', multiplier: '1', symbol: 's' }
],

// Deprecated:
...legacyMemoInfo,
defaultSettings: {
customFeeSettings: ['satPerByte'],
blockbookServers: [],
enableCustomServers: false
},
Comment thread
cursor[bot] marked this conversation as resolved.
displayName: 'eCash',
metaTokens: []
}

const engineInfo: EngineInfo = {
formats: ['bip49', 'bip84', 'bip44', 'bip32'],
gapLimit: 25,
feeUpdateInterval: 60000,
defaultFeeInfo: {
lowFeeFudgeFactor: undefined,
standardFeeLowFudgeFactor: undefined,
standardFeeHighFudgeFactor: undefined,
highFeeFudgeFactor: undefined,

highFee: '150',
lowFee: '20',
standardFeeLow: '50',
standardFeeHigh: '100',
standardFeeLowAmount: '173200',
standardFeeHighAmount: '8670000',
maximumFeeRate: maximumFeeRateCalculator(currencyInfo, 1)
}
}

export const coinInfo: CoinInfo = {
name: 'bitcoinecash',
segwit: true,
coinType: 0,

// Official pre-launch/drynet4 replay-protection params must be reverified before go-live.
replayProtectionLocktime: 499999999,

prefixes: {
messagePrefix: ['\x18Bitcoin Signed Message:\n'],
wif: [0x80],
legacyXPriv: [0x0488ade4],
legacyXPub: [0x0488b21e],
wrappedSegwitXPriv: [0x049d7878],
wrappedSegwitXPub: [0x049d7cb2],
segwitXPriv: [0x04b2430c],
segwitXPub: [0x04b24746],
pubkeyHash: [0x00],
scriptHash: [0x05],
bech32: ['bc']
}
}

export const info: PluginInfo = {
currencyInfo,
engineInfo,
coinInfo
}
8 changes: 7 additions & 1 deletion src/common/utxobased/keymanager/keymanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,14 +1005,17 @@ export function signMessageBase64(

export function makeTx(args: MakeTxArgs): MakeTxReturn {
const { log, outputSort, memos, memoIndex } = args
const coin = getCoinFromString(args.coin)
const replayProtectionLocktime = coin.replayProtectionLocktime
let sequence = 0xffffffff
if (args.enableRbf) {
sequence -= 2
} else if (replayProtectionLocktime != null) {
sequence = 0xfffffffe
}

// get coin specific replay protection sighhash bits
let sighashType = Transaction.SIGHASH_ALL
const coin = getCoinFromString(args.coin)
if (coin.sighash != null) {
sighashType = coin.sighash
}
Expand Down Expand Up @@ -1169,6 +1172,9 @@ export function makeTx(args: MakeTxArgs): MakeTxReturn {

const psbt = new Psbt()
try {
if (replayProtectionLocktime != null) {
psbt.setLocktime(replayProtectionLocktime)
}
psbt.addInputs(sortedInputs)
psbt.addOutputs(sortedOutputs)
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ export const bitcoin: FixtureType = {
]
},
getSplittableTypes: {
bip32: ['wallet:bitcoincash', 'wallet:bitcoingold'],
bip44: ['wallet:bitcoincash', 'wallet:bitcoingold'],
bip49: ['wallet:bitcoingold'],
bip84: ['wallet:bitcoingold']
bip32: ['wallet:bitcoincash', 'wallet:bitcoingold', 'wallet:bitcoinecash'],
bip44: ['wallet:bitcoincash', 'wallet:bitcoingold', 'wallet:bitcoinecash'],
bip49: ['wallet:bitcoingold', 'wallet:bitcoinecash'],
bip84: ['wallet:bitcoingold', 'wallet:bitcoinecash']
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { FixtureType, key, mnemonics } from '../common'

export const bitcoinecash: FixtureType = {
pluginId: 'bitcoinecash',
WALLET_TYPE: 'wallet:bitcoinecash',
WALLET_FORMAT: 'bip32',
'Test Currency code': 'ECX',
key,
xpub:
'xpub69FqMgncSEcrs989ejBWTBBcDNFDqkwEd7y53pVeXm8368TNfb9jCd2ne3ccpx9vvgBdpv79Edc69i2Q69kXtrdmLcQM8seffnCXzwzvWa6',
'invalid key name': {
id: 'unknown',
type: 'wallet:bitcoinecash',
keys: { bitcoinecashKeyz: '12345678abcd' }
},
'invalid wallet type': {
id: 'unknown',
type: 'shitcoin',
keys: { bitcoinecashKeyz: '12345678abcd' }
},
importKey: {
validKeys: [...mnemonics],
invalidKeys: [
...mnemonics.map(mnemonic => mnemonic.split(' ').slice(1).join(' ')),
'bunch of garbly gook !@#$%^&*()'
],
unsupportedKeys: []
},
parseUri: {
'address only': [
'1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX',
{
publicAddress: '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX',
metadata: {}
}
],
'bech32 address only': [
'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4',
{
publicAddress: 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4',
metadata: {}
}
],
'uri address with amount': [
'bitcoinecash:1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX?amount=1.23',
{
publicAddress: '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX',
metadata: {},
nativeAmount: '123000000',
currencyCode: 'ECX'
}
],
'bitcoin uri protocol rejected': [
'bitcoin:1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX'
]
},
encodeUri: {
'address only': [
{ publicAddress: '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX' },
'1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX'
],
'address & amount': [
{
publicAddress: '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX',
nativeAmount: '123000000'
},
'bitcoinecash:1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX?amount=1.23'
],
'invalid currencyCode': [
{
publicAddress: '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX',
nativeAmount: '123000000',
currencyCode: 'INVALID'
}
]
}
}
2 changes: 2 additions & 0 deletions test/common/plugin/CurrencyPlugin.fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FixtureType } from './common'
import { bitcoin } from './currencies/bitcoin'
import { bitcoincash } from './currencies/bitcoincash'
import { bitcoinecash } from './currencies/bitcoinecash'
import { bitcoinsv } from './currencies/bitcoinsv'
import { digibyte } from './currencies/digibyte'
import { ecash } from './currencies/ecash'
Expand All @@ -13,6 +14,7 @@ import { zcoin } from './currencies/zcoin'
export const fixtures: FixtureType[] = [
bitcoin,
bitcoincash,
bitcoinecash,
bitcoinsv,
digibyte,
ecash,
Expand Down
31 changes: 31 additions & 0 deletions test/common/plugin/Metadata.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EngineEvent
} from '../../../src/common/plugin/EngineEmitter'
import { makeMetadata, Metadata } from '../../../src/common/plugin/Metadata'
import { all } from '../../../src/common/utxobased/info/all'
import { makeFakeLog } from '../../utils'

chai.should()
Expand Down Expand Up @@ -49,3 +50,33 @@ describe('makeMetadata', () => {
})
})
})

describe('currency metadata', () => {
it('keeps ECX distinct from XEC', () => {
const ecash = all.find(info => info.currencyInfo.pluginId === 'ecash')
const bitcoinecash = all.find(
info => info.currencyInfo.pluginId === 'bitcoinecash'
)

if (ecash == null || bitcoinecash == null) {
throw new Error('Missing eCash plugin metadata')
}

ecash.currencyInfo.walletType.should.equal('wallet:ecash')
ecash.currencyInfo.currencyCode.should.equal('XEC')
bitcoinecash.currencyInfo.walletType.should.equal('wallet:bitcoinecash')
bitcoinecash.currencyInfo.currencyCode.should.equal('ECX')
bitcoinecash.coinInfo.coinType.should.equal(0)
bitcoinecash.coinInfo.prefixes.pubkeyHash.should.deep.equal([0x00])
bitcoinecash.coinInfo.prefixes.scriptHash.should.deep.equal([0x05])
bitcoinecash.coinInfo.prefixes.bech32?.should.deep.equal(['bc'])
})

it('has unique plugin IDs and wallet types', () => {
const pluginIds = all.map(info => info.currencyInfo.pluginId)
const walletTypes = all.map(info => info.currencyInfo.walletType)

new Set(pluginIds).size.should.equal(pluginIds.length)
new Set(walletTypes).size.should.equal(walletTypes.length)
})
})
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Transaction } from 'altcoin-js'
import { expect } from 'chai'
import { describe, it } from 'mocha'

import { UtxoData } from '../../../../../src/common/utxobased/db/types'
import { info as bitcoin } from '../../../../../src/common/utxobased/info/bitcoin'
import { info as bitcoinecash } from '../../../../../src/common/utxobased/info/bitcoinecash'
import {
addressToScriptPubkey,
AddressTypeEnum,
Expand Down Expand Up @@ -207,6 +209,54 @@ describe('bitcoin transaction creation and signing test', function () {
)
})

it('Create ECX transaction with replay-protection locktime', async () => {
const { psbtBase64 } = makeTx({
forceUseUtxo: [],
coin: 'bitcoinecash',
currencyCode: 'ECX',
enableRbf: false,
freshChangeAddress: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
feeRate: 0,
subtractFee: false,
utxos: [
{
id: '0',
scriptType: ScriptTypeEnum.p2pkh,
txid:
'7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
scriptPubkey,
value: '80000',
blockHeight: 0,
spent: false,
script:
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
'631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
'905f010000000000' +
'19' +
scriptPubkey +
'00000000',
vout: 0
}
],
targets: [],
memos: [],
outputSort: 'bip69'
})
const signedTx = await signTx({
coin: 'bitcoinecash',
feeInfo: bitcoinecash.engineInfo.defaultFeeInfo,
privateKeyEncodings: [privateKeyEncoding],
psbtBase64
})
const tx = Transaction.fromHex(signedTx.hex)

expect(tx.locktime).to.equal(499999999)
expect(tx.ins[0].sequence).to.equal(0xfffffffe)
})

it('Create transaction three outputs using bip69 outputSort', async () => {
/*
This here is the rawtransaction as assembled below:
Expand Down