├── .yarnrc
├── src
├── common
│ ├── utxobased
│ │ ├── keymanager
│ │ │ ├── declarations
│ │ │ │ ├── bn.d.ts
│ │ │ │ ├── blake-hash.d.ts
│ │ │ │ ├── bs58smartcheck.d.ts
│ │ │ │ └── wif-smart.d.ts
│ │ │ ├── coinmapper.ts
│ │ │ ├── utxopicker.ts
│ │ │ ├── utxopicker
│ │ │ │ ├── subtractFee.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── accumulative.ts
│ │ │ │ └── forceUseUtxo.ts
│ │ │ ├── types.ts
│ │ │ ├── bip69.ts
│ │ │ ├── base.ts
│ │ │ └── bitcoincashUtils
│ │ │ │ └── base32.ts
│ │ ├── engine
│ │ │ ├── constants.ts
│ │ │ ├── types.ts
│ │ │ ├── await-lock.ts
│ │ │ ├── util
│ │ │ │ └── getOwnUtxosFromTx.ts
│ │ │ └── paymentRequest.ts
│ │ ├── info
│ │ │ ├── scriptTemplates
│ │ │ │ ├── types.ts
│ │ │ │ └── bitcoincashScriptTemplates.ts
│ │ │ ├── commonInfo.ts
│ │ │ ├── utxoPickers
│ │ │ │ └── dogeUtxoPicker.ts
│ │ │ ├── badcoin.ts
│ │ │ ├── ravencoin.ts
│ │ │ ├── eboost.ts
│ │ │ ├── qtum.ts
│ │ │ ├── feathercoin.ts
│ │ │ ├── ufo.ts
│ │ │ ├── bitcoincashtestnet.ts
│ │ │ ├── bitcoingoldtestnet.ts
│ │ │ ├── smartcash.ts
│ │ │ ├── zcoin.ts
│ │ │ ├── pivx.ts
│ │ │ ├── bitcointestnet.ts
│ │ │ ├── bitcointestnet4.ts
│ │ │ ├── vertcoin.ts
│ │ │ ├── bitcoingold.ts
│ │ │ ├── all.ts
│ │ │ ├── ecash.ts
│ │ │ ├── dogecoin.ts
│ │ │ ├── digibyte.ts
│ │ │ ├── litecoin.ts
│ │ │ ├── groestlcoin.ts
│ │ │ ├── bitcoin.ts
│ │ │ ├── dash.ts
│ │ │ ├── bitcoincash.ts
│ │ │ └── bitcoinsv.ts
│ │ ├── db
│ │ │ ├── Mutex.ts
│ │ │ ├── types.ts
│ │ │ ├── util
│ │ │ │ └── utxo.ts
│ │ │ ├── Models
│ │ │ │ ├── baselet.ts
│ │ │ │ └── TransactionData.ts
│ │ │ └── Baselets.ts
│ │ └── network
│ │ │ ├── types.ts
│ │ │ ├── Deferred.ts
│ │ │ ├── SocketEmitter.ts
│ │ │ ├── windowWS.ts
│ │ │ ├── nodejsWS.ts
│ │ │ └── socketQueue.ts
│ ├── constants.ts
│ ├── plugin
│ │ ├── utils.ts
│ │ ├── util
│ │ │ └── maximumFeeRateCalculator.ts
│ │ ├── CurrencyPlugin.ts
│ │ └── Metadata.ts
│ ├── fees
│ │ ├── processMempoolSpaceFees.ts
│ │ └── calcMinerFeePerByte.ts
│ ├── upgradeMemos.ts
│ └── validateMemos.ts
├── util
│ ├── unixTime.ts
│ ├── undefinedIfEmptyString.ts
│ ├── delay.ts
│ ├── typeUtil.ts
│ ├── indexAtProtected.ts
│ ├── cleaners.ts
│ ├── filterUndefined.ts
│ └── uriKeyParams.ts
├── declare-modules.d.ts
├── index.ts
└── react-native.ts
├── .husky
└── pre-commit
├── .travis.yml
├── ios
├── edge-currency-plugins.xcodeproj
│ └── project.pbxproj
├── EdgeCurrencyPluginsModule.m
└── EdgeCurrencyPluginsModule.swift
├── nodemon.json
├── android
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── app
│ │ └── edge
│ │ └── reactnative
│ │ └── currencyplugins
│ │ ├── EdgeCurrencyPluginsModule.java
│ │ └── EdgeCurrencyPluginsPackage.java
├── format-java.sh
└── build.gradle
├── test
├── common
│ ├── utxobased
│ │ ├── engine
│ │ │ ├── engine.fixtures
│ │ │ │ ├── bitcoinTestnet
│ │ │ │ │ └── dummyData
│ │ │ │ │ │ └── tables
│ │ │ │ │ │ ├── lastUsedByFormatPath
│ │ │ │ │ │ ├── bi.json
│ │ │ │ │ │ └── config.json
│ │ │ │ │ │ ├── txById
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ ├── 9c.json
│ │ │ │ │ │ ├── 19.json
│ │ │ │ │ │ ├── 86.json
│ │ │ │ │ │ ├── 99.json
│ │ │ │ │ │ ├── e1.json
│ │ │ │ │ │ └── c5.json
│ │ │ │ │ │ ├── utxoById
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ ├── 19.json
│ │ │ │ │ │ ├── 86.json
│ │ │ │ │ │ ├── 9c.json
│ │ │ │ │ │ ├── c5.json
│ │ │ │ │ │ └── e1.json
│ │ │ │ │ │ ├── addressByScriptPubkey
│ │ │ │ │ │ └── config.json
│ │ │ │ │ │ ├── utxoIdsByScriptPubkey
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ └── a914.json
│ │ │ │ │ │ ├── scriptPubkeyByPath
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ ├── bip49_0
│ │ │ │ │ │ │ └── 0.json
│ │ │ │ │ │ └── bip49_1
│ │ │ │ │ │ │ └── 0.json
│ │ │ │ │ │ ├── txIdsByDate
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ └── 583.json
│ │ │ │ │ │ └── txIdByBlockHeight
│ │ │ │ │ │ ├── config.json
│ │ │ │ │ │ └── 12.json
│ │ │ │ └── index.ts
│ │ │ ├── PaymentRequest.spec.ts
│ │ │ └── UtxoWalletTools.spec.ts
│ │ ├── keymanager
│ │ │ └── coins
│ │ │ │ ├── checkdatasig.spec.ts
│ │ │ │ ├── cashaddr.spec.ts
│ │ │ │ └── groestlcointransactiontest.spec.ts
│ │ └── addressFormat
│ │ │ └── addressFormatIndex
│ │ │ └── addressFormat.spec.ts
│ ├── plugin
│ │ ├── CurrencyPlugin.fixtures
│ │ │ ├── index.ts
│ │ │ └── common.ts
│ │ └── Metadata.spec.ts
│ └── fees
│ │ └── fees.spec.ts
├── unit
│ └── mocha.opts
└── util
│ ├── objectKeys.ts
│ └── testLog.ts
├── .eslintignore
├── .nycrc.json
├── .editorconfig
├── .github
├── workflows
│ ├── pr-checks.yml
│ └── pr-rebase.yml
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .mocharc.json
├── tsconfig.json
├── .eslintrc.json
├── edge-currency-plugins.podspec
├── edge-currency-plugins.d.ts
├── LICENSE
├── Jenkinsfile
├── patches
└── altcoin-js+1.0.0.patch
├── README.md
├── docs
└── currency-integration.md
├── webpack.config.js
└── package.json
/.yarnrc:
--------------------------------------------------------------------------------
1 | --ignore-scripts true
2 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/declarations/bn.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'bn.js'
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run precommit
5 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/declarations/blake-hash.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'blake-hash'
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: focal
2 | language: node_js
3 | node_js:
4 | - 18
5 | script:
6 | - yarn verify
7 |
--------------------------------------------------------------------------------
/ios/edge-currency-plugins.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // Placeholder so react-native autolinking can find us
2 |
--------------------------------------------------------------------------------
/src/util/unixTime.ts:
--------------------------------------------------------------------------------
1 | export const unixTime = (ts: number = Date.now()): number =>
2 | Math.round(ts / 1000)
3 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src"],
3 | "ext": "ts,json",
4 | "exec": "npm-run-all clean -p lib types"
5 | }
6 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/lastUsedByFormatPath/bi.json:
--------------------------------------------------------------------------------
1 | {"bip49_0":3,"bip49_1":9}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/config.json:
--------------------------------------------------------------------------------
1 | {"type":"HASH_BASE","prefixSize":2}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/config.json:
--------------------------------------------------------------------------------
1 | {"type":"HASH_BASE","prefixSize":2}
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /.nyc_output/
2 | /android/src/main/assets/edge-currency-plugins/
3 | /dist/
4 | /coverage/
5 | /lib/
6 | /node_modules/
7 |
--------------------------------------------------------------------------------
/.nycrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": [".ts"],
3 | "include": ["src/**/*.ts"],
4 | "require": ["sucrase/register"],
5 | "all": true
6 | }
7 |
--------------------------------------------------------------------------------
/test/unit/mocha.opts:
--------------------------------------------------------------------------------
1 | --colors
2 | --reporter mocha-multi-reporters
3 | --reporter-options configFile=mocha-config.json
4 | --timeout 10000
5 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/addressByScriptPubkey/config.json:
--------------------------------------------------------------------------------
1 | {"type":"HASH_BASE","prefixSize":4}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/lastUsedByFormatPath/config.json:
--------------------------------------------------------------------------------
1 | {"type":"HASH_BASE","prefixSize":2}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoIdsByScriptPubkey/config.json:
--------------------------------------------------------------------------------
1 | {"type":"HASH_BASE","prefixSize":4}
--------------------------------------------------------------------------------
/ios/EdgeCurrencyPluginsModule.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface RCT_EXTERN_MODULE(EdgeCurrencyPluginsModule, NSObject)
4 | @end
5 |
--------------------------------------------------------------------------------
/src/util/undefinedIfEmptyString.ts:
--------------------------------------------------------------------------------
1 | export const undefinedIfEmptyString = (str?: string): string | undefined =>
2 | str !== '' ? str : undefined
3 |
--------------------------------------------------------------------------------
/src/util/delay.ts:
--------------------------------------------------------------------------------
1 | export const delay = async (ms: number): Promise =>
2 | await new Promise(resolve => {
3 | setTimeout(resolve, ms)
4 | })
5 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/index.ts:
--------------------------------------------------------------------------------
1 | import bitcoinTestnet_tests from './bitcoinTestnet/tests'
2 |
3 | export const fixtures = [bitcoinTestnet_tests]
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/common/utxobased/engine/constants.ts:
--------------------------------------------------------------------------------
1 | export const BLOCKBOOK_TXS_PER_PAGE = 100
2 |
3 | export const CACHE_THROTTLE = 0.05
4 |
5 | export const MAX_CONNECTIONS = 2
6 |
7 | export const NEW_CONNECTIONS = 8
8 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/scriptPubkeyByPath/config.json:
--------------------------------------------------------------------------------
1 | {"type":"COUNT_BASE","bucketSize":50,"partitions":{"":{"length":0},"bip49_0":{"length":29},"bip49_1":{"length":35}}}
--------------------------------------------------------------------------------
/src/declare-modules.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'react-native' {
2 | export const NativeModules: {
3 | EdgeCurrencyPluginsModule: {
4 | getConstants: () => {
5 | sourceUri: string
6 | }
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txIdsByDate/config.json:
--------------------------------------------------------------------------------
1 | {"type":"RANGE_BASE","bucketSize":2592000,"rangeKey":"date","idKey":"txid","idPrefixLength":1,"limits":{"":{"minRange":1511921755,"maxRange":1512020289}},"sizes":{"":6}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txIdByBlockHeight/config.json:
--------------------------------------------------------------------------------
1 | {"type":"RANGE_BASE","bucketSize":100000,"rangeKey":"blockHeight","idKey":"txid","idPrefixLength":1,"limits":{"":{"minRange":1249371,"maxRange":1250624}},"sizes":{"":6}}
--------------------------------------------------------------------------------
/.github/workflows/pr-checks.yml:
--------------------------------------------------------------------------------
1 | name: PR Checks
2 | on: [pull_request]
3 | jobs:
4 | block-wip-pr:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2.0.0
8 | - name: Block WIP PR
9 | uses: samholmes/block-wip-pr-action@v1.2.0
10 |
--------------------------------------------------------------------------------
/test/util/objectKeys.ts:
--------------------------------------------------------------------------------
1 | interface ObjectType {
2 | [key: string]: unknown
3 | }
4 |
5 | /**
6 | * A type safe way to get the keys of an object.
7 | */
8 | export const objectKeys = (object: T): Array => {
9 | return Object.keys(object)
10 | }
11 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/declarations/bs58smartcheck.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'bs58smartcheck' {
2 | export const encode: (buffer: Buffer | number[] | Uint8Array) => string
3 | export const decodeUnsafe: (string: string) => Buffer | undefined
4 | export const decode: (string: string) => Buffer
5 | }
6 |
--------------------------------------------------------------------------------
/test/util/testLog.ts:
--------------------------------------------------------------------------------
1 | import { EdgeLog } from 'edge-core-js/types'
2 |
3 | export const noOp = (..._args: any[]): void => undefined
4 |
5 | export const testLog: EdgeLog = Object.assign((): void => undefined, {
6 | breadcrumb: noOp,
7 | crash: noOp,
8 | error: noOp,
9 | warn: noOp
10 | })
11 |
--------------------------------------------------------------------------------
/src/util/typeUtil.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Like Pick, but removed fields are optional.
3 | */
4 | export type SoftPick = Pick & Partial
5 |
6 | /**
7 | * Like Omit, but removed fields are optional.
8 | */
9 | export type SoftOmit = Omit & Partial>
10 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### CHANGELOG
2 |
3 | Does this branch warrant an entry to the CHANGELOG?
4 |
5 | - [ ] Yes
6 | - [ ] No
7 |
8 | ### Dependencies
9 |
10 | none
11 |
12 | ### Description
13 |
14 | none
15 |
--------------------------------------------------------------------------------
/src/common/constants.ts:
--------------------------------------------------------------------------------
1 | export const INFO_SERVER_URI = 'https://info1.edge.app'
2 |
3 | // Fees
4 | export const FEES_PATH = 'fees.json'
5 | export const MAX_FEE = 999999999.0
6 | export const LOW_FEE = 2
7 | export const MAX_HIGH_DELAY = 200
8 | export const MAX_STANDARD_DELAY = 6
9 | export const MIN_STANDARD_DELAY = 2
10 | export const MIN_LOW_DELAY = 1
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build output:
2 | /.cache/
3 | /android/*.jar
4 | /android/src/main/assets/edge-currency-plugins/
5 | /dist/
6 | /lib/
7 |
8 | # Package managers:
9 | node_modules/
10 | npm-debug.log
11 | package-lock.json
12 | yarn-error.log
13 |
14 | # Editors:
15 | .DS_Store
16 | .idea/
17 | .vscode/
18 |
19 | # Test results:
20 | .nyc_output
21 | /coverage/
22 |
--------------------------------------------------------------------------------
/src/common/utxobased/info/scriptTemplates/types.ts:
--------------------------------------------------------------------------------
1 | export interface ScriptTemplates {
2 | [key: string]: ScriptTemplate
3 | }
4 |
5 | export type ScriptTemplate = (
6 | pubkey: string,
7 | optionalArgs?: OptionalScriptTemplateArgs
8 | ) => string
9 |
10 | export interface OptionalScriptTemplateArgs {
11 | cdsSig?: string
12 | cdsMsg?: string
13 | cdsPubKey?: string
14 | }
15 |
--------------------------------------------------------------------------------
/src/util/indexAtProtected.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets an element out of an array but includes protection if the index
3 | * overflows the length of the array.
4 | **/
5 | export const indexAtProtected = (
6 | array: T,
7 | index: number
8 | ): T extends Array ? U : undefined =>
9 | array != null ? array[Math.min(index, array.length - 1)] : undefined
10 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/coinmapper.ts:
--------------------------------------------------------------------------------
1 | import { all } from '../info/all'
2 | import { CoinInfo } from './../../plugin/types'
3 |
4 | export function getCoinFromString(coinName: string): CoinInfo {
5 | const pluginInfo = all.find(
6 | pluginInfo => pluginInfo.coinInfo.name === coinName
7 | )
8 | if (pluginInfo == null) {
9 | throw new Error('Could not find coin info ' + coinName)
10 | }
11 | return pluginInfo.coinInfo
12 | }
13 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": ["ts"],
3 | "spec": "test/**/*.spec.ts",
4 | "require": "sucrase/register",
5 | "colors": true,
6 | "reporter":"mocha-multi-reporters",
7 | "reporterOption": "configFile=.mocharc.json",
8 | "reporterEnabled": "mochawesome,mocha-junit-reporter",
9 | "mochaJunitReporterReporterOptions": {
10 | "mochaFile": "./coverage/junit.xml"
11 | },
12 | "mochawesomeReporterOptions": {
13 | "reportDir": "coverage",
14 | "reportName": "mochawesome"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "declarationDir": "lib",
5 | "emitDeclarationOnly": true,
6 | "sourceRoot": "src",
7 | "sourceMap": true,
8 | "esModuleInterop": true,
9 | "module": "commonjs",
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "target": "es2017",
13 |
14 | "strict": true
15 | },
16 | "include": ["./edge-currency-plugins.d.ts", "./src/**/*", "./test/**/*"],
17 | "exclude": ["./node_modules/**/*", "./lib/**/*"]
18 | }
19 |
--------------------------------------------------------------------------------
/android/format-java.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | here=$(dirname $0)
4 |
5 | formatJava() {
6 | tool="google-java-format-1.7-all-deps.jar"
7 | url="https://github.com/google/google-java-format/releases/download/google-java-format-1.7/$tool"
8 | jar="$here/$tool"
9 |
10 | # If the tool is missing, grab it from GitHub:
11 | if [ ! -e "$jar" ]; then
12 | curl -L -o "$jar" "$url"
13 | fi
14 |
15 | echo $(find "$here/src" -name "*.java")
16 | java -jar "$jar" --replace $(find "$here/src" -name "*.java")
17 | }
18 |
19 | formatJava
20 |
--------------------------------------------------------------------------------
/src/util/cleaners.ts:
--------------------------------------------------------------------------------
1 | import { asCodec, asString, Cleaner } from 'cleaners'
2 | import { base16 } from 'rfc4648'
3 |
4 | /**
5 | * A string of hex-encoded binary data.
6 | */
7 | export const asBase16: Cleaner = asCodec(
8 | raw => base16.parse(asString(raw)),
9 | clean => base16.stringify(clean).toLowerCase()
10 | )
11 |
12 | export function asIntegerString(raw: unknown): string {
13 | const clean = asString(raw)
14 | if (!/^\d+$/.test(clean)) {
15 | throw new Error('Expected an integer string')
16 | }
17 | return clean
18 | }
19 |
--------------------------------------------------------------------------------
/src/common/plugin/utils.ts:
--------------------------------------------------------------------------------
1 | import { all as plugins } from '../utxobased/info/all'
2 |
3 | export const getFormatsForNetwork = (coinName: string): string[] => {
4 | for (const plugin of plugins) {
5 | if (plugin.coinInfo.name === coinName) return plugin.engineInfo.formats
6 | }
7 | return []
8 | }
9 |
10 | interface GenericObject {
11 | [key: string]: T
12 | }
13 |
14 | export const removeItem = (obj: GenericObject, key: string): void => {
15 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
16 | delete obj[key]
17 | }
18 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/19.json:
--------------------------------------------------------------------------------
1 | {"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13_0":{"id":"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13_0","txid":"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13","vout":0,"value":"16250000","scriptPubkey":"a9142244ce86d664e85801f7eb2a56dd35afd268212587","script":"a9142244ce86d664e85801f7eb2a56dd35afd268212587","redeemScript":"0014d10b4362b030c3f3fad0c0eb5a36c96f5afd1d4a","scriptType":"p2wpkhp2sh","blockHeight":1249371,"spent":false}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/86.json:
--------------------------------------------------------------------------------
1 | {"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5_0":{"id":"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5_0","txid":"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5","vout":0,"value":"65000000","scriptPubkey":"a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d87","script":"a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d87","redeemScript":"0014fe19258369c5f2fc23205075e343be0953fab38f","scriptType":"p2wpkhp2sh","blockHeight":1249371,"spent":false}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/9c.json:
--------------------------------------------------------------------------------
1 | {"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d_0":{"id":"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d_0","txid":"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d","vout":0,"value":"32500000","scriptPubkey":"a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e287","script":"a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e287","redeemScript":"0014299f3268005230b9c3b9b30028b277b8df122f87","scriptType":"p2wpkhp2sh","blockHeight":1249371,"spent":false}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/c5.json:
--------------------------------------------------------------------------------
1 | {"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1_2":{"id":"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1_2","txid":"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1","vout":2,"value":"128370000","scriptPubkey":"a914bf76ff07ad44824990e72be8d85aa102b8cedc1287","script":"a914bf76ff07ad44824990e72be8d85aa102b8cedc1287","redeemScript":"0014bdd2dcc97172ad03275551da42680a8a3d89a9ac","scriptType":"p2wpkhp2sh","blockHeight":1250624,"spent":false}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoById/e1.json:
--------------------------------------------------------------------------------
1 | {"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3_0":{"id":"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3_0","txid":"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3","vout":0,"value":"130000000","scriptPubkey":"a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d1787","script":"a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d1787","redeemScript":"00142e59ca4d0b9b6cff8e2663c962bfe3abe000781f","scriptType":"p2wpkhp2sh","blockHeight":1250624,"spent":false}}
--------------------------------------------------------------------------------
/src/common/utxobased/db/Mutex.ts:
--------------------------------------------------------------------------------
1 | type Mutex = (callback: () => Promise) => Promise
2 |
3 | export function makeMutex(): Mutex {
4 | let busy = false
5 | const queue: Array<(value: unknown) => void> = []
6 | return async function lock(callback: () => T | Promise): Promise {
7 | if (busy) await new Promise(resolve => queue.push(resolve))
8 | try {
9 | busy = true
10 | return callback()
11 | } finally {
12 | busy = false
13 | const resolve = queue.shift()
14 | if (resolve != null) resolve({})
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/utxopicker.ts:
--------------------------------------------------------------------------------
1 | import { accumulative } from './utxopicker/accumulative'
2 | import { forceUseUtxo } from './utxopicker/forceUseUtxo'
3 | import { subtractFee } from './utxopicker/subtractFee'
4 | import { UtxoPickingFunc } from './utxopicker/types'
5 | export * from './utxopicker/types'
6 |
7 | export interface UtxoPicker {
8 | forceUseUtxo: UtxoPickingFunc
9 | subtractFee: UtxoPickingFunc
10 | accumulative: UtxoPickingFunc
11 | }
12 |
13 | export const utxoPicker: UtxoPicker = {
14 | forceUseUtxo,
15 | subtractFee,
16 | accumulative
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/types.ts:
--------------------------------------------------------------------------------
1 | export interface InnerSocketCallbacks {
2 | /**
3 | * Called when the socket closes.
4 | * See rfc6455 section 7.4.1 for status codes.
5 | */
6 | onClose: (code: number, reason: string) => void
7 |
8 | onError: (error?: Event | Error) => void
9 | onMessage: (message: string) => void
10 | onOpen: () => void
11 | }
12 |
13 | export enum ReadyState {
14 | CONNECTING,
15 | OPEN,
16 | CLOSING,
17 | CLOSED
18 | }
19 |
20 | export interface InnerSocket {
21 | readyState: ReadyState
22 | disconnect: () => void
23 | send: (message: string) => void
24 | }
25 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txIdsByDate/583.json:
--------------------------------------------------------------------------------
1 | [{"txid":"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13","date":1511921755},{"txid":"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5","date":1511921755},{"txid":"99701d23ae201fafeafeafa2120f048213f4cec6030208a35b4adab98f500bf0","date":1511921755},{"txid":"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d","date":1511921755},{"txid":"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1","date":1512020289},{"txid":"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3","date":1512020289}]
--------------------------------------------------------------------------------
/src/util/filterUndefined.ts:
--------------------------------------------------------------------------------
1 | // Accepts an array of values and returns a new array of values without undefined values.
2 | export const filterUndefined = (values: Array): T[] => {
3 | return values.filter(value => value !== undefined) as T[]
4 | }
5 |
6 | export const removeUndefined = >(
7 | obj: T
8 | ): Partial => {
9 | const result: Partial = {}
10 |
11 | for (const key in obj) {
12 | if (obj[key] !== undefined) {
13 | // We need to assure TypeScript that the key indeed exists on T
14 | result[key as keyof T] = obj[key]
15 | }
16 | }
17 |
18 | return result
19 | }
20 |
--------------------------------------------------------------------------------
/src/util/uriKeyParams.ts:
--------------------------------------------------------------------------------
1 | export function getUriKeyParams(uri: string): string[] {
2 | const params = uri.match(/%\{(\w|\d)+\}/g)
3 | return params ?? []
4 | }
5 |
6 | export function replaceKeyParams(
7 | uri: string,
8 | options: { [P in T]?: string }
9 | ): string {
10 | const keyParams = getUriKeyParams(uri)
11 |
12 | for (const keyParam of keyParams) {
13 | const keyIndex = keyParam.replace(/%|{|}/g, '') as T
14 | const key: string | undefined = options[keyIndex]
15 | if (key == null) throw new Error(`Missing key for connection URI ${uri}`)
16 | uri = uri.replace(keyParam, key)
17 | }
18 |
19 | return uri
20 | }
21 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txIdByBlockHeight/12.json:
--------------------------------------------------------------------------------
1 | [{"txid":"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13","blockHeight":1249371},{"txid":"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5","blockHeight":1249371},{"txid":"99701d23ae201fafeafeafa2120f048213f4cec6030208a35b4adab98f500bf0","blockHeight":1249371},{"txid":"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d","blockHeight":1249371},{"txid":"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1","blockHeight":1250624},{"txid":"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3","blockHeight":1250624}]
--------------------------------------------------------------------------------
/src/common/utxobased/info/commonInfo.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCurrencyInfo, EdgeMemoOption } from 'edge-core-js/types'
2 |
3 | export const utxoCustomFeeTemplate: EdgeCurrencyInfo['customFeeTemplate'] = [
4 | {
5 | type: 'nativeAmount',
6 | key: 'satPerByte',
7 | displayName: 'Satoshis Per Byte',
8 | displayMultiplier: '0'
9 | }
10 | ]
11 |
12 | export const utxoMemoOptions: EdgeMemoOption[] = [
13 | {
14 | type: 'hex',
15 | hidden: true,
16 | maxBytes: 80,
17 | memoName: 'OP_RETURN'
18 | }
19 | ]
20 |
21 | // Deprecated:
22 | export const legacyMemoInfo: Partial = {
23 | memoMaxLength: 80,
24 | memoType: 'text'
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCorePluginOptions, EdgeCorePlugins } from 'edge-core-js/types'
2 |
3 | import { makeCurrencyPlugin } from './common/plugin/CurrencyPlugin'
4 | import { all } from './common/utxobased/info/all'
5 |
6 | const plugins: EdgeCorePlugins = {}
7 |
8 | for (const info of all) {
9 | plugins[info.currencyInfo.pluginId] = (options: EdgeCorePluginOptions) =>
10 | makeCurrencyPlugin(options, info)
11 | }
12 |
13 | declare global {
14 | interface Window {
15 | addEdgeCorePlugins?: (plugins: EdgeCorePlugins) => void
16 | }
17 | }
18 |
19 | if (typeof window !== 'undefined') {
20 | window.addEdgeCorePlugins?.(plugins)
21 | }
22 |
23 | export default plugins
24 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/utxoIdsByScriptPubkey/a914.json:
--------------------------------------------------------------------------------
1 | {"a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e287":["9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d_0"],"a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d87":["86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5_0"],"a914bf76ff07ad44824990e72be8d85aa102b8cedc1287":["c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1_2"],"a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d1787":["e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3_0"],"a9142244ce86d664e85801f7eb2a56dd35afd268212587":["19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13_0"]}
--------------------------------------------------------------------------------
/ios/EdgeCurrencyPluginsModule.swift:
--------------------------------------------------------------------------------
1 | @objc(EdgeCurrencyPluginsModule) class EdgeCurrencyPluginsModule: NSObject {
2 | @objc static func requiresMainQueueSetup() -> Bool { return false }
3 |
4 | @objc func constantsToExport() -> NSDictionary {
5 | return ["sourceUri": sourceUri() ?? ""]
6 | }
7 |
8 | func sourceUri() -> String? {
9 | if let bundleUrl = Bundle.main.url(
10 | forResource: "edge-currency-plugins",
11 | withExtension: "bundle"
12 | ),
13 | let bundle = Bundle(url: bundleUrl),
14 | let script = bundle.url(forResource: "edge-currency-plugins", withExtension: "js")
15 | {
16 | return script.absoluteString
17 | }
18 | return nil
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/declarations/wif-smart.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'wif-smart' {
2 | // Type definitions for wif-smart 2.0.0
3 | // Project: https://github.com/SmartCash/wif
4 |
5 | export interface WIFReturn {
6 | readonly version: number
7 | readonly privateKey: Buffer
8 | readonly compressed: boolean
9 | }
10 |
11 | export function decodeRaw(buffer: Buffer, version?: number): WIFReturn
12 | export function decode(string: string, version?: number): WIFReturn
13 |
14 | export function encodeRaw(
15 | version: number,
16 | privateKey: Buffer,
17 | compressed: boolean
18 | ): Buffer
19 |
20 | export function encode(
21 | version: number,
22 | privateKey: Buffer,
23 | compressed: boolean
24 | ): string
25 | }
26 |
--------------------------------------------------------------------------------
/src/react-native.ts:
--------------------------------------------------------------------------------
1 | import { MemletConfig } from 'memlet'
2 | import { NativeModules } from 'react-native'
3 |
4 | const { EdgeCurrencyPluginsModule } = NativeModules
5 | const { sourceUri } = EdgeCurrencyPluginsModule.getConstants()
6 |
7 | export const pluginUri = sourceUri
8 | export const debugUri = 'http://localhost:8084/edge-currency-plugins.js'
9 |
10 | export interface EdgeCurrencyPluginIoOptions {
11 | readonly memletConfig?: MemletConfig
12 | }
13 |
14 | export interface EdgeCurrencyPluginNativeIo {
15 | readonly memletConfig?: MemletConfig
16 | }
17 |
18 | export function makePluginIo(
19 | options: EdgeCurrencyPluginIoOptions = {}
20 | ): EdgeCurrencyPluginNativeIo {
21 | const { memletConfig } = options
22 |
23 | return {
24 | memletConfig
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/common/plugin/util/maximumFeeRateCalculator.ts:
--------------------------------------------------------------------------------
1 | import { div, mul, round } from 'biggystring'
2 | import { EdgeCurrencyInfo } from 'edge-core-js/types'
3 |
4 | /**
5 | * Calculates a maximum fee rate of 1 USD per vByte given a USD exchange rate
6 | * (usdPrice).
7 | */
8 | export const maximumFeeRateCalculator = (
9 | currencyInfo: EdgeCurrencyInfo,
10 | usdPrice: number
11 | ): string | undefined => {
12 | const { currencyCode, denominations } = currencyInfo
13 | const primaryDenomination = denominations.find(
14 | denom => denom.name === currencyCode
15 | )
16 | if (primaryDenomination == null)
17 | throw new Error(`Missing primary currency denomination for ${currencyCode}`)
18 |
19 | return round(
20 | mul(div('1', String(usdPrice), 16), primaryDenomination.multiplier),
21 | 0
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/test/common/plugin/CurrencyPlugin.fixtures/index.ts:
--------------------------------------------------------------------------------
1 | import { FixtureType } from './common'
2 | import { bitcoin } from './currencies/bitcoin'
3 | import { bitcoincash } from './currencies/bitcoincash'
4 | import { bitcoinsv } from './currencies/bitcoinsv'
5 | import { digibyte } from './currencies/digibyte'
6 | import { ecash } from './currencies/ecash'
7 | import { feathercoin } from './currencies/feathercoin'
8 | import { groestlcoin } from './currencies/groestlcoin'
9 | import { litecoin } from './currencies/litecoin'
10 | import { smartcash } from './currencies/smartcash'
11 | import { zcoin } from './currencies/zcoin'
12 |
13 | export const fixtures: FixtureType[] = [
14 | bitcoin,
15 | bitcoincash,
16 | bitcoinsv,
17 | digibyte,
18 | ecash,
19 | feathercoin,
20 | groestlcoin,
21 | litecoin,
22 | smartcash,
23 | zcoin
24 | ]
25 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/Deferred.ts:
--------------------------------------------------------------------------------
1 | // Wrapper class for a Promise with external resolve/reject functions
2 | // Inspired by https://stackoverflow.com/questions/26150232/resolve-javascript-promise-outside-function-scope
3 |
4 | export default class Deferred {
5 | private _resolve: (value: T) => void = () => {
6 | return
7 | }
8 |
9 | private _reject: (reason?: unknown) => void = () => {
10 | return
11 | }
12 |
13 | private readonly _promise: Promise = new Promise((resolve, reject) => {
14 | this._reject = reject
15 | this._resolve = resolve
16 | })
17 |
18 | public get promise(): Promise {
19 | return this._promise
20 | }
21 |
22 | public resolve(value: T): void {
23 | this._resolve(value)
24 | }
25 |
26 | public reject(reason?: unknown): void {
27 | this._reject(reason)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/utxopicker/subtractFee.ts:
--------------------------------------------------------------------------------
1 | import { Output, UtxoPickerArgs, UtxoPickerResult } from './types'
2 | import * as utils from './utils'
3 |
4 | export function subtractFee(args: UtxoPickerArgs): UtxoPickerResult {
5 | const { utxos, targets, feeRate } = args
6 |
7 | const outputs: Output[] = targets.map(target => ({
8 | ...target,
9 | script: Buffer.from(target.script, 'hex'),
10 | scriptPubkey: Buffer.from(target.script, 'hex')
11 | }))
12 |
13 | const fee = Math.ceil(feeRate * utils.transactionBytes(utxos, outputs))
14 | targets[0].value -= fee
15 | outputs[0].value -= fee
16 |
17 | const targetValue = utils.sumOrNaN(targets)
18 | if (isNaN(targetValue) || fee > targetValue)
19 | return { inputs: utxos, fee, changeUsed: false }
20 |
21 | return { inputs: utxos, outputs, fee, changeUsed: false }
22 | }
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "standard-kit/prettier",
4 | "standard-kit/prettier/typescript",
5 | "standard-kit/prettier/node"
6 | ],
7 | "parserOptions": {
8 | "project": "tsconfig.json"
9 | },
10 | "plugins": ["json-files", "simple-import-sort"],
11 | "rules": {
12 | "@typescript-eslint/no-unused-vars": [
13 | "error",
14 | {
15 | "ignoreRestSiblings": true,
16 | "argsIgnorePattern": "^_"
17 | }
18 | ],
19 | "@typescript-eslint/restrict-template-expressions": "off",
20 | "no-unused-vars": "off",
21 | "no-useless-return": "off",
22 | "no-void": "off",
23 | "simple-import-sort/imports": "error"
24 | },
25 | "overrides": [
26 | {
27 | "files": "package.json",
28 | "rules": {
29 | "json-files/sort-package-json": "error"
30 | }
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/PaymentRequest.spec.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import { expect } from 'chai'
3 | import chaiAsPromised from 'chai-as-promised'
4 |
5 | import { getSpendTargets } from '../../../../src/common/utxobased/engine/paymentRequest'
6 |
7 | chai.should()
8 | chai.use(chaiAsPromised)
9 |
10 | describe('PaymentRequest', function () {
11 | it('calculate spend target', async function () {
12 | const outputs = [
13 | {
14 | amount: 239200,
15 | address: 'n3YaQSkTXrpbQyAns1kQQRxsECMn9ifx5n'
16 | }
17 | ]
18 | const result = getSpendTargets(outputs)
19 | expect(result).to.eql({
20 | nativeAmount: 239200,
21 | spendTargets: [
22 | {
23 | publicAddress: 'n3YaQSkTXrpbQyAns1kQQRxsECMn9ifx5n',
24 | nativeAmount: '239200'
25 | }
26 | ]
27 | })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/edge-currency-plugins.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = package['name']
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.homepage = package['homepage']
10 | s.license = package['license']
11 | s.authors = package['author']
12 |
13 | s.platform = :ios, "9.0"
14 | s.requires_arc = true
15 | s.source = {
16 | :git => "https://github.com/EdgeApp/edge-currency-plugins.git",
17 | :tag => "v#{s.version}"
18 | }
19 | s.source_files =
20 | "ios/EdgeCurrencyPluginsModule.swift",
21 | "ios/EdgeCurrencyPluginsModule.m"
22 |
23 | s.resource_bundles = {
24 | "edge-currency-plugins" => "android/src/main/assets/edge-currency-plugins/*.js"
25 | }
26 |
27 | s.dependency "React-Core"
28 | end
29 |
--------------------------------------------------------------------------------
/test/common/utxobased/keymanager/coins/checkdatasig.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import { describe, it } from 'mocha'
3 |
4 | import { scriptTemplates } from '../../../../../src/common/utxobased/info/scriptTemplates/bitcoincashScriptTemplates'
5 |
6 | describe('bitcoin cash checkdatasig scripting tests', () => {
7 | const redeemScript = scriptTemplates.replayProtection(
8 | '038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508'
9 | )
10 | it('bitcoin cash checkdatasig redeem script test', () => {
11 | expect(redeemScript).to.equal(
12 | '4630440220256c12175e809381f97637933ed6ab97737d263eaaebca6add21bced67fd12a402205ce29ecc1369d6fc1b51977ed38faaf41119e3be1d7edfafd7cfaf0b6061bd070021038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508bb21038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508ac'
13 | )
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/android/src/main/java/app/edge/reactnative/currencyplugins/EdgeCurrencyPluginsModule.java:
--------------------------------------------------------------------------------
1 | package app.edge.reactnative.currencyplugins;
2 |
3 | import com.facebook.react.bridge.ReactApplicationContext;
4 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public class EdgeCurrencyPluginsModule extends ReactContextBaseJavaModule {
9 | EdgeCurrencyPluginsModule(ReactApplicationContext context) {
10 | super(context);
11 | }
12 |
13 | @Override
14 | public Map getConstants() {
15 | final Map constants = new HashMap<>();
16 | constants.put(
17 | "sourceUri",
18 | "file:///android_asset/edge-currency-plugins/edge-currency-plugins.js");
19 | return constants;
20 | }
21 |
22 | @Override
23 | public String getName() {
24 | return "EdgeCurrencyPluginsModule";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/pr-rebase.yml:
--------------------------------------------------------------------------------
1 | name: PR Rebase
2 | on:
3 | issue_comment:
4 | types: [created]
5 | jobs:
6 | rebase:
7 | name: Rebase
8 | if: >-
9 | github.event.issue.pull_request != '' &&
10 | (
11 | contains(github.event.comment.body, '/autosquash') ||
12 | contains(github.event.comment.body, '/fixup') ||
13 | contains(github.event.comment.body, '/rebase')
14 | )
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Checkout the latest code
18 | uses: actions/checkout@v3
19 | with:
20 | token: ${{ secrets.GITHUB_TOKEN }}
21 | fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
22 | - name: Automatic Rebase
23 | uses: EdgeApp/rebase@changelog-resolver
24 | with:
25 | autosquash: ${{ true }}
26 | changelogResolver: ${{ true }}
27 | env:
28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 |
--------------------------------------------------------------------------------
/android/src/main/java/app/edge/reactnative/currencyplugins/EdgeCurrencyPluginsPackage.java:
--------------------------------------------------------------------------------
1 | package app.edge.reactnative.currencyplugins;
2 |
3 | import androidx.annotation.NonNull;
4 | import com.facebook.react.ReactPackage;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 | public class EdgeCurrencyPluginsPackage implements ReactPackage {
12 | @NonNull
13 | @Override
14 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
15 | return Collections.singletonList(
16 | new EdgeCurrencyPluginsModule(reactContext));
17 | }
18 |
19 | @NonNull
20 | @Override
21 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
22 | return Collections.emptyList();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/edge-currency-plugins.d.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCorePluginOptions, EdgeCurrencyPlugin } from 'edge-core-js/types'
2 |
3 | import {
4 | EdgeCurrencyPluginIoOptions,
5 | EdgeCurrencyPluginNativeIo
6 | } from './src/react-native'
7 |
8 | /**
9 | * Add this to your `nativeIo` object on React Native,
10 | * as `{ 'edge-currency-plugins': makePluginIo() }`
11 | */
12 | export function makePluginIo(
13 | options?: EdgeCurrencyPluginIoOptions
14 | ): EdgeCurrencyPluginNativeIo
15 |
16 | /**
17 | * Debugging-URI to use on React Native,
18 | * along with running `yarn start` in this repo.
19 | */
20 | export const debugUri: string
21 |
22 | /* Regular URI to use on React Native. */
23 | export const pluginUri: string
24 |
25 | type EdgeCorePluginFactory = (env: EdgeCorePluginOptions) => EdgeCurrencyPlugin
26 |
27 | /**
28 | * The Node.js default export.
29 | */
30 | declare const plugins: {
31 | [pluginId: string]: EdgeCorePluginFactory
32 | }
33 |
34 | export default plugins
35 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/utxopicker/types.ts:
--------------------------------------------------------------------------------
1 | import { PsbtInput } from 'bip174/src/lib/interfaces'
2 |
3 | import { ScriptTypeEnum } from '../keymanager'
4 |
5 | export type Input = UTXO
6 | export interface UTXO extends PsbtInput {
7 | hash: Buffer
8 | index: number
9 | sequence: number
10 | value: number
11 | script: Buffer
12 | scriptPubkey: Buffer
13 | scriptType: ScriptTypeEnum
14 | }
15 |
16 | export interface Output {
17 | script: Buffer
18 | scriptPubkey: Buffer
19 | value: number
20 | }
21 |
22 | export interface Target {
23 | script: string
24 | value: number
25 | }
26 |
27 | export interface UtxoPickerArgs {
28 | utxos: UTXO[]
29 | useUtxos?: UTXO[]
30 | targets: Target[]
31 | feeRate: number
32 | changeScript: string
33 | }
34 |
35 | export interface UtxoPickerResult {
36 | inputs: Input[]
37 | // Nullish outputs means fee exceeds selected inputs
38 | outputs?: Output[]
39 | changeUsed: boolean
40 | fee: number
41 | }
42 |
43 | export type UtxoPickingFunc = (args: UtxoPickerArgs) => UtxoPickerResult
44 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/SocketEmitter.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events'
2 |
3 | export declare interface SocketEmitter {
4 | emit: ((event: SocketEvent.CONNECTION_OPEN, uri: string) => void) &
5 | ((
6 | event: SocketEvent.CONNECTION_CLOSE,
7 | uri: string,
8 | error?: Error
9 | ) => boolean) &
10 | ((
11 | event: SocketEvent.CONNECTION_TIMER,
12 | uri: string,
13 | queryTime: number
14 | ) => boolean)
15 |
16 | on: ((
17 | event: SocketEvent.CONNECTION_OPEN,
18 | listener: (uri: string) => void
19 | ) => this) &
20 | ((
21 | event: SocketEvent.CONNECTION_CLOSE,
22 | listener: (uri: string, error?: Error) => void
23 | ) => this) &
24 | ((
25 | event: SocketEvent.CONNECTION_TIMER,
26 | listener: (uri: string, queryTime: number) => void
27 | ) => this)
28 | }
29 | export class SocketEmitter extends EventEmitter {}
30 |
31 | export enum SocketEvent {
32 | CONNECTION_OPEN = 'connection:open',
33 | CONNECTION_CLOSE = 'connection:close',
34 | CONNECTION_TIMER = 'connection:timer'
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Edge (formerly Airbitz)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/9c.json:
--------------------------------------------------------------------------------
1 | {"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d":{"txid":"9c8d213abeddebe6800597a5333215d9eb90fcae710c3b95daa0b7710f72612d","hex":"01000000000101b4c8feb9caaa9473c14b10234c3837466a7b1a95edadac6e716c1796a8ea6361010000001716001475f2fa56af605aa713b7f7dfc043065247383232ffffffff0220e9ef010000000017a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e28758cf39e92b00000017a914d8c5d40f959ba17f43996ee0e97d2f711fa82a2b870247304402204cc76bc4fd27b3c5b03addcb203d8880b5d9fdab8f0231e24e5e54d87920e671022069392f8ad752b3b35b4791c427119ea511e67b507e35aac9ee58c495c072a9df01210295d2069f6266db09ab1dd1926f1649f5ddc09a55e38ca85637ac893b324170e700000000","blockHeight":1249371,"date":1511921755,"fees":"100000","inputs":[{"txId":"6163eaa896176c716eacaded951a7b6a4637384c23104bc17394aacab9fec8b4","outputIndex":1,"n":0,"scriptPubkey":"a914e28b1c3694da1482c426ecda8989693da9f4423487","amount":"188629073688"}],"outputs":[{"n":0,"scriptPubkey":"a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e287","amount":"32500000"},{"n":1,"scriptPubkey":"a914d8c5d40f959ba17f43996ee0e97d2f711fa82a2b87","amount":"188596473688"}],"ourIns":[],"ourOuts":["0"],"ourAmount":"32500000"}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/19.json:
--------------------------------------------------------------------------------
1 | {"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13":{"txid":"19e59364daf34d97ed6584e9e978f3e2375adea9a4561a83d2066d92a010ba13","hex":"01000000000101b2ed393f1353c54a1cc364ebf1ae71ec9289dfb565566a4c5697959926c3fb8b01000000171600143cbeea594ca16860216d7b940eae5329f52f5026ffffffff0290f4f7000000000017a9142244ce86d664e85801f7eb2a56dd35afd268212587dd2daa282c00000017a9141f3b0503f81dd72ef6854be892a425370c9d374187024830450221008e368505903906696edbb297ec3d9d42d3631dc38b7fb8154212ce10fad6a4f0022063664b7ac33a315cface6fba7bf277def8b372d68bfb27d0da5b0dd73af734c701210346bcb05ff3e4b32e824a74f9da5cf0dd6106419fcd3939b0fe22ed646fde308b00000000","blockHeight":1249371,"date":1511921755,"fees":"100000","inputs":[{"txId":"8bfbc326999597564c6a5665b5df8992ec71aef1eb64c31c4ac553133f39edb2","outputIndex":1,"n":0,"scriptPubkey":"a91420233dbf0f3c341339e61c9fcb8e83e4e0650d7a87","amount":"189677152525"}],"outputs":[{"n":0,"scriptPubkey":"a9142244ce86d664e85801f7eb2a56dd35afd268212587","amount":"16250000"},{"n":1,"scriptPubkey":"a9141f3b0503f81dd72ef6854be892a425370c9d374187","amount":"189660802525"}],"ourIns":[],"ourOuts":["0"],"ourAmount":"16250000"}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/86.json:
--------------------------------------------------------------------------------
1 | {"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5":{"txid":"86de2d2e689bda718fd7e2a486c9c6ed371c0cee9c3c6bc905211552c02f88a5","hex":"01000000000101ef47bed0f91b51849bdf8aac058a516ba4dc3051c5d76866d15db8e4fdcf40e601000000171600143cd6efa7f9224fa989fd4b98b16bc0a6e9d36edbffffffff0240d2df030000000017a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d873c7501f92b00000017a9143224fc9ddc74444a147eb1b8acc2942202a9bce98702483045022100a29d390e64e8710d3d6070df05037277a33cb1cba90d9d297c3ef3b6e26e0eff022045fd5180d572217758cf86d5a0b982188fe918b8cbe7a25d1018daa2b96600ce012102fcbe47219bae72b108e009ea81d588e0f43f43877d84ebc820db520bc43288a800000000","blockHeight":1249371,"date":1511921755,"fees":"100000","inputs":[{"txId":"e640cffde4b85dd16668d7c55130dca46b518a05ac8adf9b84511bf9d0be47ef","outputIndex":1,"n":0,"scriptPubkey":"a9147a3f5a973d0b1c1402e40507ca1b66afbf8a656087","amount":"188926316060"}],"outputs":[{"n":0,"scriptPubkey":"a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d87","amount":"65000000"},{"n":1,"scriptPubkey":"a9143224fc9ddc74444a147eb1b8acc2942202a9bce987","amount":"188861216060"}],"ourIns":[],"ourOuts":["0"],"ourAmount":"65000000"}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/99.json:
--------------------------------------------------------------------------------
1 | {"99701d23ae201fafeafeafa2120f048213f4cec6030208a35b4adab98f500bf0":{"txid":"99701d23ae201fafeafeafa2120f048213f4cec6030208a35b4adab98f500bf0","hex":"010000000001018bf6f8644202077235c5edf57b9e3f8a06c14677735ffac066ea18039afa14260100000017160014b7e04c29e9929f2f76ce9fa7d84bd51ef293ae56ffffffff0280a4bf070000000017a9142cee2a1dc30c73232c945eb39e389cec172178ed871983f4322c00000017a914fa2199dfe08b2a12ff9741d0f2b57afb8852a75487024830450221009bcae8a93481b0285a4d7cf316e2e4265b6aeba8511c889b4295035db923305b0220021f932ce7ea5180020f8e6d6eb778c6774ad4bdfec9a1e7e26a039a496b362301210226f468993478ebfafe505de64031f79e417d69a9bf3aa8255c0893ca0507265800000000","blockHeight":1249371,"date":1511921755,"fees":"100000","inputs":[{"txId":"2614fa9a0318ea66c0fa5f737746c1068a3f9e7bf5edc5357207024264f8f68b","outputIndex":1,"n":0,"scriptPubkey":"a914ee92fd08a6ff8857dc5284e5c583b505413aec6a87","amount":"189963546169"}],"outputs":[{"n":0,"scriptPubkey":"a9142cee2a1dc30c73232c945eb39e389cec172178ed87","amount":"130000000"},{"n":1,"scriptPubkey":"a914fa2199dfe08b2a12ff9741d0f2b57afb8852a75487","amount":"189833446169"}],"ourIns":[],"ourOuts":["0"],"ourAmount":"130000000"}}
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/e1.json:
--------------------------------------------------------------------------------
1 | {"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3":{"txid":"e1d703801f2f64400cc98d9a2e7b6a4bcb304ff9e718685b9932c0d46cd124b3","hex":"0100000000010182271c18483704b162f0b90c14ec92c2646f161be0329b58cc2a7934fb5a8616010000001716001486372e3239089b15fd1dc5cb3a3d378d9bebc0a9ffffffff0280a4bf070000000017a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d17876ef5c1162c00000017a91400c9e5c10d226995dd5dc56fcefed5b01b2af73787024730440220782d8f646d0d5d31733d8e2b544307cd7ba8add919f0bca4d754e4943ca9b9d002202e66ed1575230b84a1932b5d6ca2ef2c49637ad5bf3e06f4e8ed220ee5505248012102b02a055c2137466026407f51b45be77e6e0e1b40422bd13bb7bd37e99b04c60f00000000","blockHeight":1250624,"date":1512020289,"fees":"100000","inputs":[{"txId":"16865afb34792acc589b32e01b166f64c292ec140cb9f062b1043748181c2782","outputIndex":1,"n":0,"scriptPubkey":"a9148210e70aa4b27ce1a7ee73c971585cbe7daec22187","amount":"189490471054"}],"outputs":[{"n":0,"scriptPubkey":"a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d1787","amount":"130000000"},{"n":1,"scriptPubkey":"a91400c9e5c10d226995dd5dc56fcefed5b01b2af73787","amount":"189360371054"}],"ourIns":[],"ourOuts":["0"],"ourAmount":"130000000"}}
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/types.ts:
--------------------------------------------------------------------------------
1 | import { Cleaner } from 'cleaners'
2 | import { EdgeTokenId, InsufficientFundsError } from 'edge-core-js/types'
3 |
4 | interface InsufficientFundsErrorOptsPlus {
5 | // The currency we need more of:
6 | tokenId: EdgeTokenId
7 | // If we don't have enough funds for a token send:
8 | networkFee?: string
9 |
10 | networkFeeShortage?: string
11 | }
12 | export class InsufficientFundsErrorPlus extends InsufficientFundsError {
13 | networkFeeShortage?: string
14 | constructor(opts: InsufficientFundsErrorOptsPlus) {
15 | super(opts)
16 | const { networkFeeShortage } = opts
17 | this.networkFeeShortage = networkFeeShortage
18 | }
19 | }
20 |
21 | function asMaybeError(name: string): Cleaner {
22 | return function asError(raw) {
23 | if (raw instanceof Error && raw.name === name) {
24 | const typeHack: any = raw
25 | return typeHack
26 | }
27 | }
28 | }
29 |
30 | export const asMaybeInsufficientFundsErrorPlus = asMaybeError(
31 | // Share the same name because this error type is a subtype of
32 | // InsufficientFundsError and therefore backwards compatible
33 | 'InsufficientFundsError'
34 | )
35 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.6.0'
9 | }
10 | }
11 |
12 | apply plugin: 'com.android.library'
13 |
14 | def safeExtGet(prop, fallback) {
15 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
16 | }
17 |
18 | def DEFAULT_COMPILE_SDK_VERSION = 28
19 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.2'
20 | def DEFAULT_MIN_SDK_VERSION = 19
21 | def DEFAULT_TARGET_SDK_VERSION = 27
22 |
23 | android {
24 | namespace = "app.edge.reactnative.currencyplugins"
25 |
26 | compileSdkVersion = safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
27 | buildToolsVersion = safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
28 |
29 | defaultConfig {
30 | minSdkVersion = safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
31 | targetSdkVersion = safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
32 | versionCode = 1
33 | versionName = '1.0'
34 | }
35 | lintOptions {
36 | abortOnError = false
37 | }
38 | }
39 |
40 | repositories {
41 | }
42 |
43 | dependencies {
44 | implementation 'com.facebook.react:react-native:+'
45 | }
46 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/txById/c5.json:
--------------------------------------------------------------------------------
1 | {"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1":{"txid":"c5b4b48406155c5cbddcb0be3ffcfb42dc1b698856913bd79cd0f154fb05c8a1","hex":"01000000000101f00b508fb9da4a5ba3080203c6cef41382040f12a2affeeaaf1f20ae231d709900000000171600143825dda06b736a302f2b5fbefb464ffa1100c8f0ffffffff03503403000000000017a9141cec736050250dbfaff4b4e1819af0907322143f87a0680600000000001600141729951afbbfbe44c227201fcf1b79dc7dc4874b50c5a6070000000017a914bf76ff07ad44824990e72be8d85aa102b8cedc128702483045022100f68fce50188407e1f99825d560376e2e7cecc425aef5a90588e6016bde1c6b1b02207346894645e072b9624c11ae8804ef734e73b0890e3c737792547dfeb7e31a2601210340117bc2cc9a38e179d9e5759fe3c64ef25c1a33db8c9daadfbfb2c40c7f303700000000","blockHeight":1250624,"date":1512020289,"fees":"1000000","inputs":[{"txId":"99701d23ae201fafeafeafa2120f048213f4cec6030208a35b4adab98f500bf0","outputIndex":-1,"n":0,"scriptPubkey":"a9142cee2a1dc30c73232c945eb39e389cec172178ed87","amount":"130000000"}],"outputs":[{"n":0,"scriptPubkey":"a9141cec736050250dbfaff4b4e1819af0907322143f87","amount":"210000"},{"n":1,"scriptPubkey":"00141729951afbbfbe44c227201fcf1b79dc7dc4874b","amount":"420000"},{"n":2,"scriptPubkey":"a914bf76ff07ad44824990e72be8d85aa102b8cedc1287","amount":"128370000"}],"ourIns":["0"],"ourOuts":["2"],"ourAmount":"-1630000"}}
--------------------------------------------------------------------------------
/src/common/utxobased/info/utxoPickers/dogeUtxoPicker.ts:
--------------------------------------------------------------------------------
1 | import {
2 | UtxoPicker,
3 | UtxoPickerArgs,
4 | UtxoPickerResult
5 | } from '../../keymanager/utxopicker'
6 | import { accumulative } from '../../keymanager/utxopicker/accumulative'
7 | import { forceUseUtxo } from '../../keymanager/utxopicker/forceUseUtxo'
8 | import { subtractFee } from '../../keymanager/utxopicker/subtractFee'
9 |
10 | export function makeDogeUtxoPicker(): UtxoPicker {
11 | /*
12 | According to https://github.com/dogecoin/dogecoin/blob/master/doc/fee-recommendation.md
13 | the min fee rate is now 0.01 DOGE/kB which is 0.00001 DOGE/byte or
14 | 1,000 sats/vByte .
15 | */
16 | const minRelayFeeRate = 1000
17 |
18 | return {
19 | forceUseUtxo: (args: UtxoPickerArgs): UtxoPickerResult => {
20 | if (args.feeRate < minRelayFeeRate) {
21 | args.feeRate = minRelayFeeRate
22 | }
23 | return forceUseUtxo(args)
24 | },
25 | subtractFee: (args: UtxoPickerArgs): UtxoPickerResult => {
26 | if (args.feeRate < minRelayFeeRate) {
27 | args.feeRate = minRelayFeeRate
28 | }
29 | return subtractFee(args)
30 | },
31 | accumulative: (args: UtxoPickerArgs): UtxoPickerResult => {
32 | if (args.feeRate < minRelayFeeRate) {
33 | args.feeRate = minRelayFeeRate
34 | }
35 | return accumulative(args)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/bip69.ts:
--------------------------------------------------------------------------------
1 | /***
2 | * https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
3 | */
4 |
5 | import { Input, Output } from './utxopicker'
6 |
7 | /***
8 | * Previous transaction hashes (in reversed byte-order) are to be sorted in ascending order, lexicographically.
9 | * In the event of two matching transaction hashes,
10 | * the respective previous output indices will be compared by their integer value, in ascending order.
11 | * If the previous output indices match, the inputs are considered equal.
12 | */
13 | export const sortInputs = (inputs: Input[]): Input[] =>
14 | [...inputs].sort((a, b) => {
15 | const aHash = Buffer.from(a.hash).reverse()
16 | const bHash = Buffer.from(b.hash).reverse()
17 | const compare = aHash.compare(bHash)
18 | return compare !== 0 ? compare : a.index - b.index
19 | })
20 |
21 | /***
22 | * Transaction output amounts (as 64-bit unsigned integers) are to be sorted in ascending order.
23 | * In the event of two matching output amounts,
24 | * the respective output scriptPubKeys (as a byte-array) will be compared lexicographically, in ascending order.
25 | * If the scriptPubKeys match, the outputs are considered equal.
26 | * If the previous output indices match, the inputs are considered equal.
27 | */
28 | export const sortOutputs = (outputs: Output[]): Output[] =>
29 | [...outputs].sort((a, b) =>
30 | a.value === b.value ? a.script.compare(b.script) : a.value - b.value
31 | )
32 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/scriptPubkeyByPath/bip49_0/0.json:
--------------------------------------------------------------------------------
1 | ["a9142cee2a1dc30c73232c945eb39e389cec172178ed87","a91438d5c9084ddc8e7c6abb34751fb24ce84738c39d87","a9143e5c2d1ac6394bed008d5723a74d7a49c6d7b6e287","a9142244ce86d664e85801f7eb2a56dd35afd268212587","a914af3366b24f9b542742b7f99e1adeab4faa7d58a087","a914a1bbc3898f44d673e445c44db978811e3bd3bfe287","a9142c93bdc7b9ff33aac4574a66087e8a5c9962ab6a87","a914cb3cbcd22f416bf9cb703b14f28ef2ba155c4da887","a914a74131941b19acb351e93d582be11bd21905168d87","a914b01a433772f0b32e45397ab3335ac33edb8b6b4387","a914989dadf417d6c1e25878f5552153f146932f40ee87","a914da9622ec02c0899861f3c4c3b860411260e84a2787","a914bb1d6b2628fca0b13a0983f35161eaa1951e386987","a9143d823b2f44104cae843c1355b25bcfe8bd4c317d87","a914d7fa9a636b3ec37e4264144dacb66f546357b0c887","a914ef46f6fd8c9aad8456576deffecf2755c6acb39d87","a914b2506341c9ff8111ab8b1e81fabf7fba3569879f87","a914d808f1819fe39b497b40f5298e92110e8ef063e987","a9145c7cb6419a5be938c5a087288256d4dcd1c89d5687","a91457095572458c84a4d77fef2f9c373d483958c9d387","a91428b6b5eab84ae05bccf801a1ea72a0266525ea7887","a91453b998215518395c781cf4500fbdcc8c90ea352187","a914921078cc11de8f6ac1563350d61901efbaa709fa87","a914649768982cc96d1d210dfe6ecf38517680d6b6dd87","a914d23bb3234ccbc4f9873b203c8b5d32bcadcfa4af87","a914a2296268c9aa1af6fbb5857da753e0d99a7c4e1587","a9148393c44c64136faf7a53945cf3157ec7b180d60087","a914c218708e294309e4e1b887ef3af9883f879553d187","a9144452b33291fd22a2274758eebd14650a35ba1f1587"]
--------------------------------------------------------------------------------
/src/common/fees/processMempoolSpaceFees.ts:
--------------------------------------------------------------------------------
1 | import * as bs from 'biggystring'
2 | import { asNumber, asObject } from 'cleaners'
3 |
4 | import { LOW_FEE } from '../constants'
5 | import { FeeRates } from '../plugin/types'
6 |
7 | export const asMempoolSpaceFees = asObject({
8 | fastestFee: asNumber,
9 | halfHourFee: asNumber,
10 | hourFee: asNumber
11 | })
12 |
13 | /**
14 | * Calculates the FeeRates object from MempoolSpace
15 | * Sets the minimum of LOW_FEE on all fee levels
16 | * @param fees
17 | * @returns Partial
18 | */
19 |
20 | const STANDARD_FEE_HIGH_MULTIPLIER = 1.3
21 | const HIGH_FEE_MULTIPLIER = 2
22 |
23 | export const processMempoolSpaceFees = (fees: unknown): FeeRates | null => {
24 | let mempoolFees: ReturnType
25 | try {
26 | mempoolFees = asMempoolSpaceFees(fees)
27 | } catch {
28 | return null
29 | }
30 |
31 | const { fastestFee, halfHourFee } = mempoolFees
32 |
33 | const lowFee = halfHourFee.toString()
34 | const standardFeeLow = fastestFee.toString()
35 | const standardFeeHigh = Math.round(
36 | fastestFee * STANDARD_FEE_HIGH_MULTIPLIER
37 | ).toString()
38 | const highFee = Math.round(fastestFee * HIGH_FEE_MULTIPLIER).toString()
39 |
40 | return {
41 | lowFee: bs.max(lowFee, LOW_FEE.toString()),
42 | standardFeeLow: bs.max(standardFeeLow, LOW_FEE.toString()),
43 | standardFeeHigh: bs.max(standardFeeHigh, LOW_FEE.toString()),
44 | highFee: bs.max(highFee, LOW_FEE.toString())
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/windowWS.ts:
--------------------------------------------------------------------------------
1 | import { InnerSocket, InnerSocketCallbacks, ReadyState } from './types'
2 |
3 | export function setupBrowser(
4 | uri: string,
5 | callbacks: InnerSocketCallbacks
6 | ): InnerSocket {
7 | if (window.WebSocket == null)
8 | throw Error('Native browser WebSocket does not exists')
9 |
10 | const socket = new window.WebSocket(uri)
11 | socket.onopen = () => {
12 | callbacks.onOpen()
13 | }
14 | socket.onmessage = (message: MessageEvent) => {
15 | callbacks.onMessage(message.data.toString())
16 | }
17 | socket.onerror = event => {
18 | callbacks.onError(event)
19 | }
20 | socket.onclose = event => {
21 | callbacks.onClose(event.code, event.reason)
22 | }
23 |
24 | return {
25 | get readyState(): ReadyState {
26 | switch (socket.readyState) {
27 | case WebSocket.CONNECTING:
28 | return ReadyState.CONNECTING
29 | case WebSocket.OPEN:
30 | return ReadyState.OPEN
31 | case WebSocket.CLOSING:
32 | return ReadyState.CLOSING
33 | case WebSocket.CLOSED:
34 | default:
35 | return ReadyState.CLOSED
36 | }
37 | },
38 |
39 | disconnect() {
40 | if (
41 | socket == null ||
42 | socket.readyState === WebSocket.CLOSING ||
43 | socket.readyState === WebSocket.CLOSED
44 | )
45 | return
46 |
47 | socket.close()
48 | },
49 |
50 | send(data: string) {
51 | socket.send(data)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/common/upgradeMemos.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCurrencyInfo, EdgeMemo, EdgeSpendInfo } from 'edge-core-js/types'
2 |
3 | import { validateMemos } from './validateMemos'
4 |
5 | /**
6 | * Upgrades the memo fields inside an EdgeSpendTarget,
7 | * since we need to be runtime-compatible with legacy core versions.
8 | */
9 | export function upgradeMemos(
10 | spendInfo: EdgeSpendInfo,
11 | currencyInfo: EdgeCurrencyInfo
12 | ): EdgeSpendInfo {
13 | const { memoType } = currencyInfo
14 |
15 | const legacyMemos: EdgeMemo[] = []
16 |
17 | // If this chain supports legacy memos, grab those:
18 | if (memoType === 'hex' || memoType === 'number' || memoType === 'text') {
19 | for (const target of spendInfo.spendTargets) {
20 | if (target.memo != null) {
21 | legacyMemos.push({
22 | type: memoType,
23 | value: target.memo
24 | })
25 | } else if (target.uniqueIdentifier != null) {
26 | legacyMemos.push({
27 | type: memoType,
28 | value: target.uniqueIdentifier
29 | })
30 | } else if (typeof target.otherParams?.uniqueIdentifier === 'string') {
31 | legacyMemos.push({
32 | type: memoType,
33 | value: target.otherParams.uniqueIdentifier
34 | })
35 | }
36 | }
37 | }
38 |
39 | // If we don't have modern memos, use the legacy ones:
40 | const out: EdgeSpendInfo = {
41 | ...spendInfo,
42 | memos: spendInfo.memos ?? legacyMemos
43 | }
44 |
45 | validateMemos(out, currencyInfo)
46 | return out
47 | }
48 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/nodejsWS.ts:
--------------------------------------------------------------------------------
1 | import WS from 'ws'
2 |
3 | import { InnerSocket, InnerSocketCallbacks, ReadyState } from './types'
4 |
5 | export function setupWS(
6 | uri: string,
7 | callbacks: InnerSocketCallbacks
8 | ): InnerSocket {
9 | const ws = new WS(uri, {
10 | headers: {
11 | 'User-Agent': 'NodeJS-WS-agent'
12 | }
13 | })
14 |
15 | ws.on('open', () => {
16 | callbacks.onOpen()
17 | })
18 | ws.on('message', (data: WS.Data) => {
19 | // eslint-disable-next-line @typescript-eslint/no-base-to-string
20 | callbacks.onMessage(data.toString())
21 | })
22 | ws.on('error', error => {
23 | callbacks.onError(error)
24 | })
25 | ws.on('close', (code: number, reason: string) => {
26 | callbacks.onClose(code, reason)
27 | })
28 |
29 | return {
30 | get readyState(): ReadyState {
31 | switch (ws.readyState) {
32 | case WS.CONNECTING:
33 | return ReadyState.CONNECTING
34 | case WS.OPEN:
35 | return ReadyState.OPEN
36 | case WS.CLOSING:
37 | return ReadyState.CLOSING
38 | case WS.CLOSED:
39 | default:
40 | return ReadyState.CLOSED
41 | }
42 | },
43 |
44 | disconnect(): void {
45 | if (
46 | ws == null ||
47 | ws.readyState === WS.CLOSING ||
48 | ws.readyState === WS.CLOSED
49 | )
50 | return
51 | ws.removeAllListeners()
52 | ws.close()
53 | },
54 |
55 | send(data: string): void {
56 | ws.send(data)
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/common/utxobased/network/socketQueue.ts:
--------------------------------------------------------------------------------
1 | const QUEUE_JOBS_PER_RUN = 3
2 | const QUEUE_RUN_DELAY = 1000
3 |
4 | interface UpdateQueue {
5 | id: string
6 | action?: string
7 | updateFunc: () => void
8 | }
9 |
10 | const updateQueue: UpdateQueue[] = []
11 | let timeOut: ReturnType
12 |
13 | export function pushUpdate(update: UpdateQueue): void {
14 | const shouldRun = updateQueue.length === 0
15 | let didUpdate = false
16 | for (const u of updateQueue) {
17 | if (u.id === update.id && u.action === update.action) {
18 | u.updateFunc = update.updateFunc
19 | didUpdate = true
20 | break
21 | }
22 | }
23 | if (!didUpdate) {
24 | updateQueue.push(update)
25 | }
26 | if (shouldRun) {
27 | startQueue()
28 | }
29 | }
30 |
31 | export function removeIdFromQueue(id: string): void {
32 | for (let i = 0; i < updateQueue.length; i++) {
33 | const update = updateQueue[i]
34 | if (id === update.id) {
35 | updateQueue.splice(i, 1)
36 | break
37 | }
38 | }
39 | if (updateQueue.length === 0) {
40 | clearTimeout(timeOut)
41 | }
42 | }
43 |
44 | function startQueue(): void {
45 | const numJobs =
46 | QUEUE_JOBS_PER_RUN < updateQueue.length
47 | ? QUEUE_JOBS_PER_RUN
48 | : updateQueue.length
49 | for (let i = 0; i < numJobs; i++) {
50 | if (updateQueue.length > 0) {
51 | const u = updateQueue.shift()
52 | u?.updateFunc()
53 | }
54 | }
55 | if (updateQueue.length > 0) {
56 | timeOut = setTimeout(() => {
57 | startQueue()
58 | }, QUEUE_RUN_DELAY)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/utxopicker/accumulative.ts:
--------------------------------------------------------------------------------
1 | import { Output, UTXO, UtxoPickerArgs, UtxoPickerResult } from './types'
2 | import * as utils from './utils'
3 | // add inputs until we reach or surpass the target value (or deplete)
4 | // worst-case: O(n)
5 |
6 | export function accumulative(args: UtxoPickerArgs): UtxoPickerResult {
7 | const { utxos, targets, feeRate, changeScript } = args
8 |
9 | if (!isFinite(utils.uintOrNaN(feeRate))) {
10 | throw new Error('No rate provided')
11 | }
12 |
13 | const outputs: Output[] = targets.map(target => ({
14 | ...target,
15 | script: Buffer.from(target.script, 'hex'),
16 | scriptPubkey: Buffer.from(target.script, 'hex')
17 | }))
18 |
19 | let inValue = 0
20 | const inputs: UTXO[] = []
21 | const targetValue = utils.sumOrNaN(targets)
22 |
23 | for (let i = 0; i < utxos.length; ++i) {
24 | const utxo = utxos[i]
25 |
26 | // skip detrimental input
27 | const utxoFee = feeRate * utils.inputBytes(utxo)
28 | if (utxoFee > utxo.value) {
29 | if (i === utxos.length - 1) {
30 | break
31 | } else {
32 | continue
33 | }
34 | }
35 |
36 | inputs.push(utxo)
37 | inValue += utxo.value
38 |
39 | const bytes = utils.transactionBytes(inputs, outputs)
40 | const fee = bytes * feeRate
41 |
42 | // go again?
43 | if (inValue < targetValue + fee) continue
44 |
45 | return utils.finalize(inputs, outputs, feeRate, changeScript)
46 | }
47 |
48 | return {
49 | changeUsed: false,
50 | fee: Math.ceil(feeRate * utils.transactionBytes(inputs, outputs)),
51 | inputs
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/common/plugin/Metadata.spec.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import chaiAsPromised from 'chai-as-promised'
3 | import { Disklet, makeMemoryDisklet } from 'disklet'
4 | import { MemoryStorage } from 'disklet/lib/src/backends/memory'
5 | import { EdgeLog } from 'edge-core-js/types'
6 |
7 | import {
8 | EngineEmitter,
9 | EngineEvent
10 | } from '../../../src/common/plugin/EngineEmitter'
11 | import { makeMetadata, Metadata } from '../../../src/common/plugin/Metadata'
12 | import { makeFakeLog } from '../../utils'
13 |
14 | chai.should()
15 | chai.use(chaiAsPromised)
16 |
17 | const wait = async (seconds: number): Promise =>
18 | await new Promise(resolve => setTimeout(resolve, seconds * 1000))
19 |
20 | describe('makeMetadata', () => {
21 | const memory: MemoryStorage = {}
22 | let disklet: Disklet
23 | let metadata: Metadata
24 | const log: EdgeLog = makeFakeLog()
25 | const emitter = new EngineEmitter()
26 |
27 | before(async () => {
28 | disklet = makeMemoryDisklet(memory)
29 | metadata = await makeMetadata({
30 | disklet,
31 | emitter,
32 | log
33 | })
34 | })
35 |
36 | describe('block height update', () => {
37 | it('should only ever increase the block height', async function () {
38 | this.timeout(3000)
39 |
40 | metadata.state.lastSeenBlockHeight.should.eql(0)
41 |
42 | emitter.emit(EngineEvent.BLOCK_HEIGHT_CHANGED, '', 10)
43 | await wait(1)
44 | metadata.state.lastSeenBlockHeight.should.eql(10)
45 |
46 | emitter.emit(EngineEvent.BLOCK_HEIGHT_CHANGED, '', 5)
47 | await wait(1)
48 | metadata.state.lastSeenBlockHeight.should.eql(10)
49 | })
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/common/plugin/CurrencyPlugin.fixtures/common.ts:
--------------------------------------------------------------------------------
1 | import { EdgeEncodeUri, EdgeWalletInfo } from 'edge-core-js/types'
2 |
3 | import {
4 | EncodeUriMetadata,
5 | ExtendedParseUri
6 | } from '../../../../src/common/plugin/types'
7 |
8 | export interface FixtureType {
9 | pluginId: string
10 | WALLET_TYPE: string
11 | WALLET_FORMAT: string
12 | 'Test Currency code': string
13 | key: number[]
14 | xpub: string
15 | 'invalid key name': EdgeWalletInfo
16 | 'invalid wallet type': EdgeWalletInfo
17 | importKey: {
18 | validKeys: string[]
19 | invalidKeys: string[]
20 | unsupportedKeys: string[]
21 | }
22 | parseUri: {
23 | [testName: string]: [string, ExtendedParseUri] | [string]
24 | }
25 | encodeUri: {
26 | [testName: string]:
27 | | [EdgeEncodeUri & EncodeUriMetadata, string]
28 | | [EdgeEncodeUri & EncodeUriMetadata]
29 | }
30 | getSplittableTypes?: {
31 | [walletFormat: string]: string[]
32 | }
33 | }
34 |
35 | // The mnemonic and key represent the same private key
36 | const mnemonic =
37 | 'chicken valve parrot park animal proof youth detail glance review artwork cluster drive more charge lunar uncle neglect brain act rose job photo spot'
38 | export const key = [
39 | 39,
40 | 190,
41 | 34,
42 | 129,
43 | 208,
44 | 32,
45 | 145,
46 | 88,
47 | 191,
48 | 217,
49 | 226,
50 | 98,
51 | 183,
52 | 16,
53 | 52,
54 | 150,
55 | 52,
56 | 53,
57 | 31,
58 | 137,
59 | 164,
60 | 40,
61 | 236,
62 | 146,
63 | 128,
64 | 107,
65 | 129,
66 | 59,
67 | 192,
68 | 240,
69 | 40,
70 | 238
71 | ]
72 | export const mnemonics = [mnemonic]
73 | export const airbitzSeeds = ['AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=']
74 |
--------------------------------------------------------------------------------
/test/common/utxobased/engine/engine.fixtures/bitcoinTestnet/dummyData/tables/scriptPubkeyByPath/bip49_1/0.json:
--------------------------------------------------------------------------------
1 | ["a9141cf9c7ff6ef78b78e47765922f824f0153e987d787","a9141f74859dcf06afa47e15c8ece5277b60bbc5b04387","a914e04d6d45c7437509f2b6cedeb6315078a0b4608487","a914bf76ff07ad44824990e72be8d85aa102b8cedc1287","a9144a4b87abba761724f7a5a8c0fba78d9544c012e787","a9147cffcf53d21c7b405e3089bacfbf830d3c012c6887","a9141d646ffdcd9ab40c5d6a31710b0c1cbdda2aeda287","a91430bd80f8b0beb1c30738d57090ce188746a298db87","a914bbeecf1b89b5bb7adfcc02feca57416c14b2b24f87","a9147a38669dfc5147ca79d4fea0b2b78c3838ce4d1787","a9141c48c5335c2e78d99051b797d6d43dabbbf41a6c87","a9141380c031262e59b42a284e87da11a4802eaf8e1987","a914347defe6f22eaeef9f309f47a3aaad74373e4bab87","a914ae7a57b4a6568360f5126d278e0302f0207fdf0987","a914de23511e8b5f37f6e9f4a2d0fe0c294fdbd5c67387","a914b9862104c52909a56811e18a10c7d3122223822887","a9148f70336fdc8416664422443bece3dda7ff6eecf987","a9144d4cbdad294022e5226a51d8db42c426e5a1b81c87","a914c4566ce9f721884d727d51762373af79d611875a87","a914ae39a8e6ab12259293912b90f4c411a3e507d55787","a91419b947c203be2b81fbf2df5a839a04a5a3d4e4c787","a914a857a42d3310ca1df556f3c66fdfa4b43d22dd2a87","a9142b6a43856d28276b4ece62dc215875d32efb701687","a91445bb77fc8d98d5b9d443542f808f36d6f183f3d487","a9142c81d63debd886083fa857191714af7c57e5443787","a914359e463b19e95b37a0f4e77e8dc32fa303084ab987","a914c0066116b189508480021f788b6f75c0bb06fe6087","a9149e5875c02a692b28ef04bd95b993ae39533200af87","a9149a3a1373ea803daea029b0ada97208313a1157a487","a9144a1a8338554264c4bd7cec75764a2e3a9c8d307687","a914d1be0b70ed4b76551f29791f5eaccc9fb4231e4187","a914763ce446a95abfb457eecd8f8f9078c80c9253ae87","a914008ac628bbbbf70237f0f45eb7c95f66a064f9cd87","a9141f0d1a2245d94ebdb201ef83873b5dcaa29d744587","a914b99bd84ffd5d4d48fcaa4d24566bd9a825463f8487"]
--------------------------------------------------------------------------------
/src/common/utxobased/engine/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | asArray,
3 | asBoolean,
4 | asMaybe,
5 | asNumber,
6 | asObject,
7 | asOptional,
8 | asString,
9 | asValue
10 | } from 'cleaners'
11 | import { EdgeSpendInfo } from 'edge-core-js/types'
12 |
13 | import { asTxOptions } from '../../plugin/types'
14 | import { Input, Output } from '../keymanager/utxopicker/types'
15 |
16 | export interface UtxoInitOptions {
17 | nowNodesApiKey?: string
18 | }
19 | export const asUtxoInitOptions = asObject({
20 | nowNodesApiKey: asOptional(asString)
21 | })
22 |
23 | export const asUtxoUserSettings = asObject({
24 | blockbookServers: asMaybe(asArray(asString), []),
25 | enableCustomServers: asMaybe(asBoolean, false)
26 | })
27 | export type UtxoUserSettings = ReturnType
28 |
29 | export interface UtxoTxOtherParams {
30 | unsignedTx: string // hex
31 | psbt?: {
32 | base64: string
33 | inputs: Input[]
34 | outputs: Output[]
35 | }
36 | edgeSpendInfo?: EdgeSpendInfo
37 | ourScriptPubkeys: string[]
38 | replacedTxid?: string
39 | }
40 |
41 | export type UtxoSignMessageOtherParams = ReturnType<
42 | typeof asUtxoSignMessageOtherParams
43 | >
44 | export const asUtxoSignMessageOtherParams = asObject({
45 | publicAddress: asString
46 | })
47 |
48 | const asOutputSort = asValue('bip69', 'targets')
49 |
50 | export type UtxoSpendInfoOtherParams = ReturnType<
51 | typeof asUtxoSpendInfoOtherParams
52 | >
53 | export const asUtxoSpendInfoOtherParams = asObject({
54 | /** @deprecated use `EdgeSpendInfo['enableRbf']` */
55 | enableRbf: asOptional(asBoolean),
56 | forceChangeAddress: asOptional(asString),
57 | memoIndex: asOptional(asNumber),
58 | outputSort: asOptional(asOutputSort, 'bip69'),
59 | txOptions: asOptional(asTxOptions),
60 | utxoSourceAddress: asOptional(asString)
61 | })
62 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/base.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | import bs58 from 'bs58'
4 | import { Buffer } from 'buffer'
5 |
6 | interface Base58BaseReturn {
7 | encode: (payload: Buffer) => string
8 | decode: (stringPayload: string | undefined) => Buffer
9 | decodeUnsafe: (stringPayload: string) => Buffer | undefined
10 | }
11 |
12 | export function base58Base(
13 | checksumFn: (buffer: Buffer) => Buffer
14 | ): Base58BaseReturn {
15 | // Encode a buffer as a base58-check encoded string
16 | function encode(payload: Buffer): string {
17 | const checksum: Buffer = checksumFn(payload)
18 |
19 | return bs58.encode(Buffer.concat([payload, checksum], payload.length + 4))
20 | }
21 |
22 | function decodeRaw(buffer: Buffer): Buffer | undefined {
23 | const payload = buffer.slice(0, -4)
24 | const checksum = buffer.slice(-4)
25 | const newChecksum = checksumFn(payload)
26 |
27 | if (
28 | ((checksum[0] ^ newChecksum[0]) |
29 | (checksum[1] ^ newChecksum[1]) |
30 | (checksum[2] ^ newChecksum[2]) |
31 | (checksum[3] ^ newChecksum[3])) ===
32 | 1
33 | )
34 | return
35 |
36 | return payload
37 | }
38 |
39 | // Decode a base58-check encoded string to a buffer, no result if checksum is wrong
40 | function decodeUnsafe(stringPayload: string): Buffer | undefined {
41 | const buffer = bs58.decodeUnsafe(stringPayload)
42 | if (buffer == null) return
43 |
44 | return decodeRaw(buffer)
45 | }
46 |
47 | function decode(payload: string | undefined): Buffer {
48 | if (payload == null) throw new Error('Invalid base58 string')
49 | const buffer = bs58.decode(payload)
50 | const bPayload = decodeRaw(buffer)
51 | if (bPayload == null) throw new Error('Invalid checksum')
52 | return bPayload
53 | }
54 |
55 | return {
56 | encode,
57 | decode,
58 | decodeUnsafe
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/common/fees/fees.spec.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import chaiAsPromised from 'chai-as-promised'
3 | import { makeMemoryDisklet } from 'disklet'
4 | import { MemoryStorage } from 'disklet/lib/src/backends/memory'
5 |
6 | import { FEES_PATH } from '../../../src/common/constants'
7 | import { Fees, makeFees } from '../../../src/common/fees/makeFees'
8 | import { FeeInfo } from '../../../src/common/plugin/types'
9 | import { makeFakeIo, makeFakeLog, makeFakePluginInfo } from '../../utils'
10 |
11 | chai.should()
12 | chai.use(chaiAsPromised)
13 | const { expect } = chai
14 |
15 | describe('fees', function () {
16 | const fakeIo = makeFakeIo()
17 | const fakeLog = makeFakeLog()
18 | const fakeMakePluginInfo = makeFakePluginInfo()
19 | const memory: MemoryStorage = {}
20 | const disklet = makeMemoryDisklet(memory)
21 | let fees: Fees
22 |
23 | const testJson = (expected?: FeeInfo): void => {
24 | const str = memory[`/${FEES_PATH}`]
25 | if (typeof str === 'string') {
26 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions
27 | expected == null
28 | ? expect(str).to.be.undefined
29 | : expect(JSON.parse(str)).to.eql(expected)
30 | }
31 | }
32 |
33 | describe('makeFees', () => {
34 | it('should load fees from the currency info file on wallet first load', async () => {
35 | fees = await makeFees({
36 | disklet,
37 | pluginInfo: fakeMakePluginInfo,
38 | io: fakeIo,
39 | log: fakeLog
40 | })
41 |
42 | fees.feeInfo.should.eql(fakeMakePluginInfo.engineInfo.defaultFeeInfo)
43 |
44 | testJson()
45 | })
46 |
47 | it('should cache fees after started', async () => {
48 | await fees.start()
49 |
50 | testJson(fakeMakePluginInfo.engineInfo.defaultFeeInfo)
51 |
52 | // be sure to stop after start, test will hang otherwise
53 | fees.stop()
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/src/common/utxobased/engine/await-lock.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A mutex lock for coordination across async functions
3 | */
4 | export default class AwaitLock {
5 | private _acquired = false
6 | private readonly _waitingResolvers: Array<() => void> = []
7 |
8 | /**
9 | * Whether the lock is currently acquired or not. Accessing this property does not affect the
10 | * status of the lock.
11 | */
12 | get acquired(): boolean {
13 | return this._acquired
14 | }
15 |
16 | /**
17 | * Acquires the lock, waiting if necessary for it to become free if it is already locked. The
18 | * returned promise is fulfilled once the lock is acquired.
19 | *
20 | * After acquiring the lock, you **must** call `release` when you are done with it.
21 | */
22 | async acquireAsync(): Promise {
23 | if (!this._acquired) {
24 | this._acquired = true
25 | return await Promise.resolve()
26 | }
27 |
28 | return await new Promise(resolve => {
29 | this._waitingResolvers.push(resolve)
30 | })
31 | }
32 |
33 | /**
34 | * Acquires the lock if it is free and otherwise returns immediately without waiting. Returns
35 | * `true` if the lock was free and is now acquired, and `false` otherwise,
36 | */
37 | tryAcquire(): boolean {
38 | if (!this._acquired) {
39 | this._acquired = true
40 | return true
41 | }
42 |
43 | return false
44 | }
45 |
46 | /**
47 | * Releases the lock and gives it to the next waiting acquirer, if there is one. Each acquirer
48 | * must release the lock exactly once.
49 | */
50 | release(): void {
51 | if (!this._acquired) {
52 | throw new Error(`Cannot release an unacquired lock`)
53 | }
54 |
55 | if (this._waitingResolvers.length > 0) {
56 | const resolve = this._waitingResolvers.shift()
57 | if (resolve == null) {
58 | throw new Error('Cannot release null lock')
59 | }
60 | resolve()
61 | } else {
62 | this._acquired = false
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/utxopicker/forceUseUtxo.ts:
--------------------------------------------------------------------------------
1 | import { Output, UTXO, UtxoPickerArgs, UtxoPickerResult } from './types'
2 | import * as utils from './utils'
3 | // implementation very similar to accumulative strategy
4 | // add inputs until we reach or surpass the target value (or deplete)
5 | // worst-case: O(n)
6 |
7 | export function forceUseUtxo(args: UtxoPickerArgs): UtxoPickerResult {
8 | const { utxos, useUtxos, targets, feeRate, changeScript } = args
9 |
10 | if (!isFinite(utils.uintOrNaN(feeRate))) {
11 | throw new Error('No rate provided')
12 | }
13 |
14 | const outputs: Output[] = targets.map(target => ({
15 | ...target,
16 | script: Buffer.from(target.script, 'hex'),
17 | scriptPubkey: Buffer.from(target.script, 'hex')
18 | }))
19 |
20 | const inputs: UTXO[] = useUtxos ?? []
21 | let inValue = inputs.reduce((n, { value }) => n + value, 0)
22 | const targetValue = utils.sumOrNaN(targets)
23 | const bytes = utils.transactionBytes(inputs, outputs)
24 | const fee = bytes * feeRate
25 | // if the new feeRate is already covered by lowering the change amount, return
26 | if (inValue >= targetValue + fee) {
27 | return utils.finalize(inputs, outputs, feeRate, changeScript)
28 | }
29 |
30 | for (let i = 0; i < utxos.length; ++i) {
31 | const utxo = utxos[i]
32 |
33 | // skip detrimental input
34 | const utxoFee = feeRate * utils.inputBytes(utxo)
35 | if (utxoFee > utxo.value) {
36 | if (i === utxos.length - 1) {
37 | break
38 | } else {
39 | continue
40 | }
41 | }
42 |
43 | inputs.push(utxo)
44 | inValue += utxo.value
45 |
46 | const bytes = utils.transactionBytes(inputs, outputs)
47 | const fee = bytes * feeRate
48 |
49 | // go again?
50 | if (inValue < targetValue + fee) continue
51 |
52 | return utils.finalize(inputs, outputs, feeRate, changeScript)
53 | }
54 |
55 | return {
56 | changeUsed: false,
57 | fee: Math.ceil(feeRate * utils.transactionBytes(inputs, outputs)),
58 | inputs
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/common/utxobased/info/scriptTemplates/bitcoincashScriptTemplates.ts:
--------------------------------------------------------------------------------
1 | import { OptionalScriptTemplateArgs, ScriptTemplates } from './types'
2 |
3 | const OP_CHECKDATASIGVERIFY = 'bb'
4 | const OP_CHECKDATASIG = 'ba'
5 | const OP_CHECKSIG = 'ac'
6 | const CDS_SIGNATURE =
7 | '30440220256c12175e809381f97637933ed6ab97737d263eaaebca6add21bced67fd12a402205ce29ecc1369d6fc1b51977ed38faaf41119e3be1d7edfafd7cfaf0b6061bd07'
8 | const CDS_MESSAGE = ''
9 | const CDS_PUBKEY =
10 | '038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508'
11 |
12 | const hexToVarByte = (hex: string): string => {
13 | const len = hex.length / 2
14 | const str = len.toString(16)
15 | const hexLen = str.length % 2 === 0 ? str : `0${str}`
16 | return hexLen + hex
17 | }
18 |
19 | const cds = (
20 | cdsSigs: string,
21 | cdsMsg: string,
22 | cdsPubKey: string,
23 | pubKey: string
24 | ): string[] => {
25 | const cdsSuffix = `${hexToVarByte(pubKey)}${OP_CHECKSIG}`
26 | const cdsPrefix = `${hexToVarByte(cdsSigs)}${hexToVarByte(
27 | cdsMsg
28 | )}${hexToVarByte(cdsPubKey)}`
29 | return [cdsPrefix, cdsSuffix]
30 | }
31 |
32 | export const scriptTemplates: ScriptTemplates = {
33 | replayProtection: (pubKey: string): string => {
34 | return cds(CDS_SIGNATURE, CDS_MESSAGE, CDS_PUBKEY, pubKey).join(
35 | OP_CHECKDATASIGVERIFY
36 | )
37 | },
38 | checkdatasig: (
39 | pubKey: string,
40 | optionalArgs?: OptionalScriptTemplateArgs
41 | ): string => {
42 | const cdsSig = optionalArgs?.cdsSig ?? ''
43 | const cdsMsg = optionalArgs?.cdsMsg ?? ''
44 | const cdsPubKey = optionalArgs?.cdsPubKey ?? ''
45 | return cds(cdsSig, cdsMsg, cdsPubKey, pubKey).join(OP_CHECKDATASIG)
46 | },
47 | checkdatasigverify: (
48 | pubKey: string,
49 | optionalArgs?: OptionalScriptTemplateArgs
50 | ): string => {
51 | const cdsSig = optionalArgs?.cdsSig ?? ''
52 | const cdsMsg = optionalArgs?.cdsMsg ?? ''
53 | const cdsPubKey = optionalArgs?.cdsPubKey ?? ''
54 | return cds(cdsSig, cdsMsg, cdsPubKey, pubKey).join(OP_CHECKDATASIGVERIFY)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/common/utxobased/keymanager/bitcoincashUtils/base32.ts:
--------------------------------------------------------------------------------
1 | /***
2 | * https://github.com/bitcoincashjs/cashaddr
3 | * Copyright (c) 2018 Matias Alejo Garcia
4 | * Copyright (c) 2017 Emilio Almansi
5 | * Distributed under the MIT software license, see the accompanying
6 | * file LICENSE or http://www.opensource.org/licenses/mit-license.php.
7 | */
8 |
9 | /***
10 | * Charset containing the 32 symbols used in the base32 encoding.
11 | */
12 | const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
13 |
14 | /***
15 | * Inverted index mapping each symbol into its index within the charset.
16 | */
17 |
18 | interface CHARSET_INVERSE_INDEX_TYPE {
19 | [value: string]: number
20 | }
21 |
22 | const CHARSET_INVERSE_INDEX: CHARSET_INVERSE_INDEX_TYPE = {
23 | q: 0,
24 | p: 1,
25 | z: 2,
26 | r: 3,
27 | y: 4,
28 | 9: 5,
29 | x: 6,
30 | 8: 7,
31 | g: 8,
32 | f: 9,
33 | 2: 10,
34 | t: 11,
35 | v: 12,
36 | d: 13,
37 | w: 14,
38 | 0: 15,
39 | s: 16,
40 | 3: 17,
41 | j: 18,
42 | n: 19,
43 | 5: 20,
44 | 4: 21,
45 | k: 22,
46 | h: 23,
47 | c: 24,
48 | e: 25,
49 | 6: 26,
50 | m: 27,
51 | u: 28,
52 | a: 29,
53 | 7: 30,
54 | l: 31
55 | }
56 |
57 | /***
58 | * Encodes the given array of 5-bit integers as a base32-encoded string.
59 | *
60 | * @param {Array} data Array of integers between 0 and 31 inclusive.
61 | */
62 | export function encode(data: number[]): string {
63 | let base32 = ''
64 | for (let i = 0; i < data.length; i++) {
65 | const value = data[i]
66 | if (value < 0 || value > 32) throw new Error(`InvalidArgument: ${value}`)
67 | base32 += CHARSET[value]
68 | }
69 | return base32
70 | }
71 |
72 | /***
73 | * Decodes the given base32-encoded string into an array of 5-bit integers.
74 | *
75 | * @param {string} base32
76 | */
77 | export function decode(base32: string): number[] {
78 | const data = []
79 | for (let i = 0; i < base32.length; i++) {
80 | const value = base32[i]
81 | if (!(value in CHARSET_INVERSE_INDEX)) {
82 | throw new Error(`InvalidArgument: ${value} not in CHARSET_INVERSE_INDEX`)
83 | }
84 | data.push(CHARSET_INVERSE_INDEX[value])
85 | }
86 | return data
87 | }
88 |
--------------------------------------------------------------------------------
/src/common/utxobased/db/types.ts:
--------------------------------------------------------------------------------
1 | import { asBoolean, asNumber, asObject, asOptional, asString } from 'cleaners'
2 |
3 | import { SoftPick } from '../../../util/typeUtil'
4 | import { AddressPath } from '../../plugin/types'
5 | import { asScriptTypeEnum, ScriptTypeEnum } from '../keymanager/keymanager'
6 |
7 | export interface AddressData {
8 | scriptPubkey: string
9 | redeemScript?: string
10 | lastQueriedBlockHeight: number
11 | path?: AddressPath
12 | lastQuery: number
13 | lastTouched: number
14 | used: boolean
15 | balance?: string
16 | }
17 |
18 | export const makeAddressData = (
19 | addressFields: SoftPick
20 | ): AddressData => {
21 | const { scriptPubkey, used = false, ...rest } = addressFields
22 |
23 | return {
24 | scriptPubkey,
25 | used,
26 | lastQueriedBlockHeight: 0,
27 | lastQuery: 0,
28 | lastTouched: 0,
29 | ...rest
30 | }
31 | }
32 |
33 | export interface UtxoData {
34 | id: string
35 | txid: string
36 | vout: number
37 | value: string
38 | scriptPubkey: string
39 | script: string
40 | redeemScript?: string
41 | scriptType: ScriptTypeEnum
42 | blockHeight: number
43 | spent: boolean
44 | }
45 | export const asUtxoData = asObject({
46 | id: asString,
47 | txid: asString,
48 | vout: asNumber,
49 | value: asString,
50 | scriptPubkey: asString,
51 | script: asString,
52 | redeemScript: asOptional(asString),
53 | scriptType: asScriptTypeEnum,
54 | blockHeight: asNumber,
55 | spent: asBoolean
56 | })
57 |
58 | export interface TransactionData {
59 | txid: string
60 | hex: string
61 | blockHeight: number
62 | confirmations?: 'confirmed' | 'unconfirmed' | 'dropped' | number
63 | date: number
64 | fees: string
65 | inputs: TransactionDataInput[]
66 | outputs: TransactionDataOutput[]
67 | ourIns: string[]
68 | ourOuts: string[]
69 | ourAmount: string
70 | }
71 |
72 | export interface TransactionDataOutput {
73 | amount: string
74 | n: number
75 | scriptPubkey: string
76 | }
77 |
78 | export interface TransactionDataInput {
79 | amount: string
80 | n: number
81 | outputIndex: number
82 | scriptPubkey: string
83 | sequence: number
84 | txId: string
85 | }
86 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent any
3 | tools {
4 | nodejs "stable"
5 | }
6 | options {
7 | timestamps()
8 | skipDefaultCheckout true
9 | overrideIndexTriggers false
10 | buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '7', numToKeepStr: '10')
11 | disableConcurrentBuilds()
12 | }
13 | triggers {
14 | pollSCM("H/5 * * * *")
15 | }
16 |
17 | stages {
18 | stage("Clean the workspace and checkout source") {
19 | steps {
20 | deleteDir()
21 | checkout scm
22 | }
23 | }
24 |
25 | stage ("Install Dependencies") {
26 | steps {
27 | sh "yarn"
28 | }
29 | }
30 |
31 | stage ("Check Lint") {
32 | steps {
33 | sh "yarn lint"
34 | }
35 | }
36 |
37 | stage ("Check Types") {
38 | steps {
39 | sh "yarn types"
40 | }
41 | }
42 |
43 | stage ("Test Package") {
44 | steps {
45 | sh "yarn coverage"
46 | }
47 | }
48 | }
49 |
50 | post {
51 | always {
52 | echo 'Setting the build version'
53 | script {
54 | def packageJson = readJSON file: "./package.json"
55 | currentBuild.description = "[version] ${packageJson.version}"
56 | }
57 | echo 'Trying to publish the test report'
58 | junit healthScaleFactor: 100.0, testResults: '**/coverage/junit.xml', allowEmptyResults: true
59 | echo 'Trying to publish the code coverage report'
60 | cobertura(
61 | coberturaReportFile: '**/coverage/cobertura-coverage.xml',
62 | failUnhealthy: false,
63 | failNoReports: false,
64 | failUnstable: false,
65 | onlyStable: false,
66 | zoomCoverageChart: false,
67 | conditionalCoverageTargets: '70, 0, 0',
68 | lineCoverageTargets: '70, 0, 0',
69 | methodCoverageTargets: '70, 0, 0',
70 | maxNumberOfBuilds: 0,
71 | sourceEncoding: 'ASCII'
72 | )
73 | }
74 | success {
75 | echo "The force is strong with this one"
76 | deleteDir()
77 | }
78 | unstable {
79 | echo "Do or do not there is no try"
80 | }
81 | failure {
82 | echo "The dark side I sense in you."
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/common/utxobased/db/util/utxo.ts:
--------------------------------------------------------------------------------
1 | import { currencyFormatToPurposeType } from '../../engine/utils'
2 | import {
3 | BIP43PurposeTypeEnum,
4 | ScriptTypeEnum
5 | } from '../../keymanager/keymanager'
6 | import { DataLayer } from '../DataLayer'
7 | import { TransactionData, UtxoData } from '../types'
8 |
9 | export const utxoFromTransactionDataInput = async (
10 | dataLayer: DataLayer,
11 | transactionData: TransactionData,
12 | inputIndex: number
13 | ): Promise => {
14 | const input = transactionData.inputs[inputIndex]
15 | const address = await dataLayer.fetchAddress(input.scriptPubkey)
16 |
17 | if (address == null)
18 | throw new Error(`Cannot find address for ${input.scriptPubkey}`)
19 | if (address.path == null)
20 | throw new Error(`Address has no derivation path information`)
21 |
22 | // Optional redeemScript for "our addresses"
23 | const redeemScript = address.redeemScript
24 |
25 | const purposeType = currencyFormatToPurposeType(address.path.format)
26 | const getScripts = async (): Promise<{
27 | script: string
28 | scriptType: ScriptTypeEnum
29 | }> => {
30 | switch (purposeType) {
31 | case BIP43PurposeTypeEnum.Airbitz:
32 | case BIP43PurposeTypeEnum.Legacy: {
33 | return {
34 | script: transactionData.hex,
35 | scriptType:
36 | redeemScript != null ? ScriptTypeEnum.p2sh : ScriptTypeEnum.p2pkh
37 | }
38 | }
39 | case BIP43PurposeTypeEnum.WrappedSegwit:
40 | return {
41 | script: input.scriptPubkey,
42 | scriptType: ScriptTypeEnum.p2wpkhp2sh
43 | }
44 | case BIP43PurposeTypeEnum.Segwit:
45 | return {
46 | script: input.scriptPubkey,
47 | scriptType: ScriptTypeEnum.p2wpkh
48 | }
49 | default:
50 | throw new Error(`Unknown purpose type ${purposeType}`)
51 | }
52 | }
53 | const { script, scriptType } = await getScripts()
54 |
55 | return {
56 | id: `${input.txId}_${input.outputIndex}`,
57 | txid: input.txId,
58 | vout: input.outputIndex,
59 | value: input.amount,
60 | scriptPubkey: input.scriptPubkey,
61 | script,
62 | redeemScript,
63 | scriptType,
64 | blockHeight: transactionData.blockHeight,
65 | spent: true
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/common/utxobased/info/badcoin.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCurrencyInfo } from 'edge-core-js/types'
2 |
3 | import { CoinInfo, EngineInfo, PluginInfo } from '../../plugin/types'
4 | import {
5 | legacyMemoInfo,
6 | utxoCustomFeeTemplate,
7 | utxoMemoOptions
8 | } from './commonInfo'
9 |
10 | const currencyInfo: EdgeCurrencyInfo = {
11 | assetDisplayName: 'Badcoin',
12 | chainDisplayName: 'Badcoin',
13 | currencyCode: 'BAD',
14 | customFeeTemplate: utxoCustomFeeTemplate,
15 | memoOptions: utxoMemoOptions,
16 | pluginId: 'badcoin',
17 | walletType: 'wallet:badcoin',
18 |
19 | // Explorers:
20 | addressExplorer: 'https://www.blockingbad.com/address/%s',
21 | blockExplorer: 'https://www.blockingbad.com/block/%s',
22 | transactionExplorer: 'https://www.blockingbad.com/tx/%s',
23 |
24 | denominations: [
25 | { name: 'BAD', multiplier: '100000000', symbol: 'BAD' },
26 | { name: 'mBAD', multiplier: '100000', symbol: 'mBAD' }
27 | ],
28 |
29 | // Deprecated:
30 | ...legacyMemoInfo,
31 | defaultSettings: {
32 | customFeeSettings: ['satPerByte'],
33 | blockbookServers: [],
34 | enableCustomServers: false
35 | },
36 | displayName: 'Badcoin',
37 | metaTokens: []
38 | }
39 |
40 | const engineInfo: EngineInfo = {
41 | formats: ['bip49', 'bip44', 'bip32'],
42 | gapLimit: 10,
43 | feeUpdateInterval: 60000,
44 | defaultFeeInfo: {
45 | lowFeeFudgeFactor: undefined,
46 | standardFeeLowFudgeFactor: undefined,
47 | standardFeeHighFudgeFactor: undefined,
48 | highFeeFudgeFactor: undefined,
49 |
50 | highFee: '300',
51 | lowFee: '100',
52 | standardFeeLow: '150',
53 | standardFeeHigh: '200',
54 | standardFeeLowAmount: '20000000',
55 | standardFeeHighAmount: '981000000'
56 | }
57 | }
58 |
59 | export const coinInfo: CoinInfo = {
60 | name: 'badcoin',
61 | segwit: false,
62 | coinType: 324,
63 | prefixes: {
64 | messagePrefix: [
65 | '\x18Badcoin Signed Message:\n',
66 | '\x18Bitcoin Signed Message:\n'
67 | ],
68 | wif: [0xb0, 0x80],
69 | legacyXPriv: [0x06c4abc9, 0x0488ade4],
70 | legacyXPub: [0x06c4abc8, 0x0488b21e],
71 | pubkeyHash: [0x1c, 0x00],
72 | scriptHash: [0x19, 0x05]
73 | }
74 | }
75 |
76 | export const info: PluginInfo = {
77 | currencyInfo,
78 | engineInfo,
79 | coinInfo
80 | }
81 |
--------------------------------------------------------------------------------
/patches/altcoin-js+1.0.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/altcoin-js/src/transaction.js b/node_modules/altcoin-js/src/transaction.js
2 | index ec6af8c..a137e15 100644
3 | --- a/node_modules/altcoin-js/src/transaction.js
4 | +++ b/node_modules/altcoin-js/src/transaction.js
5 | @@ -53,6 +53,7 @@ class Transaction {
6 | const flag = bufferReader.readUInt8();
7 | let hasWitnesses = false;
8 | if (
9 | + tx.version !== 3 && // avoid segwit for PIVX
10 | marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
11 | flag === Transaction.ADVANCED_TRANSACTION_FLAG
12 | ) {
13 | @@ -88,7 +89,14 @@ class Transaction {
14 | tx.locktime = bufferReader.readUInt32();
15 | if (_NO_STRICT) return tx;
16 | if (bufferReader.offset !== buffer.length)
17 | - throw new Error('Transaction has unexpected data');
18 | + {
19 | + // Throw for all transaction other then PIVX transactions
20 | + if (tx.version !== 3) {
21 | + throw new Error('Transaction has unexpected data');
22 | + }
23 | + // Keep the sapling data for PIVX transactions
24 | + tx.saplingData = bufferReader.readSlice(bufferReader.buffer.length - bufferReader.offset);
25 | + }
26 | return tx;
27 | }
28 | static fromHex(hex) {
29 | @@ -155,6 +163,7 @@ class Transaction {
30 | }
31 | byteLength(_ALLOW_WITNESS = true) {
32 | const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
33 | + const hasSaplingData = this.saplingData != null;
34 | return (
35 | (hasWitnesses ? 10 : 8) +
36 | bufferutils_1.varuint.encodingLength(this.ins.length) +
37 | @@ -169,7 +178,8 @@ class Transaction {
38 | ? this.ins.reduce((sum, input) => {
39 | return sum + vectorSize(input.witness);
40 | }, 0)
41 | - : 0)
42 | + : 0) +
43 | + (hasSaplingData ? this.saplingData.length : 0)
44 | );
45 | }
46 | clone() {
47 | @@ -604,6 +614,10 @@ class Transaction {
48 | });
49 | }
50 | bufferWriter.writeUInt32(this.locktime);
51 | + // Read sapling data for PIVX transactions
52 | + if (this.saplingData) {
53 | + bufferWriter.writeSlice(this.saplingData);
54 | + }
55 | // avoid slicing unless necessary
56 | if (initialOffset !== undefined)
57 | return buffer.slice(initialOffset, bufferWriter.offset);
58 |
--------------------------------------------------------------------------------
/src/common/utxobased/info/ravencoin.ts:
--------------------------------------------------------------------------------
1 | import { EdgeCurrencyInfo } from 'edge-core-js/types'
2 |
3 | import { CoinInfo, EngineInfo, PluginInfo } from '../../plugin/types'
4 | import { maximumFeeRateCalculator } from '../../plugin/util/maximumFeeRateCalculator'
5 | import {
6 | legacyMemoInfo,
7 | utxoCustomFeeTemplate,
8 | utxoMemoOptions
9 | } from './commonInfo'
10 |
11 | const currencyInfo: EdgeCurrencyInfo = {
12 | assetDisplayName: 'Ravencoin',
13 | chainDisplayName: 'Ravencoin',
14 | currencyCode: 'RVN',
15 | customFeeTemplate: utxoCustomFeeTemplate,
16 | memoOptions: utxoMemoOptions,
17 | pluginId: 'ravencoin',
18 | walletType: 'wallet:ravencoin',
19 |
20 | // Explorers:
21 | addressExplorer: 'https://rvn.cryptoscope.io/address/?address=%s',
22 | blockExplorer: 'https://rvn.cryptoscope.io/block/?blockheight=%s',
23 | transactionExplorer: 'https://rvn.cryptoscope.io/tx/?txid=%s',
24 |
25 | denominations: [{ name: 'RVN', multiplier: '100000000', symbol: 'R' }],
26 |
27 | // Deprecated:
28 | ...legacyMemoInfo,
29 | defaultSettings: {
30 | customFeeSettings: ['satPerByte'],
31 | blockbookServers: ['wss://blockbook.ravencoin.org'],
32 | enableCustomServers: false
33 | },
34 | displayName: 'Ravencoin',
35 | metaTokens: []
36 | }
37 |
38 | const engineInfo: EngineInfo = {
39 | formats: ['bip44', 'bip32'],
40 | gapLimit: 10,
41 | feeUpdateInterval: 60000,
42 | defaultFeeInfo: {
43 | lowFeeFudgeFactor: undefined,
44 | standardFeeLowFudgeFactor: undefined,
45 | standardFeeHighFudgeFactor: undefined,
46 | highFeeFudgeFactor: undefined,
47 |
48 | highFee: '150',
49 | lowFee: '20',
50 | standardFeeLow: '50',
51 | standardFeeHigh: '100',
52 | standardFeeLowAmount: '173200',
53 | standardFeeHighAmount: '8670000',
54 | maximumFeeRate: maximumFeeRateCalculator(currencyInfo, 0.01419)
55 | }
56 | }
57 |
58 | export const coinInfo: CoinInfo = {
59 | name: 'ravencoin',
60 | segwit: false,
61 | coinType: 175,
62 | prefixes: {
63 | messagePrefix: ['\x18Bitcoin Signed Message:\n'],
64 | wif: [0x80],
65 | legacyXPriv: [0x0488ade4],
66 | legacyXPub: [0x0488b21e],
67 | pubkeyHash: [0x3c],
68 | scriptHash: [0x7a]
69 | }
70 | }
71 |
72 | export const info: PluginInfo = {
73 | currencyInfo,
74 | engineInfo,
75 | coinInfo
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Edge Currency Plugin for UTXO-Based currencies
2 |
3 | > Plugins for [edge-core-js](https://github.com/EdgeApp/edge-core-js), to support various cryptocurrencies such as bitcoin, bitcoin cash, and litecoin,
4 |
5 | ## Installing
6 |
7 | First, add this library to your project:
8 |
9 | ```sh
10 | npm i -s edge-currency-plugins
11 | ```
12 |
13 | ### Node.js
14 |
15 | For Node.js, you should call `addEdgeCorePlugins` to register these plugins with edge-core-js:
16 |
17 | ```js
18 | const { addEdgeCorePlugins, lockEdgeCorePlugins } = require("edge-core-js");
19 | const plugins = require("edge-currency-plugins");
20 |
21 | addEdgeCorePlugins(plugins);
22 |
23 | // Once you are done adding plugins, call this:
24 | lockEdgeCorePlugins();
25 | ```
26 |
27 | You can also add plugins individually if you want to be more picky:
28 |
29 | ```js
30 | addEdgeCorePlugins({
31 | bitcoin: plugins.bitcoin,
32 | });
33 | ```
34 |
35 | ### Browser
36 |
37 | The bundle located in `dist/edge-currency-plugins.js` will automatically register itself with edge-core-js. Just serve the entire `dist` directory along with your app, and then load the script:
38 |
39 | ```html
40 |