From d99d3eb1dd7b96b17ea50efc0b58a5bfa92e28c7 Mon Sep 17 00:00:00 2001 From: Samuel Holmes Date: Fri, 25 Aug 2023 17:42:59 -0700 Subject: [PATCH] Add Filecoin --- package.json | 1 + src/filecoin/FilecoinEngine.ts | 383 +++++++++++++++++++++++++++++++++ src/filecoin/FilecoinTools.ts | 184 ++++++++++++++++ src/filecoin/Filscan.ts | 231 ++++++++++++++++++++ src/filecoin/RpcExtra.ts | 93 ++++++++ src/filecoin/filecoinInfo.ts | 85 ++++++++ src/filecoin/filecoinTypes.ts | 89 ++++++++ src/index.ts | 2 + test/plugin/fixtures.ts | 134 ++++++++++++ yarn.lock | 108 +++++++++- 10 files changed, 1307 insertions(+), 3 deletions(-) create mode 100644 src/filecoin/FilecoinEngine.ts create mode 100644 src/filecoin/FilecoinTools.ts create mode 100644 src/filecoin/Filscan.ts create mode 100644 src/filecoin/RpcExtra.ts create mode 100644 src/filecoin/filecoinInfo.ts create mode 100644 src/filecoin/filecoinTypes.ts diff --git a/package.json b/package.json index 17c11ad0d..720c45e7c 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@hashgraph/sdk": "^1.1.9", "@polkadot/api": "^10.9.1", "@solana/web3.js": "^1.32.0", + "@zondax/izari-filecoin": "^1.2.0", "algosdk": "^2.1.0", "biggystring": "^4.1.3", "bip39": "^3.0.2", diff --git a/src/filecoin/FilecoinEngine.ts b/src/filecoin/FilecoinEngine.ts new file mode 100644 index 000000000..976f93799 --- /dev/null +++ b/src/filecoin/FilecoinEngine.ts @@ -0,0 +1,383 @@ +import { + Address, + RPC, + Signature, + SignatureType, + Token, + Transaction, + Wallet +} from '@zondax/izari-filecoin' +import { add, lte, mul, sub } from 'biggystring' +import { + EdgeCurrencyEngine, + EdgeCurrencyEngineOptions, + EdgeEnginePrivateKeyOptions, + EdgeFreshAddress, + EdgeSpendInfo, + EdgeTransaction, + EdgeWalletInfo, + InsufficientFundsError, + JsonObject, + NoAmountSpecifiedError +} from 'edge-core-js/types' + +import { CurrencyEngine } from '../common/CurrencyEngine' +import { PluginEnvironment } from '../common/innerPlugin' +import { FilecoinTools } from './FilecoinTools' +import { + asFilecoinPrivateKeys, + asFilecoinTxOtherParams, + asFilecoinWalletOtherData, + asSafeFilecoinWalletInfo, + FilecoinNetworkInfo, + FilecoinTxOtherParams, + FilecoinWalletOtherData, + SafeFilecoinWalletInfo +} from './filecoinTypes' +import { Filscan, FilscanMessage } from './Filscan' +import { RpcExtra } from './RpcExtra' + +const CHECK_BALANCE_INTERVAL = 15000 +const CHECK_BLOCKHEIGHT_INTERVAL = 30000 +const CHECK_TRANSACTION_INTERVAL = 15000 + +export class FilecoinEngine extends CurrencyEngine< + FilecoinTools, + SafeFilecoinWalletInfo +> { + address: Address + availableAttoFil: string + networkInfo: FilecoinNetworkInfo + otherData!: FilecoinWalletOtherData + pluginId: string + + // Backends: + filRpc: RPC + filscanApi: Filscan + rpcExtra: RpcExtra + + constructor( + env: PluginEnvironment, + tools: FilecoinTools, + walletInfo: SafeFilecoinWalletInfo, + opts: EdgeCurrencyEngineOptions + ) { + super(env, tools, walletInfo, opts) + const { networkInfo } = env + this.address = Address.fromString(walletInfo.keys.address) + this.availableAttoFil = '0' + this.filRpc = new RPC(env.networkInfo.rpcNode.networkName, { + url: env.networkInfo.rpcNode.url, + token: env.currencyInfo.currencyCode + }) + this.filscanApi = new Filscan(env.networkInfo.filscanUrl, env.io.fetchCors) + this.rpcExtra = new RpcExtra(env.networkInfo.rpcNode.url, env.io.fetchCors) + + this.networkInfo = networkInfo + this.pluginId = this.currencyInfo.pluginId + } + + setOtherData(raw: any): void { + this.otherData = asFilecoinWalletOtherData(raw) + } + + initData(): void { + // Initialize walletLocalData: + // ... + + // Engine variables + this.availableAttoFil = '0' + } + + initSubscriptions(): void { + this.addToLoop('checkBalance', CHECK_BALANCE_INTERVAL).catch(error => + this.log(error) + ) + this.addToLoop('checkBlockHeight', CHECK_BLOCKHEIGHT_INTERVAL).catch( + error => this.log(error) + ) + this.addToLoop('checkTransactions', CHECK_TRANSACTION_INTERVAL).catch( + error => this.log(error) + ) + } + + onUpdateBlockHeight(networkBlockHeight: number): void { + if (this.walletLocalData.blockHeight !== networkBlockHeight) { + this.walletLocalData.blockHeight = networkBlockHeight + this.walletLocalDataDirty = true + this.currencyEngineCallbacks.onBlockHeightChanged( + this.walletLocalData.blockHeight + ) + } + } + + onUpdateTransactions(): void { + if (this.transactionsChangedArray.length > 0) { + this.currencyEngineCallbacks.onTransactionsChanged( + this.transactionsChangedArray + ) + this.transactionsChangedArray = [] + } + } + + async startEngine(): Promise { + this.initData() + this.initSubscriptions() + await super.startEngine() + } + + async killEngine(): Promise { + await super.killEngine() + } + + async clearBlockchainCache(): Promise { + await super.clearBlockchainCache() + } + + async resyncBlockchain(): Promise { + await super.killEngine() + await this.clearBlockchainCache() + await this.startEngine() + } + + async getFreshAddress(): Promise { + const { address: publicAddress } = this.walletInfo.keys + return { + publicAddress + } + } + + async getMaxSpendable(spendInfo: EdgeSpendInfo): Promise { + const tx = await this.makeSpend(spendInfo) + const networkFee = tx.networkFee + const spendableBalance = sub(this.availableAttoFil, networkFee) + + if (lte(spendableBalance, '0')) throw new InsufficientFundsError() + + return spendableBalance + } + + async makeSpend(edgeSpendInfoIn: EdgeSpendInfo): Promise { + const { edgeSpendInfo, currencyCode } = this.makeSpendCheck(edgeSpendInfoIn) + const spendTarget = edgeSpendInfo.spendTargets[0] + const { publicAddress, nativeAmount } = spendTarget + + if (publicAddress == null) + throw new Error('Missing publicAddress in EdgeSpendInfo') + if (nativeAmount == null) throw new NoAmountSpecifiedError() + + const toAddress = Address.fromString(publicAddress) + + // Great new blank transaction: + const transaction = Transaction.getNew( + toAddress, + this.address, // from + Token.fromAtto(nativeAmount), // value + 0 // method + ) + // Add nonce and gas fields: + await transaction.prepareToSend(this.filRpc) + + const txJson = transaction.toJSON() + + const otherParams: FilecoinTxOtherParams = { + sigJson: undefined, + txJson + } + + const networkFee = mul(txJson.GasLimit.toString(), txJson.GasPremium) // TODO: Include base fee and burn fee somehow? + const totalTxAmount = add(nativeAmount, networkFee) + + const edgeTransaction: EdgeTransaction = { + txid: '', + date: 0, + currencyCode, + blockHeight: 0, + nativeAmount: `-${totalTxAmount}`, + isSend: nativeAmount.startsWith('-'), + networkFee, + ourReceiveAddresses: [], + otherParams, + signedTx: '', + walletId: this.walletId + } + + return edgeTransaction + } + + async signTx( + edgeTransaction: EdgeTransaction, + privateKeys: JsonObject + ): Promise { + const otherParams = asFilecoinTxOtherParams(edgeTransaction.otherParams) + const transaction = Transaction.fromJSON(otherParams.txJson) + + // Add signature JSON to otherParams: + const filecoinPrivateKeys = asFilecoinPrivateKeys(this.pluginId)( + privateKeys + ) + const accountData = Wallet.deriveAccount( + filecoinPrivateKeys.mnemonic, + SignatureType.SECP256K1, + this.tools.derivationPath + ) + const signature = await Wallet.signTransaction(accountData, transaction) + edgeTransaction.otherParams = { + ...edgeTransaction.otherParams, + sigJson: signature.toJSON() + } + + return edgeTransaction + } + + async broadcastTx( + edgeTransaction: EdgeTransaction, + opts?: EdgeEnginePrivateKeyOptions + ): Promise { + const otherParams = asFilecoinTxOtherParams(edgeTransaction.otherParams) + + if (otherParams.sigJson == null) + throw new Error('Cannot broadcast unsigned transaction') + + const signature: Signature = Signature.fromJSON(otherParams.sigJson) + const transaction: Transaction = Transaction.fromJSON(otherParams.txJson) + + const response = await this.filRpc.broadcastTransaction( + transaction, + signature + ) + if ('error' in response) throw new Error(response.error.message) + + // Save CID as the txid + edgeTransaction.txid = response.result['/'] + + return edgeTransaction + } + + getDisplayPrivateSeed(privateKeys: JsonObject): string { + const filecoinPrivateKeys = asFilecoinPrivateKeys(this.pluginId)( + privateKeys + ) + return filecoinPrivateKeys.mnemonic + } + + getDisplayPublicSeed(): string { + return this.walletInfo.keys.publicKey + } + + async loadEngine(): Promise { + await super.loadEngine() + this.engineOn = true + } + + // + // Filecoin Engine Specific + // + + async checkBalance(): Promise { + const response = await this.filRpc.walletBalance(this.address) + if ('error' in response) throw new Error(response.error.message) + + const { result: balance } = response + this.availableAttoFil = balance + this.updateBalance(this.currencyInfo.currencyCode, balance) + this.tokenCheckBalanceStatus[this.currencyInfo.currencyCode] = 1 + this.updateOnAddressesChecked() + this.walletLocalDataDirty = true + } + + async checkBlockHeight(): Promise { + const response = await this.rpcExtra.getChainHead() + const blockHeight = response.result.Height + + this.onUpdateBlockHeight(blockHeight) + } + + async checkTransactions(): Promise { + const addressString = this.address.toString() + + const messagesPerPage = 20 + let index = 0 + let messagesChecked = 0 + let messageCount = 0 + do { + const messagesResponse = await this.filscanApi.getAccountMessages( + addressString, + index++, + messagesPerPage + ) + const messages = messagesResponse.result.messages_by_account_id_list + + for (const message of messages) { + const txid = message.cid + const idx = this.findTransaction(this.currencyInfo.currencyCode, txid) + + if (idx >= 0) { + // Exit early because we reached transaction history from previous + // check + return + } + + // Process message into a transaction + this.processMessage(message) + } + + messageCount = messagesResponse.result.total_count + messagesChecked += messages.length + this.tokenCheckTransactionsStatus[this.currencyInfo.currencyCode] = + messagesChecked / messageCount + this.updateOnAddressesChecked() + } while (messagesChecked < messageCount) + } + + processMessage(message: FilscanMessage): void { + const addressString = this.address.toString() + let netNativeAmount = message.value + const ourReceiveAddresses = [] + + const networkFee = '0' // TODO: calculate transaction fee from onchain gas fields + if (message.to !== addressString) { + // check if tx is a spend + netNativeAmount = `-${add(netNativeAmount, networkFee)}` + } else { + ourReceiveAddresses.push(addressString) + } + + const edgeTransaction: EdgeTransaction = { + txid: message.cid, + date: message.block_time, + currencyCode: this.currencyInfo.currencyCode, + blockHeight: message.height, + nativeAmount: netNativeAmount, + isSend: netNativeAmount.startsWith('-'), + networkFee, + ourReceiveAddresses, // blank if you sent money otherwise array of addresses that are yours in this transaction + signedTx: '', + otherParams: {}, + walletId: this.walletId + } + this.addTransaction(this.currencyInfo.currencyCode, edgeTransaction) + this.onUpdateTransactions() + + // Progress the block-height if the message's height is greater than + // last poll for block-height. + if (this.walletLocalData.blockHeight < message.height) { + this.onUpdateBlockHeight(message.height) + } + } +} +export async function makeCurrencyEngine( + env: PluginEnvironment, + tools: FilecoinTools, + walletInfo: EdgeWalletInfo, + opts: EdgeCurrencyEngineOptions +): Promise { + const safeWalletInfo = asSafeFilecoinWalletInfo(walletInfo) + + const engine = new FilecoinEngine(env, tools, safeWalletInfo, opts) + + // Do any async initialization necessary for the engine + await engine.loadEngine() + + return engine +} diff --git a/src/filecoin/FilecoinTools.ts b/src/filecoin/FilecoinTools.ts new file mode 100644 index 000000000..6ed27ef0b --- /dev/null +++ b/src/filecoin/FilecoinTools.ts @@ -0,0 +1,184 @@ +import { Address, SignatureType, Wallet } from '@zondax/izari-filecoin' +import { div } from 'biggystring' +import { fromSeed } from 'bip32' +import { entropyToMnemonic, mnemonicToSeed, validateMnemonic } from 'bip39' +import { asMaybe } from 'cleaners' +import { + EdgeCurrencyInfo, + EdgeCurrencyTools, + EdgeEncodeUri, + EdgeIo, + EdgeMetaToken, + EdgeParsedUri, + EdgeTokenMap, + EdgeWalletInfo, + JsonObject +} from 'edge-core-js/types' +import { base16 } from 'rfc4648' + +import { PluginEnvironment } from '../common/innerPlugin' +import { encodeUriCommon, parseUriCommon } from '../common/uriHelpers' +import { getLegacyDenomination } from '../common/utils' +import { + asFilecoinPrivateKeys, + asFilPublicKey, + FilecoinNetworkInfo +} from './filecoinTypes' + +export class FilecoinTools implements EdgeCurrencyTools { + builtinTokens: EdgeTokenMap + currencyInfo: EdgeCurrencyInfo + io: EdgeIo + networkInfo: FilecoinNetworkInfo + derivationPath: string + + constructor(env: PluginEnvironment) { + const { builtinTokens, currencyInfo, io, networkInfo } = env + this.builtinTokens = builtinTokens + this.currencyInfo = currencyInfo + this.io = io + this.networkInfo = networkInfo + this.derivationPath = `m/44'/${this.networkInfo.hdPathCoinType}'/0'/0/0` + } + + async isValidAddress(address: string): Promise { + try { + Address.fromString(address) + return true + } catch (error) { + return false + } + } + + async importPrivateKey( + userInput: string, + opts: JsonObject = {} + ): Promise { + const { pluginId } = this.currencyInfo + + if (!validateMnemonic(userInput)) { + throw new Error('Invalid mnemonic') + } + + const seed = (await mnemonicToSeed(userInput)).toString('hex') + + return { + [`${pluginId}Key`]: seed, + [`${pluginId}Mnemonic`]: userInput + } + } + + async createPrivateKey(walletType: string): Promise { + const { pluginId } = this.currencyInfo + + if (walletType !== this.currencyInfo.walletType) { + throw new Error('InvalidWalletType') + } + + const seed = base16.stringify(this.io.random(32)) + const mnemonic = entropyToMnemonic(seed) + + return { + [`${pluginId}Key`]: seed, + [`${pluginId}Mnemonic`]: mnemonic + } + } + + async checkPublicKey(publicKey: JsonObject): Promise { + return asMaybe(asFilPublicKey)(publicKey) != null + } + + async derivePublicKey(walletInfo: EdgeWalletInfo): Promise { + if (walletInfo.type !== this.currencyInfo.walletType) { + throw new Error('InvalidWalletType') + } + + const { pluginId } = this.currencyInfo + const { hdPathCoinType } = this.networkInfo + + const filecoinPrivateKeys = asFilecoinPrivateKeys(pluginId)(walletInfo.keys) + + // TODO: Figure out how to use the accountData.privateKey Buffer to gen xpub + const seed = await mnemonicToSeed(filecoinPrivateKeys.mnemonic) + const inter = fromSeed(seed) + const xprivDerivation = inter + .deriveHardened(44) + .deriveHardened(hdPathCoinType) + .deriveHardened(0) + const xpubDerivation = xprivDerivation.neutered() + const xpub = xpubDerivation.toBase58() + + const accountData = Wallet.deriveAccount( + filecoinPrivateKeys.mnemonic, + SignatureType.SECP256K1, + this.derivationPath + ) + const address = accountData.address.toString() + + return { + publicKey: xpub, + address + } + } + + async parseUri( + uri: string, + currencyCode?: string, + customTokens?: EdgeMetaToken[] + ): Promise { + const { pluginId } = this.currencyInfo + const networks = { [pluginId]: true } + + const { + edgeParsedUri, + edgeParsedUri: { publicAddress } + } = parseUriCommon( + this.currencyInfo, + uri, + networks, + currencyCode ?? this.currencyInfo.currencyCode, + customTokens + ) + + if (publicAddress == null || !(await this.isValidAddress(publicAddress))) { + throw new Error('InvalidPublicAddressError') + } + + return edgeParsedUri + } + + async encodeUri( + obj: EdgeEncodeUri, + customTokens: EdgeMetaToken[] = [] + ): Promise { + const { pluginId } = this.currencyInfo + const { nativeAmount, currencyCode, publicAddress } = obj + + if (!(await this.isValidAddress(publicAddress))) { + throw new Error('InvalidPublicAddressError') + } + + let amount + if (nativeAmount != null) { + const denom = getLegacyDenomination( + currencyCode ?? this.currencyInfo.currencyCode, + this.currencyInfo, + customTokens + ) + if (denom == null) { + throw new Error('InternalErrorInvalidCurrencyCode') + } + amount = div(nativeAmount, denom.multiplier, 18) + } + const encodedUri = encodeUriCommon(obj, `${pluginId}`, amount) + return encodedUri + } +} + +export async function makeCurrencyTools( + env: PluginEnvironment +): Promise { + return new FilecoinTools(env) +} + +export { makeCurrencyEngine } from './FilecoinEngine' diff --git a/src/filecoin/Filscan.ts b/src/filecoin/Filscan.ts new file mode 100644 index 000000000..c0fb381bc --- /dev/null +++ b/src/filecoin/Filscan.ts @@ -0,0 +1,231 @@ +import { + asArray, + asEither, + asJSON, + asNumber, + asObject, + asString, + Cleaner +} from 'cleaners' +import { EdgeFetchFunction } from 'edge-core-js/types' + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// +// Response Templates +// + +export interface FilscanOkResponse { + result: Result +} +export const asFilscanOkResponse = ( + asResult: Cleaner +): Cleaner> => + asObject({ + result: asResult + }) + +export type FilscanError = ReturnType +export const asFilscanError = asJSON( + asObject({ + code: asNumber, + message: asString + }) +) + +export type FilscanEnvelope = FilscanOkResponse | FilscanError +export const asFilscanEnvelope = ( + asResult: Cleaner +): Cleaner> => + asJSON(asEither(asFilscanError, asFilscanOkResponse(asResult))) + +// +// Nominal Types +// + +export type FilscanMessage = ReturnType +export const asFilscanMessage = asObject({ + height: asNumber, + block_time: asNumber, + cid: asString, + from: asString, + to: asString, + value: asString, + exit_code: asString, + method_name: asString +}) + +// +// Account Info +// + +export type FilscanAccountInfoResult = ReturnType< + typeof asFilscanAccountInfoResponse +> +export const asFilscanAccountInfoResponse = asObject({ + account_type: asString, + account_info: asObject({ + account_basic: asObject({ + account_id: asString, + account_address: asString, + account_type: asString, + account_balance: asString, + nonce: asNumber, + code_cid: asString, + create_time: asString, + latest_transfer_time: asString + }) + }) +}) + +// +// Messages +// + +export type FilscanMessagesResult = ReturnType +export const asFilscanMessagesResult = asObject({ + messages_by_account_id_list: asArray(asFilscanMessage), + total_count: asNumber +}) + +// +// Message Details +// + +export type FilscanMessageDetailsResult = ReturnType< + typeof asFilscanMessageDetailsResult +> +export const asFilscanMessageDetailsResult = asObject({ + MessageDetails: asObject({ + message_basic: asObject({ + height: asNumber, + block_time: asNumber, + cid: asString, + from: asString, + to: asString, + value: asString, + exit_code: asString, + method_name: asString + }), + blk_cids: asArray(asString), + consume_list: asArray( + asObject({ + from: asString, + to: asString, + value: asString, + consume_type: asString + }) + ), + version: asNumber, + nonce: asNumber, + gas_fee_cap: asString, + gas_premium: asString, + gas_limit: asNumber, + gas_used: asString, + base_fee: asString, + all_gas_fee: asString, + // params_detail: asNull + // returns_detail: asNull + eth_message: asString + }) +}) + +// ----------------------------------------------------------------------------- +// Implementation +// ----------------------------------------------------------------------------- + +export class Filscan { + baseUrl: string + fetch: EdgeFetchFunction + + constructor(baseUrl: string, fetchFn: EdgeFetchFunction) { + this.baseUrl = baseUrl + this.fetch = fetchFn + } + + async getAccountInfo( + accountId: string + ): Promise> { + const response = await this.fetch(`${this.baseUrl}/AccountInfoByID`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + account_id: accountId + }) + }) + const responseText = await response.text() + const responseBody = asFilscanEnvelope(asFilscanAccountInfoResponse)( + responseText + ) + + if (!('result' in responseBody)) + throw new Error( + `Error response code ${responseBody.code}: ${responseBody.message}` + ) + + return responseBody + } + + async getAccountMessages( + accountId: string, + index: number, + limit: number = 20 + ): Promise> { + const response = await this.fetch(`${this.baseUrl}/MessagesByAccountID`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + account_id: accountId, + address: '', + filters: { + index, + page: 0, + limit, + method_name: '' + } + }) + }) + const responseText = await response.text() + const responseBody = asFilscanEnvelope(asFilscanMessagesResult)( + responseText + ) + + if (!('result' in responseBody)) + throw new Error( + `Error response code ${responseBody.code}: ${responseBody.message}` + ) + + return responseBody + } + + async getMessageDetails( + messageCid: string + ): Promise> { + const response = await this.fetch(`${this.baseUrl}/MessageDetails`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + message_cid: messageCid + }) + }) + const responseText = await response.text() + const responseBody = asFilscanEnvelope(asFilscanMessageDetailsResult)( + responseText + ) + + if (!('result' in responseBody)) + throw new Error( + `Error response code ${responseBody.code}: ${responseBody.message}` + ) + + return responseBody + } +} diff --git a/src/filecoin/RpcExtra.ts b/src/filecoin/RpcExtra.ts new file mode 100644 index 000000000..13a3bfa4f --- /dev/null +++ b/src/filecoin/RpcExtra.ts @@ -0,0 +1,93 @@ +import { + asEither, + asJSON, + asNumber, + asObject, + asString, + Cleaner +} from 'cleaners' +import { EdgeFetchFunction } from 'edge-core-js/types' + +// ----------------------------------------------------------------------------- +// Types +// ----------------------------------------------------------------------------- + +// +// Response Templates +// + +export interface RpcOkResponse { + result: Result +} +export const asRpcOkResponse = ( + asResult: Cleaner +): Cleaner> => + asObject({ + id: asNumber, + jsonrpc: asString, + result: asResult + }) + +export type RpcError = ReturnType +export const asRpcError = asObject({ + id: asNumber, + jsonrpc: asString, + error: asObject({ + code: asNumber, + message: asString + }) +}) + +export type RpcEnvelope = RpcOkResponse | RpcError +export const asRpcEnvelope = ( + asResult: Cleaner +): Cleaner> => + asJSON(asEither(asRpcError, asRpcOkResponse(asResult))) + +// +// ChainHead +// + +export type RpcChainHeadResponse = ReturnType +export const asRpcChainHeadResponse = asObject({ + Height: asNumber +}) + +// ----------------------------------------------------------------------------- +// Implementation +// ----------------------------------------------------------------------------- + +export class RpcExtra { + baseUrl: string + fetch: EdgeFetchFunction + + constructor(baseUrl: string, fetchFn: EdgeFetchFunction) { + this.baseUrl = baseUrl + this.fetch = fetchFn + } + + async getChainHead(): Promise> { + const nonce = Math.floor(Math.random() * 10 ** 8) + const response = await this.fetch(this.baseUrl, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({ + id: nonce, + jsonrpc: '2.0', + method: 'Filecoin.ChainHead', + params: null + }) + }) + const responseText = await response.text() + const responseBody = asRpcEnvelope(asRpcChainHeadResponse)(responseText) + + if ('error' in responseBody) + throw new Error( + `Error response code ${responseBody.error.code}: ${responseBody.error.message}` + ) + + return responseBody + } +} diff --git a/src/filecoin/filecoinInfo.ts b/src/filecoin/filecoinInfo.ts new file mode 100644 index 000000000..75c5d0166 --- /dev/null +++ b/src/filecoin/filecoinInfo.ts @@ -0,0 +1,85 @@ +import { Network } from '@zondax/izari-filecoin' +import { EdgeCurrencyInfo } from 'edge-core-js/types' + +import { makeOuterPlugin } from '../common/innerPlugin' +import type { FilecoinTools } from './FilecoinTools' +import type { FilecoinNetworkInfo } from './filecoinTypes' + +const networkInfo: FilecoinNetworkInfo = { + filscanUrl: 'https://api-v2.filscan.io/api/v1', + hdPathCoinType: 461, + rpcNode: { + networkName: Network.Mainnet, + url: 'https://api.node.glif.io/' + } +} + +export const currencyInfo: EdgeCurrencyInfo = { + // Basic currency information: + currencyCode: 'FIL', + displayName: 'Filecoin', + pluginId: 'filecoin', + requiredConfirmations: 900, + walletType: 'wallet:filecoin', + + defaultSettings: {}, + + addressExplorer: 'https://blockchair.com/filecoin/address/%s?from=edgeapp', + transactionExplorer: + 'https://blockchair.com/filecoin/transaction/%s?from=edgeapp', + + denominations: [ + // An array of Objects of the possible denominations for this currency + { + name: 'FIL', + multiplier: '1000000000000000000', + symbol: '⨎' + }, + { + name: 'milliFIL', + multiplier: '1000000000000000', + symbol: 'm⨎' + }, + { + name: 'microFIL', + multiplier: '1000000000000', + symbol: 'µ⨎' + }, + { + name: 'nanoFIL', + multiplier: '1000000000', + symbol: 'n⨎' + }, + { + name: 'picoFIL', + multiplier: '1000000', + symbol: 'p⨎' + }, + { + name: 'femtoFIL', + multiplier: '1000', + symbol: 'f⨎' + }, + { + name: 'attoFIL', + multiplier: '1', + symbol: 'a⨎' + } + ], + + metaTokens: [], // Deprecated + + unsafeBroadcastTx: true +} + +export const filecoin = makeOuterPlugin({ + currencyInfo, + networkInfo, + + async getInnerPlugin() { + return await import( + /* webpackChunkName: "filecoin" */ + './FilecoinTools' + ) + } +}) diff --git a/src/filecoin/filecoinTypes.ts b/src/filecoin/filecoinTypes.ts new file mode 100644 index 000000000..4358cb652 --- /dev/null +++ b/src/filecoin/filecoinTypes.ts @@ -0,0 +1,89 @@ +import { + asCodec, + asNumber, + asObject, + asOptional, + asString, + asValue, + Cleaner +} from 'cleaners' + +import { asWalletInfo } from '../common/types' + +// Copy of `import { Network } from '@zondax/izari-filecoin'` +declare enum Network { + Mainnet = 'mainnet', + Calibration = 'calibration', + Butterfly = 'butterfly' +} +export interface FilecoinNetworkInfo { + filscanUrl: string + hdPathCoinType: number + rpcNode: { + networkName: Network + url: string + } +} + +export type FilecoinWalletOtherData = ReturnType< + typeof asFilecoinWalletOtherData +> +export const asFilecoinWalletOtherData = asObject({}) + +export type FilecoinTxOtherParams = ReturnType +export const asFilecoinTxOtherParams = asObject({ + sigJson: asOptional( + asObject({ + Data: asString, + Type: asValue(1, 3) + }) + ), + txJson: asObject({ + To: asString, + From: asString, + Nonce: asNumber, + Value: asString, + GasLimit: asNumber, + GasFeeCap: asString, + GasPremium: asString, + Method: asNumber, + Params: asString + }) +}) + +export const asFilPublicKey = asObject({ + address: asString, + publicKey: asString +}) + +export type SafeFilecoinWalletInfo = ReturnType +export const asSafeFilecoinWalletInfo = asWalletInfo(asFilPublicKey) + +export interface FilecoinPrivateKeys { + mnemonic: string + privateKey: string +} +export const asFilecoinPrivateKeys = ( + pluginId: string +): Cleaner => { + const asKeys = asObject({ + [`${pluginId}Mnemonic`]: asString, + [`${pluginId}Key`]: asString + }) + + return asCodec( + raw => { + const clean = asKeys(raw) + return { + mnemonic: clean[`${pluginId}Mnemonic`], + privateKey: clean[`${pluginId}Key`] + } + }, + clean => { + return { + [`${pluginId}Mnemonic`]: clean.mnemonic, + [`${pluginId}Key`]: clean.privateKey + } + } + ) +} diff --git a/src/index.ts b/src/index.ts index a93919cb4..f971546a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { algorand } from './algorand/algorandInfo' import { binance } from './binance/binanceInfo' import { eosPlugins } from './eos/eosInfos' import { ethereumPlugins } from './ethereum/ethereumInfos' +import { filecoin } from './filecoin/filecoinInfo' import { fio } from './fio/fioInfo' import { hedera } from './hedera/hederaInfo' import { polkadot } from './polkadot/polkadotInfo' @@ -20,6 +21,7 @@ const plugins = { ...ethereumPlugins, algorand, binance, + filecoin, fio, hedera, piratechain, diff --git a/test/plugin/fixtures.ts b/test/plugin/fixtures.ts index db71090fb..0a2509dca 100644 --- a/test/plugin/fixtures.ts +++ b/test/plugin/fixtures.ts @@ -378,6 +378,140 @@ export default [ ] } }, + { + /* + abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about m/44'/461'/0'/0/0 + f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq + + room soda device label bicycle hill fork nest lion knee purpose hen m/44'/461'/0'/0/0 + f1l5o3gou4vzh2v6wagaan7mvomwa7tlwrpndxhya + */ + pluginId: 'filecoin', + WALLET_TYPE: 'wallet:filecoin', + 'Test Currency code': 'FIL', + key: [ + 39, 190, 34, 129, 208, 32, 145, 88, 191, 217, 226, 98, 183, 16, 52, 150, + 52, 53, 31, 137, 164, 40, 236, 146, 128, 107, 129, 59, 192, 240, 40, 238 + ], + mnemonic: + 'room soda device label bicycle hill fork nest lion knee purpose hen', + xpub: 'xpub6C6UdTHYiyFsD5m3LtRpvuSHXxNE2e3CiVYwhztwVFR9AHGUveisJnr75hvyW18uTT6Qi2HwkXyoZcM8gNPi8C887DoqmrvmtFTFrqitGHg', // m/44'/461'/0' + key_length: 64, + 'invalid key name': { + type: 'wallet:filecoin', + keys: { filecoinKeyz: '12345678abcd' } + }, + 'invalid wallet type': { + type: 'shitcoin', + keys: { filecoinKey: '12345678abcd' } + }, + parseUri: { + 'address only': [ + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' + ], + 'address with provided currency code': { + args: ['f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', 'USDC'], + output: { + publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' + } + }, + // 'checksum address only': [ + // '0x3C40cbb7F82A7E1bc83C4E3E98590b19e0e1bf07', + // '0x3c40cbb7f82a7e1bc83c4e3e98590b19e0e1bf07' + // ], + // 'invalid checksum address only': [ + // '0x3C40cbb7F82A7E1bc83C4E3E98590b19e0e1Bf07' + // ], + 'invalid address': [ + '0x466d506cd7fbcd29a06015da03f0de814df050ez', + '0466d506cd7fbcd29a06015da03f0de814df050ee', + '0x466d506cd7fbcd29a06015da03f0de814df050ee1' + ], + 'uri address': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' + ], + 'uri address with amount': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=12345.6789', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + '12345678900000000000000', + 'FIL' + ], + 'uri address with unique identifier': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?dt=123456789', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + 'FIL' + ], + 'uri address with unique identifier and without network prefix': [ + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?dt=123456789', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + 'FIL' + ], + 'uri address with amount & label': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=1234.56789&label=Johnny%20Ripple', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + '1234567890000000000000', + 'FIL', + 'Johnny Ripple' + ], + 'uri address with amount, label & message': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=1234.56789&label=Johnny%20Ripple&message=Hello%20World,%20I%20miss%20you%20!', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + '1234567890000000000000', + 'FIL', + 'Johnny Ripple', + 'Hello World, I miss you !' + ], + 'uri address with unsupported param': [ + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?unsupported=helloworld&amount=12345.6789', + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + '12345678900000000000000', + 'FIL' + ] + }, + encodeUri: { + 'address only': [ + { publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' }, + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' + ], + 'weird address': [ + { publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' }, + 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq' + ], + 'invalid address': [ + { publicAddress: '0x04b6b3bcbc16a5fb6a20301d650f8def513122az' }, + { publicAddress: '04b6b3bcbc16a5fb6a20301d650f8def513122a8' }, + { publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhqa' } + ], + 'address & amount': [ + { + publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + nativeAmount: '123456780000' + }, + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=0.00000012345678' + ], + 'address, amount, and label': [ + { + publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + nativeAmount: '123000000000000', + currencyCode: 'FIL', + label: 'Johnny Filecoin' + }, + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=0.000123&label=Johnny%20Filecoin' + ], + 'address, amount, label, & message': [ + { + publicAddress: 'f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq', + nativeAmount: '123000000000000', + currencyCode: 'FIL', + label: 'Johnny Filecoin', + message: 'Hello World, I miss you !' + }, + 'filecoin:f1qode47ievxlxzk6z2viuovedabmn3tq6t57uqhq?amount=0.000123&label=Johnny%20Filecoin&message=Hello%20World,%20I%20miss%20you%20!' + ] + } + }, { pluginId: 'ethereum', WALLET_TYPE: 'wallet:ethereum', diff --git a/yarn.lock b/yarn.lock index ff6678cd7..cef35f7d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -249,6 +249,14 @@ optionalDependencies: "@ledgerhq/hw-transport-node-hid" "^5.10.0" +"@bitcoinerlab/secp256k1@^1.0.2": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz#4643ba73619c24c7c455cc63c6338c69c2cf187c" + integrity sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w== + dependencies: + "@noble/hashes" "^1.1.5" + "@noble/secp256k1" "^1.7.1" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -738,6 +746,14 @@ dependencies: browser-headers "^0.4.0" +"@ipld/dag-cbor@^9.0.0": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-9.0.4.tgz#5e1bc63aeabf882a750be88bb6879017c5c41930" + integrity sha512-HBNVngk/47pKNLTAelN6ORWgKkjJtQj96Xb+jIBtRShJGCsXgghj1TzTynTTIp1dZxwPe5rVIL6yjZmvdyP2Wg== + dependencies: + cborg "^2.0.1" + multiformats "^12.0.1" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -882,6 +898,16 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@^1.1.5", "@noble/hashes@^1.2.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@noble/secp256k1@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" + integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1281,7 +1307,7 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@scure/base@1.1.1": +"@scure/base@1.1.1", "@scure/base@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== @@ -2034,6 +2060,23 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +"@zondax/izari-filecoin@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@zondax/izari-filecoin/-/izari-filecoin-1.2.0.tgz#84019247d573b915c5333f16a71c7051e570a1ff" + integrity sha512-pixz+VOaNy2RXbU/eUPYtoulxJ4QYKpj05rP5rzEG4yckE1WkLVMajrYH/VWJlNw48GjtxZko6cWwJAi8ZDolQ== + dependencies: + "@bitcoinerlab/secp256k1" "^1.0.2" + "@ipld/dag-cbor" "^9.0.0" + axios "^1.3.2" + base32-decode "^1.0.0" + bip32 "^4.0.0" + bip39 "^3.0.4" + blakejs "^1.2.1" + bn.js "^5.2.1" + leb128 "^0.0.5" + multiformats "^11.0.2" + secp256k1 "^5.0.0" + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -2317,7 +2360,7 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axios@0.19.0, axios@^0.18.0, axios@^0.19.0, axios@^0.26.1: +axios@0.19.0, axios@^0.18.0, axios@^0.19.0, axios@^0.26.1, axios@^1.3.2: version "0.19.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== @@ -2367,6 +2410,11 @@ base-x@^4.0.0: resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== +base32-decode@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base32-decode/-/base32-decode-1.0.0.tgz#2a821d6a664890c872f20aa9aca95a4b4b80e2a7" + integrity sha512-KNWUX/R7wKenwE/G/qFMzGScOgVntOmbE27vvc6GrniDGYb6a5+qWcuoXl8WIOQL7q0TpK7nZDm1Y04Yi3Yn5g== + base32.js@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" @@ -2454,6 +2502,16 @@ bip32@^2.0.5, bip32@^2.0.6: typeforce "^1.11.5" wif "^2.0.6" +bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@^3.0.2, bip39@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -2471,7 +2529,7 @@ bip66@^1.1.3: dependencies: safe-buffer "^5.0.1" -blakejs@^1.1.0: +blakejs@^1.1.0, blakejs@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== @@ -2637,6 +2695,13 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-pipe@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/buffer-pipe/-/buffer-pipe-0.0.3.tgz#242197681d4591e7feda213336af6c07a5ce2409" + integrity sha512-GlxfuD/NrKvCNs0Ut+7b1IHjylfdegMBxQIlZHj7bObKVQBxB5S84gtm2yu1mQ8/sSggceWBDPY0cPXgvX2MuA== + dependencies: + safe-buffer "^5.1.2" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2726,6 +2791,11 @@ caniuse-lite@^1.0.30001400: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb" integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A== +cborg@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/cborg/-/cborg-2.0.4.tgz#f5a5e75dd355d18330cf27816a8a86210915485b" + integrity sha512-QradkXyNBLIyg1XNxcoXqUG4stcOhfuR1uexq+qNzL+EvFV5TXvN+c+LCh5XXxTv2fjBIkNB+3I6IO17EnKuKg== + chai@^4.2.0: version "4.3.7" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" @@ -5448,6 +5518,14 @@ klaw-sync@^6.0.0: dependencies: graceful-fs "^4.1.11" +leb128@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/leb128/-/leb128-0.0.5.tgz#84524a86ef7799fb3933ce41345f6490e27ac948" + integrity sha512-elbNtfmu3GndZbesVF6+iQAfVjOXW9bM/aax9WwMlABZW+oK9sbAZEXoewaPHmL34sxa8kVwWsru8cNE/yn2gg== + dependencies: + bn.js "^5.0.0" + buffer-pipe "0.0.3" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -5804,6 +5882,16 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" +multiformats@^11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-11.0.2.tgz#b14735efc42cd8581e73895e66bebb9752151b60" + integrity sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg== + +multiformats@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-12.0.1.tgz#dd3e19dd44114c2672e4795a36888d263be30131" + integrity sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ== + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -5868,6 +5956,11 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -6943,6 +7036,15 @@ secp256k1@^4.0.1, secp256k1@^4.0.2: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +secp256k1@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" + integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^5.0.0" + node-gyp-build "^4.2.0" + secure-random@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/secure-random/-/secure-random-1.1.2.tgz#ed103b460a851632d420d46448b2a900a41e7f7c"