Created
February 23, 2018 18:58
-
-
Save leongaban/402034e3efaf3beeb1375b59a34baf66 to your computer and use it in GitHub Desktop.
Decred currencyPlugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import type { | |
AbcCorePluginOptions, | |
AbcCurrencyEngine, | |
AbcCurrencyEngineOptions, | |
AbcCurrencyInfo, | |
AbcCurrencyPlugin, | |
AbcEncodeUri, | |
AbcIo, | |
AbcParsedUri, | |
AbcWalletInfo | |
} from 'edge-login' | |
import { | |
validAddress, | |
sanitizeAddress, | |
dirtyAddress | |
} from '../utils/addressFormat/addressFormatIndex.js' | |
import bcoin from 'bcoin' | |
import { bns } from 'biggystring' | |
// $FlowFixMe | |
import buffer from 'buffer-hack' | |
import { parse, serialize } from 'uri-js' | |
import { CurrencyEngine } from '../engine/currencyEngine.js' | |
import { PluginState } from './pluginState.js' | |
// $FlowFixMe | |
const { Buffer } = buffer | |
const getParameterByName = (param: string, url: string) => { | |
const name = param.replace(/[[\]]/g, '\\$&') | |
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)') | |
const results = regex.exec(url) | |
if (!results) return null | |
if (!results[2]) return '' | |
return decodeURIComponent(results[2].replace(/\+/g, ' ')) | |
} | |
/** | |
* The core currency plugin. | |
* Provides information about the currency, | |
* as well as generic (non-wallet) functionality. | |
*/ | |
export class CurrencyPlugin { | |
currencyInfo: AbcCurrencyInfo | |
network: string | |
pluginName: string | |
io: AbcIo | |
state: PluginState | |
// ------------------------------------------------------------------------ | |
// Private API | |
// ------------------------------------------------------------------------ | |
constructor (options: AbcCorePluginOptions, currencyInfo: AbcCurrencyInfo) { | |
// Validate that we are a valid AbcCurrencyPlugin: | |
// eslint-disable-next-line no-unused-vars | |
const test: AbcCurrencyPlugin = this | |
// Public API: | |
this.currencyInfo = currencyInfo | |
this.network = this.currencyInfo.defaultSettings.network.type | |
this.pluginName = this.currencyInfo.pluginName | |
console.log(`Creating Currency Plugin for ${this.pluginName}`) | |
// Private API: | |
this.io = options.io | |
this.state = new PluginState(this.io, currencyInfo) | |
} | |
// ------------------------------------------------------------------------ | |
// Public API | |
// ------------------------------------------------------------------------ | |
createPrivateKey (walletType: string) { | |
const randomBuffer = Buffer.from(this.io.random(32)) | |
const mnemonic = bcoin.hd.Mnemonic.fromEntropy(randomBuffer) | |
return { | |
[`${this.network}Key`]: mnemonic.getPhrase() | |
} | |
} | |
async derivePublicKey (walletInfo: AbcWalletInfo) { | |
if (!~this.currencyInfo.walletTypes.indexOf(walletInfo.type)) { | |
throw new Error('InvalidWalletType') | |
} | |
if (!walletInfo.keys) throw new Error('InvalidKeyName') | |
const walletType = walletInfo.keys[`${this.network}Key`] | |
if (!walletType) throw new Error('InvalidKeyName') | |
const mnemonic = bcoin.hd.Mnemonic.fromPhrase(walletType) | |
// TODO: Allow fromMnemonic to be async. API needs to change -paulvp | |
let privateKey | |
const result = bcoin.hd.PrivateKey.fromMnemonic(mnemonic, this.network) | |
if (typeof result.then === 'function') { | |
privateKey = await Promise.resolve(result) | |
} else { | |
privateKey = result | |
} | |
return { | |
[`${this.network}Key`]: walletInfo.keys[`${this.network}Key`], | |
[`${this.network}Xpub`]: privateKey.xpubkey() | |
} | |
} | |
async makeEngine ( | |
walletInfo: AbcWalletInfo, | |
options: AbcCurrencyEngineOptions | |
): Promise<AbcCurrencyEngine> { | |
if (!options.optionalSettings) { | |
options.optionalSettings = {} | |
} | |
options.optionalSettings.io = this.io | |
if (!options.walletLocalFolder) { | |
throw new Error('Cannot create an engine without a local folder') | |
} | |
const engine = new CurrencyEngine( | |
walletInfo, | |
this.currencyInfo, | |
this.state, | |
options | |
) | |
await engine.load() | |
return engine | |
} | |
parseUri (uri: string): AbcParsedUri { | |
const parsedUri = parse(uri) | |
const currencyInfo = this.currencyInfo | |
if ( | |
parsedUri.scheme && | |
parsedUri.scheme.toLowerCase() !== currencyInfo.currencyName.toLowerCase() | |
) { | |
throw new Error('InvalidUriError') | |
} | |
let publicAddress = parsedUri.host || parsedUri.path | |
if (!publicAddress) throw new Error('InvalidUriError') | |
publicAddress = publicAddress.replace('/', '') // Remove any slashes | |
publicAddress = dirtyAddress(publicAddress, this.network) | |
if (!validAddress(publicAddress, this.network)) { throw new Error('InvalidPublicAddressError') } | |
const amountStr = getParameterByName('amount', uri) | |
const metadata = {} | |
const name = getParameterByName('label', uri) | |
const message = getParameterByName('message', uri) | |
if (name) metadata.name = name | |
if (message) metadata.message = message | |
const abcParsedUri: AbcParsedUri = { publicAddress, metadata } | |
if (amountStr && typeof amountStr === 'string') { | |
const denom: any = currencyInfo.denominations.find( | |
e => e.name === currencyInfo.currencyCode | |
) | |
const multiplier: string = denom.multiplier.toString() | |
const t = bns.mul(amountStr, multiplier) | |
abcParsedUri.nativeAmount = bns.toFixed(t, 0, 0) | |
abcParsedUri.currencyCode = currencyInfo.currencyCode | |
} | |
return abcParsedUri | |
} | |
encodeUri (obj: AbcEncodeUri): string { | |
if (!obj.publicAddress || !validAddress(obj.publicAddress, this.network)) { | |
throw new Error('InvalidPublicAddressError') | |
} | |
if (!obj.nativeAmount && !obj.metadata) { return dirtyAddress(obj.publicAddress, this.network) } | |
obj.publicAddress = sanitizeAddress(obj.publicAddress, this.network) | |
let queryString = '' | |
const info = this.currencyInfo | |
if (obj.nativeAmount) { | |
const currencyCode = obj.currencyCode || info.currencyCode | |
const denom: any = info.denominations.find(e => e.name === currencyCode) | |
const multiplier: string = denom.multiplier.toString() | |
// $FlowFixMe | |
const amount = bns.div(obj.nativeAmount, multiplier, 8) | |
queryString += 'amount=' + amount.toString() + '&' | |
} | |
if (obj.metadata) { | |
// $FlowFixMe | |
if (obj.metadata.name) queryString += `label=${obj.metadata.name}&` | |
if (obj.metadata.message) { | |
// $FlowFixMe | |
queryString += `message=${obj.metadata.message}&` | |
} | |
} | |
queryString = queryString.substr(0, queryString.length - 1) | |
return serialize({ | |
scheme: info.currencyName.toLowerCase(), | |
path: obj.publicAddress, | |
query: queryString | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment