diff --git a/packages/extension/src/libs/background/index.ts b/packages/extension/src/libs/background/index.ts index d00f28109..ca4fdee91 100644 --- a/packages/extension/src/libs/background/index.ts +++ b/packages/extension/src/libs/background/index.ts @@ -48,6 +48,7 @@ class BackgroundHandler { [ProviderName.bitcoin]: {}, [ProviderName.kadena]: {}, [ProviderName.solana]: {}, + [ProviderName.massa]: {}, }; this.#providers = Providers; } diff --git a/packages/extension/src/libs/background/types.ts b/packages/extension/src/libs/background/types.ts index 73cc10eb9..ae2c834cd 100644 --- a/packages/extension/src/libs/background/types.ts +++ b/packages/extension/src/libs/background/types.ts @@ -3,6 +3,7 @@ import type EthereumProvider from '@/providers/ethereum'; import type PolkadotProvider from '@/providers/polkadot'; import type KadenaProvider from '@/providers/kadena'; import SolanaProvider from '@/providers/solana'; +import MassaProvider from '@/providers/massa'; export interface TabProviderType { [key: string]: Record< @@ -12,6 +13,7 @@ export interface TabProviderType { | BitcoinProvider | KadenaProvider | SolanaProvider + | MassaProvider >; } export interface ProviderType { @@ -20,7 +22,8 @@ export interface ProviderType { | typeof PolkadotProvider | typeof BitcoinProvider | typeof KadenaProvider - | typeof SolanaProvider; + | typeof SolanaProvider + | typeof MassaProvider; } export interface ExternalMessageOptions { savePersistentEvents: boolean; diff --git a/packages/extension/src/libs/utils/initialize-wallet.ts b/packages/extension/src/libs/utils/initialize-wallet.ts index 60d159be4..3b68f728d 100644 --- a/packages/extension/src/libs/utils/initialize-wallet.ts +++ b/packages/extension/src/libs/utils/initialize-wallet.ts @@ -4,10 +4,12 @@ import PolkadotNetworks from '@/providers/polkadot/networks'; import BitcoinNetworks from '@/providers/bitcoin/networks'; import KadenaNetworks from '@/providers/kadena/networks'; import SolanaNetworks from '@/providers/solana/networks'; +import MassaNetworks from '@/providers/massa/networks'; import { NetworkNames, WalletType } from '@enkryptcom/types'; import { getAccountsByNetworkName } from '@/libs/utils/accounts'; import BackupState from '../backup-state'; export const initAccounts = async (keyring: KeyRing) => { + console.log("<>>>>>>>>>>> YOOLOOOOOOO initAccounts",keyring) const secp256k1btc = ( await getAccountsByNetworkName(NetworkNames.Bitcoin) ).filter(acc => !acc.isTestWallet); @@ -23,6 +25,9 @@ export const initAccounts = async (keyring: KeyRing) => { const ed25519sol = ( await getAccountsByNetworkName(NetworkNames.Solana) ).filter(acc => !acc.isTestWallet); + const ed25519massa = ( + await getAccountsByNetworkName(NetworkNames.Massa) + ).filter(acc => !acc.isTestWallet); if (secp256k1.length == 0) await keyring.saveNewAccount({ basePath: EthereumNetworks.ethereum.basePath, @@ -58,6 +63,13 @@ export const initAccounts = async (keyring: KeyRing) => { signerType: SolanaNetworks.solana.signer[0], walletType: WalletType.mnemonic, }); + if (ed25519massa.length == 0) + await keyring.saveNewAccount({ + basePath: MassaNetworks.mainnet.basePath, + name: 'Massa Account 1', + signerType: MassaNetworks.mainnet.signer[0], + walletType: WalletType.mnemonic, + }); }; export const onboardInitializeWallets = async (options: { mnemonic: string; diff --git a/packages/extension/src/libs/utils/networks.ts b/packages/extension/src/libs/utils/networks.ts index 7634ac299..d8ec0c3ef 100644 --- a/packages/extension/src/libs/utils/networks.ts +++ b/packages/extension/src/libs/utils/networks.ts @@ -13,6 +13,8 @@ import Polkadot from '@/providers/polkadot/networks/polkadot'; import Bitcoin from '@/providers/bitcoin/networks/bitcoin'; import Kadena from '@/providers/kadena/networks/kadena'; import Solana from '@/providers/solana/networks/solana'; +import MassaNetworks from '@/providers/massa/networks'; +import Massa from '@/providers/massa/networks/mainnet'; const providerNetworks: Record> = { [ProviderName.ethereum]: EthereumNetworks, @@ -20,6 +22,7 @@ const providerNetworks: Record> = { [ProviderName.bitcoin]: BitcoinNetworks, [ProviderName.kadena]: KadenaNetworks, [ProviderName.solana]: SolanaNetworks, + [ProviderName.massa]: MassaNetworks, [ProviderName.enkrypt]: {}, }; const getAllNetworks = async (includeCustom: boolean = true): Promise => { @@ -32,7 +35,9 @@ const getAllNetworks = async (includeCustom: boolean = true): Promise void; + + constructor(toWindow: (message: string) => void) { + super(); + this.toWindow = toWindow; + this.setMiddleWares(); + this.requestProvider = getRequestProvider('', this.middlewares); + this.requestProvider.on('notification', (notif: any) => { + this.sendNotification(JSON.stringify(notif)); + }); + this.namespace = ProviderName.massa; + this.KeyRing = new PublicKeyRing(); + this.api = new MassaAPI(''); + this.client = new JsonRPCClient(''); + this.transactionHandler = new TransactionHandler(this.client); + this.messageHandler = new MessageHandler(this.client); + this.UIRoutes = UIRoutes; + } + + private setMiddleWares(): void { + this.middlewares = Middlewares(this); + } + + setRequestProvider(network: BaseNetwork): void { + this.api = new MassaAPI(network.node); + this.client = new JsonRPCClient(network.node); + this.transactionHandler = new TransactionHandler(this.client); + this.messageHandler = new MessageHandler(this.client); + this.requestProvider.changeNetwork(network.node); + } + + async isPersistentEvent(): Promise { + return false; + } + + async sendNotification(notif: string): Promise { + return this.toWindow(notif); + } + + request(request: ProviderRPCRequest): Promise { + return this.requestProvider + .request(request) + .then(res => { + return { + result: JSON.stringify(res), + }; + }) + .catch(e => { + return { + error: JSON.stringify(e.message), + }; + }); + } + + getUIPath(page: string): string { + return GetUIPath(page, this.namespace); + } + + // Massa-specific methods + async getBalance(address: string): Promise { + return this.api.getBalance(address); + } + + async getTransactionStatus(opId: string): Promise { + return this.api.getTransactionStatus(opId); + } + + async createTransaction( + from: string, + to: string, + amount: string, + options: MassaTransactionOptions = {} + ): Promise { + return this.transactionHandler.createTransaction(from, to, amount, options); + } + + async sendTransaction( + from: string, + to: string, + amount: string, + options: MassaTransactionOptions = {} + ): Promise { + return this.transactionHandler.sendTransaction(from, to, amount, options); + } + + async getTransactionInfo(opId: string): Promise { + return this.transactionHandler.getTransactionInfo(opId); + } + + async estimateFee(): Promise { + return this.transactionHandler.estimateFee(); + } +} diff --git a/packages/extension/src/providers/massa/inject.ts b/packages/extension/src/providers/massa/inject.ts new file mode 100644 index 000000000..5353e3127 --- /dev/null +++ b/packages/extension/src/providers/massa/inject.ts @@ -0,0 +1,157 @@ +import EventEmitter from 'eventemitter3'; +import { + ProviderInterface, + ProviderName, + ProviderType, + ProviderOptions, + SendMessageHandler, +} from '@/types/provider'; +import { EnkryptWindow } from '@/types/globals'; +import { InjectedProvider, MassaAccount, MassaTransaction } from './types'; +import { SettingsType } from '@/libs/settings-state/types'; +import { InternalMethods } from '@/types/messenger'; + +export class Provider extends EventEmitter implements ProviderInterface, InjectedProvider { + name: ProviderName; + type: ProviderType; + version = __VERSION__; + connected = false; + sendMessageHandler: SendMessageHandler; + + constructor(options: ProviderOptions) { + super(); + this.name = options.name; + this.type = options.type; + this.sendMessageHandler = options.sendMessageHandler; + } + + async request(request: any): Promise { + const res = await this.sendMessageHandler( + this.name, + JSON.stringify(request) + ); + return res; + } + + async connect(): Promise { + const accounts = await this.request({ + method: 'massa_connect', + }); + this.connected = true; + return accounts; + } + + async disconnect(): Promise { + await this.request({ + method: 'massa_disconnect', + }); + this.connected = false; + } + + async signTransaction(transaction: MassaTransaction): Promise { + return this.request({ + method: 'massa_signTransaction', + params: [transaction], + }); + } + + async signMessage(message: string): Promise { + return this.request({ + method: 'massa_signMessage', + params: [message], + }); + } + + async sendTransaction(transaction: { + from: string; + to: string; + amount: string; + fee?: string; + data?: string; + validityStartPeriod?: number; + }): Promise<{ operationId: string; status: string; hash: string }> { + return this.request({ + method: 'massa_sendTransaction', + params: [transaction], + }); + } + + async getAccounts(): Promise { + return this.request({ + method: 'massa_getAccounts', + }); + } + + async getBalance(address: string): Promise { + return this.request({ + method: 'massa_getBalance', + params: [address], + }); + } + + async getNetwork(): Promise { + return this.request({ + method: 'massa_getNetwork', + }); + } + + isConnected(): boolean { + return this.connected; + } + + handleMessage(msg: string): void { + const { method, params } = JSON.parse(msg); + this.emit(method, params); + } +} + +const ProxyHandler = { + proxymethods: ['request', 'connect', 'disconnect', 'signTransaction', 'signMessage', 'sendTransaction', 'getAccounts', 'getBalance', 'getNetwork'], + ownKeys(target: Provider) { + return Object.keys(target).concat(this.proxymethods); + }, + set(target: Provider, name: keyof Provider, value: any) { + if (!this.ownKeys(target).includes(name)) this.proxymethods.push(name); + return Reflect.set(target, name, value); + }, + getOwnPropertyDescriptor(target: Provider, name: keyof Provider) { + return { + value: this.get(target, name), + configurable: true, + writable: false, + enumerable: true, + }; + }, + get(target: Provider, prop: keyof Provider) { + if (typeof target[prop] === 'function') { + return (target[prop] as () => any).bind(target); + } + return target[prop]; + }, + has(target: Provider, name: keyof Provider) { + return this.ownKeys(target).includes(name); + }, +}; + +const injectDocument = ( + document: EnkryptWindow | Window, + options: ProviderOptions, +): void => { + const provider = new Provider(options); + const proxiedProvider = new Proxy(provider, ProxyHandler); + document['enkrypt']['providers'][options.name] = provider; + document['massa'] = proxiedProvider; + + options + .sendMessageHandler( + ProviderName.enkrypt, + JSON.stringify({ method: InternalMethods.getSettings, params: [] }), + ) + .then((settings: SettingsType) => { + if (settings.massa?.inject) { + document['massa'] = proxiedProvider; + } + }); +}; + +export default injectDocument; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/libs/api.ts b/packages/extension/src/providers/massa/libs/api.ts new file mode 100644 index 000000000..f61282538 --- /dev/null +++ b/packages/extension/src/providers/massa/libs/api.ts @@ -0,0 +1,62 @@ +import { ProviderAPIInterface } from '@/types/provider'; +import { PublicProvider, JsonRpcPublicProvider, OperationStatus } from '@massalabs/massa-web3'; + +export default class MassaAPI extends ProviderAPIInterface { + private provider: PublicProvider; + public node: string; + + constructor(node: string) { + super(node); + this.node = node; + this.provider = JsonRpcPublicProvider.fromRPCUrl(node); + } + + public get api() { + return this; + } + + async init(): Promise { + } + + async getBalance(address: string): Promise { + const [account] = await this.provider.balanceOf([address], false); + if (!account) return '0'; + return account.balance.toString(); + } + + async getMinimalFee(): Promise { + try { + const networkInfo = await this.provider.networkInfos(); + return networkInfo.minimalFee.toString(); + } catch (error) { + console.error('Error getting minimal fee:', error); + // Return a default minimal fee if network info is not available + return '10000000'; // 0.01 MAS in base units (9 decimals) + } + } + + async getTransactionStatus(opId: string): Promise { + try { + return this.provider.getOperationStatus(opId); + } catch (error) { + console.error('Error getting operation:', error); + return null; + } + } + + async getNodeStatus(): Promise { + return this.provider.getNodeStatus(); + } + + async getOperationsByAddress(address: string): Promise { + try { + // const operations = await this.client.publicApi().getOperations([address]); + // return operations || []; + console.warn("not available for massa") + return []; + } catch (error) { + console.error('Error getting operations:', error); + return []; + } + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/massa/libs/blockies.ts b/packages/extension/src/providers/massa/libs/blockies.ts new file mode 100644 index 000000000..e83e6caf5 --- /dev/null +++ b/packages/extension/src/providers/massa/libs/blockies.ts @@ -0,0 +1,64 @@ +export default function createIcon(address: string, options: any = {}): string { + try { + if (!address || typeof address !== 'string') { + throw new Error('Invalid address provided'); + } + + // Use the address as seed for blockies + const seed = address.toLowerCase(); + + // Simple blockies implementation + const size = options.size || 8; + const scale = options.scale || 4; + const color = options.color || '#000000'; + const bgcolor = options.bgcolor || '#FFFFFF'; + + // Generate a simple pattern based on the address + let hash = 0; + for (let i = 0; i < seed.length; i++) { + hash = ((hash << 5) - hash + seed.charCodeAt(i)) & 0xffffffff; + } + + const pattern = []; + for (let i = 0; i < size * size; i++) { + hash = ((hash << 13) - hash) & 0xffffffff; + pattern.push(hash % 2); + } + + // Create SVG with proper encoding + let svg = ``; + svg += ``; + + for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + if (pattern[i * size + j]) { + svg += ``; + } + } + } + + svg += ''; + + // Use a more robust base64 encoding + let base64; + if (typeof btoa !== 'undefined') { + base64 = btoa(svg); + } else { + // Fallback for environments where btoa is not available + base64 = Buffer.from(svg, 'utf8').toString('base64'); + } + + return `data:image/svg+xml;base64,${base64}`; + } catch (error) { + console.error('Error creating Massa identicon:', error); + // Return a default icon - a simple white square + const defaultSvg = ''; + let defaultBase64; + if (typeof btoa !== 'undefined') { + defaultBase64 = btoa(defaultSvg); + } else { + defaultBase64 = Buffer.from(defaultSvg, 'utf8').toString('base64'); + } + return `data:image/svg+xml;base64,${defaultBase64}`; + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/massa/libs/message-handler.ts b/packages/extension/src/providers/massa/libs/message-handler.ts new file mode 100644 index 000000000..2a1564917 --- /dev/null +++ b/packages/extension/src/providers/massa/libs/message-handler.ts @@ -0,0 +1,71 @@ +import { JsonRPCClient } from '@massalabs/massa-web3/dist/esm/client/jsonRPCClient'; +import { MassaMessage } from '../types'; + +export interface IMessageData { + amount: bigint; + recipientAddress: string; + senderAddress: string; + data: Uint8Array; +} + +export class MessageHandler { + private client: JsonRPCClient; + + constructor(client: JsonRPCClient) { + this.client = client; + } + + async createMessage( + from: string, + to: string, + amount: string, + data: string = '' + ): Promise { + return { + amount: BigInt(amount), + recipientAddress: to, + senderAddress: from, + data: data ? new TextEncoder().encode(data) : new Uint8Array(0), + }; + } + + async signMessage( + message: IMessageData, + privateKey: string + ): Promise { + // TODO: Implement actual message signing + return `sig_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + async verifyMessage( + message: IMessageData, + signature: string, + publicKey: string + ): Promise { + try { + // TODO: Implement actual message verification + return true; + } catch (error) { + console.error('Error verifying message:', error); + return false; + } + } + + async getMessageInfo(messageId: string): Promise { + try { + // TODO: Implement actual message info retrieval + return { + id: messageId, + sender: '', + recipient: '', + amount: '0', + data: '', + timestamp: Date.now(), + status: false, + }; + } catch (error) { + console.error('Error getting message info:', error); + return null; + } + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/massa/libs/transaction-handler.ts b/packages/extension/src/providers/massa/libs/transaction-handler.ts new file mode 100644 index 000000000..5be86c1d0 --- /dev/null +++ b/packages/extension/src/providers/massa/libs/transaction-handler.ts @@ -0,0 +1,46 @@ +import { OperationOptions, Provider } from '@massalabs/massa-web3'; + + +export class TransactionHandler { + private provider: Provider; + + constructor(provider: Provider) { + this.provider = provider; + } + + // async createTransaction( + // from: string, + // to: string, + // amount: string, + // options: MassaTransactionOptions = {} + // ): Promise { + // const { fee = '1000000', data = '', validityStartPeriod = 0 } = options; + // return { + // fee: BigInt(fee), + // amount: BigInt(amount), + // recipientAddress: to, + // senderAddress: from, + // data: data ? new TextEncoder().encode(data) : new Uint8Array(0), + // validityStartPeriod, + // }; + // } + + async sendTransaction( + to: string, + amount: string, + options: OperationOptions = {} + ): Promise { + const operation = await this.provider.transfer( to, BigInt(amount),options); + return operation.id; + } + + async getTransactionInfo(opId: string): Promise { + // TODO: Implement actual transaction info retrieval + return { id: opId, status: 'pending' }; + } + + async estimateFee(): Promise { + // Massa has a fixed fee structure for now + return '1000000'; + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/index.ts b/packages/extension/src/providers/massa/methods/index.ts new file mode 100644 index 000000000..9488d9d44 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/index.ts @@ -0,0 +1,23 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface } from '@/types/provider'; +import massa_requestAccounts from './massa_requestAccounts'; +import massa_connect from './massa_connect'; +import massa_getAccounts from './massa_getAccounts'; +import massa_getBalance from './massa_getBalance'; +import massa_getNetwork from './massa_getNetwork'; +import massa_sendTransaction from './massa_sendTransaction'; + +export default (provider: BackgroundProviderInterface): MiddlewareFunction[] => { + return [ + massa_requestAccounts, + massa_connect, + massa_getAccounts, + massa_getBalance, + massa_getNetwork, + massa_sendTransaction, + async (request, response, next) => { + // Add any additional Massa-specific middleware logic here + return next(); + }, + ]; +}; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_connect.ts b/packages/extension/src/providers/massa/methods/massa_connect.ts new file mode 100644 index 000000000..cf6475636 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_connect.ts @@ -0,0 +1,20 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { CallbackFunction } from '@enkryptcom/types'; +import massa_requestAccounts from './massa_requestAccounts'; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_connect') return next(); + else { + // Alias for massa_requestAccounts + payload.method = 'massa_requestAccounts'; + return massa_requestAccounts.call(this, payload, res, next); + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_getAccounts.ts b/packages/extension/src/providers/massa/methods/massa_getAccounts.ts new file mode 100644 index 000000000..d4aae91fb --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_getAccounts.ts @@ -0,0 +1,34 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { CallbackFunction } from '@enkryptcom/types'; +import { SignerType } from '@enkryptcom/types'; +import PublicKeyRing from '@/libs/keyring/public-keyring'; +import { getNetworkByName } from '@/libs/utils/networks'; +import { NetworkNames } from '@enkryptcom/types'; +import { MassaNetwork } from '../networks/mainnet'; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_getAccounts') return next(); + else { + try { + const publicKeyring = new PublicKeyRing(); + const accounts = await publicKeyring.getAccounts([SignerType.ed25519]); + + const network = getNetworkByName(NetworkNames.Massa) as MassaNetwork; + + const massaAccounts = accounts.map(acc => network.displayAddress(acc.address)); + + res(null, massaAccounts); + } catch (error) { + console.error('Error in massa_getAccounts:', error); + res(null, []); + } + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_getBalance.ts b/packages/extension/src/providers/massa/methods/massa_getBalance.ts new file mode 100644 index 000000000..167b35372 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_getBalance.ts @@ -0,0 +1,35 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { CallbackFunction } from '@enkryptcom/types'; +import { getNetworkByName } from '@/libs/utils/networks'; +import { NetworkNames } from '@enkryptcom/types'; +import { MassaNetwork } from '../networks/mainnet'; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_getBalance') return next(); + else { + try { + const address = payload.params?.[0]; + if (!address) { + res(new Error('Address parameter is required'), null); + return; + } + + const network = getNetworkByName(NetworkNames.Massa) as MassaNetwork; + const api = await network.api(); + const balance = await api.getBalance(address); + + res(null, balance); + } catch (error) { + console.error('Error in massa_getBalance:', error); + res(error, null); + } + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_getNetwork.ts b/packages/extension/src/providers/massa/methods/massa_getNetwork.ts new file mode 100644 index 000000000..092bea7e4 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_getNetwork.ts @@ -0,0 +1,30 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { CallbackFunction } from '@enkryptcom/types'; +import DomainState from '@/libs/domain-state'; +import { NetworkNames } from '@enkryptcom/types'; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_getNetwork') return next(); + else { + try { + const domainState = new DomainState(); + const selectedNetwork = await domainState.getSelectedNetWork(); + + // Default to mainnet if no network is selected + const network = selectedNetwork || NetworkNames.Massa; + + res(null, network); + } catch (error) { + console.error('Error in massa_getNetwork:', error); + res(null, NetworkNames.Massa); + } + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_requestAccounts.ts b/packages/extension/src/providers/massa/methods/massa_requestAccounts.ts new file mode 100644 index 000000000..30bfb5611 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_requestAccounts.ts @@ -0,0 +1,118 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { CallbackFunction } from '@enkryptcom/types'; +import { getCustomError } from '@/libs/error'; +import { ErrorCodes } from '@/providers/ethereum/types'; +import openOnboard from '@/libs/utils/open-onboard'; +import { SignerType } from '@enkryptcom/types'; +import { NetworkNames } from '@enkryptcom/types'; +import { getNetworkByName } from '@/libs/utils/networks'; +import { MassaNetwork } from '../networks/mainnet'; +import { getAccountsByNetworkName } from '@/libs/utils/accounts'; +import { fromBase } from '@enkryptcom/utils'; +import DomainState from '@/libs/domain-state'; +import PublicKeyRing from '@/libs/keyring/public-keyring'; + +let isAccountAccessPending = false; +const pendingPromises: Array<{ + payload: ProviderRPCRequest; + res: CallbackFunction; +}> = []; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_requestAccounts') return next(); + else { + if (isAccountAccessPending) { + pendingPromises.push({ + payload, + res, + }); + return; + } + isAccountAccessPending = true; + const handleRemainingPromises = () => { + isAccountAccessPending = false; + if (pendingPromises.length) { + const promi = pendingPromises.pop(); + if (promi) handleAccountAccess(promi.payload, promi.res); + } + }; + const handleAccountAccess = ( + _payload: ProviderRPCRequest, + _res: CallbackFunction, + ) => { + if (_payload.options && _payload.options.domain) { + isAccountAccessPending = true; + const isInitialized = this.KeyRing.isInitialized(); + if (!isInitialized) { + _res(getCustomError('Enkrypt not initialized')); + openOnboard(); + return handleRemainingPromises(); + } + const domainState = new DomainState(); + const publicKeyring = new PublicKeyRing(); + + const selectedAddressPromise = domainState.getSelectedAddress(); + const selectedNetworkPromise = domainState.getSelectedNetWork(); + const accountsPromise = publicKeyring.getAccounts([SignerType.ed25519]); + + Promise.all([ + selectedAddressPromise, + selectedNetworkPromise, + accountsPromise, + ]).then(([selectedAddress, selectedNetwork, accounts]) => { + const selectedNetworkName = Object.values(NetworkNames).find( + n => n === selectedNetwork, + ); + + const account = accounts.find(acc => acc.address === selectedAddress); + + const network = getNetworkByName(selectedNetworkName || NetworkNames.Massa) as MassaNetwork; + + return { + selectedNetwork: network, + selectedAccountAddress: network.displayAddress( + account?.address || '', + ), + accounts: accounts.map(acc => { + return { + address: network.displayAddress(acc.address), + publicKey: acc.publicKey, + name: acc.name, + type: acc.signerType, + }; + }), + }; + }).then(async (accountData) => { + const api = await accountData.selectedNetwork.api(); + const balance = await api.getBalance(accountData.selectedAccountAddress); + + const massaAccount = { + address: accountData.selectedAccountAddress, + balance: fromBase(balance, accountData.selectedNetwork.decimals), + activeRolls: 0, // TODO: Implement rolls fetching + candidateRolls: 0, // TODO: Implement rolls fetching + }; + + _res(null, [massaAccount]); + handleRemainingPromises(); + }).catch((error) => { + console.error('Error in massa_requestAccounts:', error); + _res(getCustomError(ErrorCodes.userRejected)); + handleRemainingPromises(); + }); + } else { + _res(getCustomError(ErrorCodes.userRejected)); + handleRemainingPromises(); + } + }; + handleAccountAccess(payload, res); + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/methods/massa_sendTransaction.ts b/packages/extension/src/providers/massa/methods/massa_sendTransaction.ts new file mode 100644 index 000000000..a76ad0ec1 --- /dev/null +++ b/packages/extension/src/providers/massa/methods/massa_sendTransaction.ts @@ -0,0 +1,96 @@ +import { MiddlewareFunction } from '@enkryptcom/types'; +import { BackgroundProviderInterface, ProviderRPCRequest } from '@/types/provider'; +import { getCustomError } from '@/libs/error'; +import { WindowPromise } from '@/libs/window-promise'; +import { getNetworkByName } from '@/libs/utils/networks'; +import { NetworkNames } from '@enkryptcom/types'; +import { JsonRpcProvider, Account, OperationOptions } from '@massalabs/massa-web3'; + +const method: MiddlewareFunction = async function ( + this: BackgroundProviderInterface, + payload: ProviderRPCRequest, + res, + next, +): Promise { + if (payload.method !== 'massa_sendTransaction') return next(); + else { + if (!payload.params || payload.params.length < 1) { + return res( + getCustomError( + 'massa_sendTransaction: invalid request not enough params', + ), + ); + } + + const txParams = payload.params[0] as { + from: string; + to: string; + amount: string; + fee?: string; + }; + + if (!txParams.from || !txParams.to || !txParams.amount) { + return res( + getCustomError( + 'massa_sendTransaction: missing required parameters (from, to, amount)', + ), + ); + } + + try { + // Get the account to sign the transaction + + const account = await this.KeyRing.getAccount(txParams.from) + if (!account) { + return res( + getCustomError( + 'massa_sendTransaction: account not found', + ), + ); + } + + // Get network information + const network = await getNetworkByName(NetworkNames.Massa); + + // Fetch the account balance + const api = await network?.api(); + const balance = await api?.getBalance(account.address); + + // Create account object with balance information + const accountWithBalance = { + ...account, + balance: balance, // Balance in base units + }; + + // Create transaction options + const options: OperationOptions = { + fee: txParams.fee ? BigInt(txParams.fee) : undefined, + }; + + // Create a window promise for user confirmation + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.massaSendTransaction.path), + JSON.stringify({ + ...payload, + params: [txParams, accountWithBalance, network, options], + }), + true, + ) + .then(async ({ error, result }) => { + if (error) return res(error); + res(null, result as string); + }) + .catch((error) => { + console.error('Error in massa_sendTransaction:', error); + res(getCustomError('User rejected transaction')); + }); + } catch (error) { + console.error('Error in massa_sendTransaction:', error); + res(getCustomError(`Transaction failed: ${error.message}`)); + } + } +}; + +export default method; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/networks/buildnet.ts b/packages/extension/src/providers/massa/networks/buildnet.ts new file mode 100644 index 000000000..8986a594a --- /dev/null +++ b/packages/extension/src/providers/massa/networks/buildnet.ts @@ -0,0 +1,56 @@ +import { NetworkNames } from '@enkryptcom/types'; +import { BaseNetwork } from '@/types/base-network'; +import MassaAPI from '../libs/api'; +import { SignerType } from '@enkryptcom/types'; +import { ProviderName } from '@/types/provider'; +import createIcon from '../libs/blockies'; +import { CHAIN_ID, PublicApiUrl } from '@massalabs/massa-web3'; +import { MassaNetworkOptions } from '../types'; +import icon from './icons/Massa_logo.webp'; + +const buildnetOptions: MassaNetworkOptions = { + name: NetworkNames.MassaBuildnet, + name_long: 'Massa Buildnet', + homePage: 'https://massa.net/', + blockExplorerTX: 'https://www.massexplo.com/tx/[[txHash]]?network=buildnet', + blockExplorerAddr: 'https://www.massexplo.com/address/[[address]]?network=buildnet', + isTestNetwork: true, + currencyName: 'MAS', + currencyNameLong: 'Massa', + node: PublicApiUrl.Buildnet, + icon, + decimals: 9, + signer: [SignerType.ed25519mas], + displayAddress: (address: string) => address, + provider: ProviderName.massa, + identicon: createIcon, + basePath: "m/44'/632'", + chainId: CHAIN_ID.Buildnet, + api: async () => { + const api = new MassaAPI(PublicApiUrl.Buildnet); + await api.init(); + return api; + }, +}; + +class MassaNetwork extends BaseNetwork { + constructor(options: MassaNetworkOptions) { + super(options); + } + + async getAllTokens(): Promise { + return []; + } + + async getAllTokenInfo(): Promise { + return []; + } + + async getAllActivity(): Promise { + return []; + } +} + +const buildnet = new MassaNetwork(buildnetOptions); + +export default buildnet; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/networks/icons/Massa_logo.webp b/packages/extension/src/providers/massa/networks/icons/Massa_logo.webp new file mode 100644 index 000000000..8ab091f59 Binary files /dev/null and b/packages/extension/src/providers/massa/networks/icons/Massa_logo.webp differ diff --git a/packages/extension/src/providers/massa/networks/index.ts b/packages/extension/src/providers/massa/networks/index.ts new file mode 100644 index 000000000..36e794fa2 --- /dev/null +++ b/packages/extension/src/providers/massa/networks/index.ts @@ -0,0 +1,9 @@ +import mainnet from './mainnet'; +import buildnet from './buildnet'; + + + +export default { + mainnet, + buildnet, +}; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/networks/mainnet.ts b/packages/extension/src/providers/massa/networks/mainnet.ts new file mode 100644 index 000000000..403d38b5f --- /dev/null +++ b/packages/extension/src/providers/massa/networks/mainnet.ts @@ -0,0 +1,95 @@ +import { NetworkNames } from '@enkryptcom/types'; +import { BaseNetwork } from '@/types/base-network'; +import MassaAPI from '../libs/api'; +import { SignerType } from '@enkryptcom/types'; +import { ProviderName } from '@/types/provider'; +import createIcon from '../libs/blockies'; +import { CHAIN_ID, PublicApiUrl } from '@massalabs/massa-web3'; +import { MassaNetworkOptions } from '../types'; +import { ActivityStatus, ActivityType } from '@/types/activity'; +import { OperationStatus, Operation } from '@massalabs/massa-web3'; +import icon from './icons/Massa_logo.webp'; + + +const mainnetOptions: MassaNetworkOptions = { + name: NetworkNames.Massa, + name_long: 'Massa', + homePage: 'https://massa.net/', + blockExplorerTX: 'https://explorer.massa.net/operation/[[txHash]]', + blockExplorerAddr: 'https://explorer.massa.net/address/[[address]]', + isTestNetwork: false, + currencyName: 'MAS', + currencyNameLong: 'Massa', + node: PublicApiUrl.Mainnet, + icon, + decimals: 9, + signer: [SignerType.ed25519mas], + displayAddress: (address: string) => address, + provider: ProviderName.massa, + identicon: createIcon, + basePath: "m/44'/632'", + coingeckoID: 'massa', + chainId: CHAIN_ID.Mainnet, + api: async () => { + const api = new MassaAPI(PublicApiUrl.Mainnet); + await api.init(); + return api; + }, +}; + +class MassaNetwork extends BaseNetwork { + private selectedAddress: string = ''; + + constructor(options: MassaNetworkOptions) { + super(options); + } + + setSelectedAddress(address: string) { + this.selectedAddress = address; + } + + async getAllTokens(): Promise { + return []; + } + + async getAllTokenInfo(): Promise { + return []; + } + + async getAllActivity(): Promise { + try { + const api = await this.api() as MassaAPI; + const operations = await api.getOperationsByAddress(this.selectedAddress); + // return operations.map((op: Operation) => ({ + // nonce: op.id, + // from: op.sender_address, + // to: op.recipient_address, + // isIncoming: op.recipient_address === this.selectedAddress, + // network: this.name, + // rawInfo: op, + // status: op.status === OperationStatus.Success || op.status === OperationStatus.SpeculativeSuccess + // ? ActivityStatus.success + // : ActivityStatus.failed, + // timestamp: op.slot * 16000, // Approximate timestamp based on slot + // value: op.amount ? op.amount.toString() : '0', + // transactionHash: op.id, + // type: ActivityType.transaction, + // token: { + // decimals: this.decimals, + // icon: this.icon, + // name: this.currencyNameLong, + // symbol: this.currencyName, + // price: '0', // TODO: Implement price fetching + // }, + // })); + return []; + } catch (error) { + console.error('Error fetching activity:', error); + return []; + } + } +} + +const mainnet = new MassaNetwork(mainnetOptions); + +export default mainnet; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/types/index.ts b/packages/extension/src/providers/massa/types/index.ts new file mode 100644 index 000000000..a0e5d481f --- /dev/null +++ b/packages/extension/src/providers/massa/types/index.ts @@ -0,0 +1,95 @@ +import { NetworkName} from '@massalabs/massa-web3'; +import { BaseNetworkOptions } from '@/types/base-network'; +import { CHAIN_ID } from '@massalabs/massa-web3'; +import { ToTokenData } from '@/ui/action/types/token'; +import { EnkryptAccount } from '@enkryptcom/types'; +import { BaseNetwork } from '@/types/base-network'; + +export const MassaNetworks = NetworkName; + +export interface MassaNetworkOptions extends BaseNetworkOptions { + chainId?: typeof CHAIN_ID[keyof typeof CHAIN_ID]; +} + +export interface MassaRawInfo { + hash: string; + blockNumber: number; + timestamp: number; + from: string; + to: string; + value: string; + status: boolean; +} + +export interface MassaTransaction { + fee: bigint; + amount: bigint; + recipientAddress: string; +} + +export interface MassaMessage { + id: string; + sender: string; + recipient: string; + amount: string; + data: string; + timestamp: number; + status: boolean; +} + +export interface MassaSignInInput { + address: string; + privateKey: string; +} + +export interface MassaSignInOutput { + address: string; + publicKey: string; +} + +export interface MassaAccount{ + address: string; + balance: string; + activeRolls: number; + candidateRolls: number; +} + +export interface TxFeeInfo { + nativeValue: string; + fiatValue: string; + nativeSymbol: string; + fiatSymbol: string; +} + +export interface SendTransactionDataType { + from: string; + value: string; + to: string; + data: `0x${string}`; +} + +export interface VerifyTransactionParams { + fromAddress: string; + fromAddressName: string; + toAddress: string; + toToken: ToTokenData; + txFee: TxFeeInfo; + TransactionData: SendTransactionDataType; +} + +export interface SignerTransactionOptions { + transaction: Buffer; + account: EnkryptAccount; + network: BaseNetwork; +} + +export interface InjectedProvider { + connect(): Promise; + disconnect(): Promise; + signTransaction(transaction: MassaTransaction): Promise; + signMessage(message: string): Promise; + getAccounts(): Promise; + getBalance(address: string): Promise; + getNetwork(): Promise; + isConnected(): boolean; +} \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/accounts/index.vue b/packages/extension/src/providers/massa/ui/accounts/index.vue new file mode 100644 index 000000000..5f0994b54 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/accounts/index.vue @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/index.ts b/packages/extension/src/providers/massa/ui/index.ts new file mode 100644 index 000000000..abb9f3921 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/index.ts @@ -0,0 +1,9 @@ +import { ProviderName, UIExportOptions } from '@/types/provider'; +import getRoutes from './routes'; + +const uiExport: UIExportOptions = { + providerName: ProviderName.massa, + routes: getRoutes(ProviderName.massa), +}; + +export default uiExport; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/libs/signer.ts b/packages/extension/src/providers/massa/ui/libs/signer.ts new file mode 100644 index 000000000..b35ebf814 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/libs/signer.ts @@ -0,0 +1,79 @@ +import { InternalMethods, InternalOnMessageResponse } from '@/types/messenger'; +import { SignerTransactionOptions } from '../../types'; +import HWwallet from '@enkryptcom/hw-wallets'; +import { HWwalletType } from '@enkryptcom/types'; +import { getCustomError } from '@/libs/error'; +import { bufferToHex, hexToBuffer } from '@enkryptcom/utils'; +import sendUsingInternalMessengers from '@/libs/messenger/internal-messenger'; + +/** + * Sign a transaction + */ +const TransactionSigner = ( + options: SignerTransactionOptions, +): Promise => { + const { transaction, network, account } = options; + if (account.isHardware) { + const hwwallets = new HWwallet(); + return hwwallets + .signTransaction({ + transaction: { massaTx: transaction }, + networkName: network.name, + pathIndex: account.pathIndex.toString(), + pathType: { + basePath: account.basePath, + path: account.HWOptions!.pathTemplate, + }, + wallet: account.walletType as unknown as HWwalletType, + }) + .then((rpcsig: string) => { + return hexToBuffer(rpcsig); + }) + .catch(e => { + return Promise.reject({ + error: getCustomError(e.message), + }); + }); + } else { + const msgHash = bufferToHex(transaction); + return sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [msgHash, account], + }).then(res => { + if (res.error) { + return Promise.reject(res); + } else { + return hexToBuffer(JSON.parse(res.result!)); + } + }); + } +}; + +/** + * Sign a message + */ +const MessageSigner = ( + options: { payload: string; account: any; network: any }, +): Promise => { + const { account, payload } = options; + if (account.isHardware) { + return Promise.reject({ + error: getCustomError('massa-hw-sign: hw wallets not supported for message signing'), + }); + } else { + return sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [payload, account], + }).then(res => { + if (res.error) return res; + return { + result: JSON.stringify({ + signature: JSON.parse(res.result!), + message: payload, + }), + }; + }); + } +}; + +export { TransactionSigner, MessageSigner }; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/routes/index.ts b/packages/extension/src/providers/massa/ui/routes/index.ts new file mode 100644 index 000000000..21e4359a9 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/routes/index.ts @@ -0,0 +1,12 @@ +import { RouteRecordRaw } from 'vue-router'; +import RouteNames from './names'; + +const routes = Object.assign({}, RouteNames); + +export default (namespace: string): RouteRecordRaw[] => { + return Object.values(routes).map(route => { + route.path = `/${namespace}/${route.path}`; + route.name = `${namespace}-${String(route.name)}`; + return route; + }); +}; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/routes/names.ts b/packages/extension/src/providers/massa/ui/routes/names.ts new file mode 100644 index 000000000..c7ecd23ca --- /dev/null +++ b/packages/extension/src/providers/massa/ui/routes/names.ts @@ -0,0 +1,31 @@ +import { RouteRecordRaw } from 'vue-router'; + +const routes: Record = { + send: { + path: 'send', + name: 'massa-send', + component: () => import('../send-transaction/index.vue'), + }, + massaSendTransaction: { + path: 'send-transaction', + name: 'massa-send-transaction', + component: () => import('../send-transaction/index.vue'), + }, + verify: { + path: 'verify', + name: 'massa-verify', + component: () => import('../send-transaction/verify-transaction/index.vue'), + }, + accounts: { + path: 'accounts', + name: 'massa-accounts', + component: () => import('../accounts/index.vue'), + }, + settings: { + path: 'settings', + name: 'massa-settings', + component: () => import('../settings/index.vue'), + }, +}; + +export default routes; \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/send-transaction/components/send-address-input.vue b/packages/extension/src/providers/massa/ui/send-transaction/components/send-address-input.vue new file mode 100644 index 000000000..f0d4f9636 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/send-transaction/components/send-address-input.vue @@ -0,0 +1,216 @@ + + + + + \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/send-transaction/index.vue b/packages/extension/src/providers/massa/ui/send-transaction/index.vue new file mode 100644 index 000000000..2ac49281e --- /dev/null +++ b/packages/extension/src/providers/massa/ui/send-transaction/index.vue @@ -0,0 +1,631 @@ + + + + + \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/massa/ui/send-transaction/verify-transaction/index.vue new file mode 100644 index 000000000..e1992d260 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/send-transaction/verify-transaction/index.vue @@ -0,0 +1,401 @@ + + + + + \ No newline at end of file diff --git a/packages/extension/src/providers/massa/ui/settings/index.vue b/packages/extension/src/providers/massa/ui/settings/index.vue new file mode 100644 index 000000000..87c38af00 --- /dev/null +++ b/packages/extension/src/providers/massa/ui/settings/index.vue @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/packages/extension/src/scripts/inject.ts b/packages/extension/src/scripts/inject.ts index 5fed1666d..c738bcc11 100644 --- a/packages/extension/src/scripts/inject.ts +++ b/packages/extension/src/scripts/inject.ts @@ -9,6 +9,7 @@ import PolkadotProvider from '@/providers/polkadot/inject'; import BitcoinProvider from '@/providers/bitcoin/inject'; import KadenaProvider from '@/providers/kadena/inject'; import SolanaProvider from '@/providers/solana/inject'; +import MassaProvider from '@/providers/massa/inject'; import { InternalMethods } from '@/types/messenger'; @@ -43,6 +44,11 @@ const loadInjectedProviders = () => { type: ProviderType.solana, sendMessageHandler: providerSendMessage, }); + MassaProvider(window, { + name: ProviderName.massa, + type: ProviderType.massa, + sendMessageHandler: providerSendMessage, + }); }; loadInjectedProviders(); diff --git a/packages/extension/src/types/base-network.ts b/packages/extension/src/types/base-network.ts index d876a0c99..3ca1af7a2 100644 --- a/packages/extension/src/types/base-network.ts +++ b/packages/extension/src/types/base-network.ts @@ -8,6 +8,7 @@ import { CoingeckoPlatform, SignerType, NetworkNames } from '@enkryptcom/types'; import { Activity } from './activity'; import { BaseToken } from './base-token'; import { BNType } from '@/providers/common/types'; +import MassaAPI from '../providers/massa/libs/api'; export interface SubNetworkOptions { id: string; @@ -38,7 +39,8 @@ export interface BaseNetworkOptions { | Promise | Promise | Promise - | Promise; + | Promise + | Promise; customTokens?: boolean; } @@ -80,7 +82,8 @@ export abstract class BaseNetwork { | Promise | Promise | Promise - | Promise; + | Promise + | Promise; public customTokens: boolean; constructor(options: BaseNetworkOptions) { diff --git a/packages/extension/src/types/provider.ts b/packages/extension/src/types/provider.ts index 0f831c71b..42afd561b 100644 --- a/packages/extension/src/types/provider.ts +++ b/packages/extension/src/types/provider.ts @@ -3,6 +3,7 @@ import type { InjectedProvider as PolkadotProvider } from '@/providers/polkadot/ import type { InjectedProvider as BitcoinProvider } from '@/providers/bitcoin/types'; import type { InjectedProvider as KadenaProvider } from '@/providers/kadena/types'; import type { InjectedProvider as SolanaProvider } from '@/providers/solana/types'; +import type { InjectedProvider as MassaProvider } from '@/providers/massa/types'; import EventEmitter from 'eventemitter3'; import { MiddlewareFunction, @@ -24,6 +25,7 @@ import { KadenaRawInfo, SOLRawInfo, } from './activity'; +import { OperationStatus } from '@massalabs/massa-web3'; export enum ProviderName { enkrypt = 'enkrypt', @@ -32,6 +34,7 @@ export enum ProviderName { polkadot = 'polkadot', kadena = 'kadena', solana = 'solana', + massa = 'massa', } export enum InternalStorageNamespace { keyring = 'KeyRing', @@ -68,6 +71,7 @@ export enum ProviderType { bitcoin, kadena, solana, + massa, } export type SendMessageHandler = ( @@ -144,6 +148,7 @@ export abstract class ProviderAPIInterface { | BTCRawInfo | KadenaRawInfo | SOLRawInfo + | OperationStatus | null >; } @@ -163,13 +168,15 @@ export { BitcoinProvider, KadenaProvider, SolanaProvider, + MassaProvider, }; export type Provider = | EthereumProvider | PolkadotProvider | BitcoinProvider | KadenaProvider - | SolanaProvider; + | SolanaProvider + | MassaProvider; export interface ProviderRequestOptions { url: string; diff --git a/packages/extension/src/ui/action/App.vue b/packages/extension/src/ui/action/App.vue index 4b1b6728e..429b165f2 100644 --- a/packages/extension/src/ui/action/App.vue +++ b/packages/extension/src/ui/action/App.vue @@ -211,6 +211,8 @@ const openBuyPage = () => { case NetworkNames.SyscoinNEVMTest: case NetworkNames.RolluxTest: return (currentNetwork.value as EvmNetwork).options.buyLink; + case NetworkNames.Massa: + return "https://www.massa.net/get-mas"; default: return `https://ccswap.myetherwallet.com/?to=${currentNetwork.value.displayAddress( accountHeaderData.value.selectedAccount!.address, @@ -316,11 +318,15 @@ const setNetwork = async (network: BaseNetwork) => { currentSubNetwork.value = ''; } const activeAccounts = await getAccountsByNetworkName(network.name); - + console.log("<>>>>>>>>>>> YOOLOOOOOOO activeAccounts", activeAccounts) const inactiveAccounts = await kr.getAccounts( getOtherSigners(network.signer), ); + console.log("<>>>>>>>>>>> YOOLOOOOOOO inactiveAccounts", inactiveAccounts) + const selectedAddress = await domainState.getSelectedAddress(); + console.log("<>>>>>>>>>>> YOOLOOOOOOO selectedAddress", selectedAddress) + let selectedAccount = activeAccounts[0]; if (selectedAddress) { const found = activeAccounts.find(acc => acc.address === selectedAddress); diff --git a/packages/extension/src/ui/action/views/network-activity/index.vue b/packages/extension/src/ui/action/views/network-activity/index.vue index b2f33a951..ba79e9099 100644 --- a/packages/extension/src/ui/action/views/network-activity/index.vue +++ b/packages/extension/src/ui/action/views/network-activity/index.vue @@ -79,6 +79,7 @@ import Swap, { } from '@enkryptcom/swap'; import EvmAPI from '@/providers/ethereum/libs/api'; import type Web3Eth from 'web3-eth'; +import { OperationStatus } from '@massalabs/massa-web3'; const props = defineProps({ network: { @@ -217,6 +218,15 @@ const handleActivityUpdate = (activity: Activity, info: any, timer: any) => { } else { return; /* Give the transaction more time to be mined */ } + } else if (props.network.provider === ProviderName.massa) { + if (!info) return; + const massaInfo = info as OperationStatus; + if (isActivityUpdating) return; + activity.status = (massaInfo === OperationStatus.Success || massaInfo === OperationStatus.SpeculativeSuccess) + ? ActivityStatus.success + : ActivityStatus.failed; + activity.rawInfo = massaInfo; + updateActivitySync(activity).then(() => updateVisibleActivity(activity)); } // If we're this far in then the transaction has reached a terminal status diff --git a/packages/extension/src/ui/action/views/send-transaction/index.vue b/packages/extension/src/ui/action/views/send-transaction/index.vue index 5e3e90ffe..4a796a729 100644 --- a/packages/extension/src/ui/action/views/send-transaction/index.vue +++ b/packages/extension/src/ui/action/views/send-transaction/index.vue @@ -12,6 +12,7 @@ import SendTransactionEVM from '@/providers/ethereum/ui/send-transaction/index.v import SendTransactionBTC from '@/providers/bitcoin/ui/send-transaction/index.vue'; import SendTransactionKadena from '@/providers/kadena/ui/send-transaction/index.vue'; import SendTransactionSolana from '@/providers/solana/ui/send-transaction/index.vue'; +import SendTransactionMassa from '@/providers/massa/ui/send-transaction/index.vue'; import { useRoute } from 'vue-router'; import { ProviderName } from '@/types/provider'; import { getNetworkByName } from '@/libs/utils/networks'; @@ -25,6 +26,7 @@ const sendLayouts: Record = { [ProviderName.bitcoin]: SendTransactionBTC, [ProviderName.kadena]: SendTransactionKadena, [ProviderName.solana]: SendTransactionSolana, + [ProviderName.massa]: SendTransactionMassa, [ProviderName.enkrypt]: null, }; diff --git a/packages/extension/src/ui/action/views/verify-transaction/index.vue b/packages/extension/src/ui/action/views/verify-transaction/index.vue index 874fdf894..5760dbe73 100644 --- a/packages/extension/src/ui/action/views/verify-transaction/index.vue +++ b/packages/extension/src/ui/action/views/verify-transaction/index.vue @@ -8,6 +8,7 @@ import VerifyTransactionEVM from '@/providers/ethereum/ui/send-transaction/verif import VerifyTransactionBTC from '@/providers/bitcoin/ui/send-transaction/verify-transaction/index.vue'; import VerifyTransactionKadena from '@/providers/kadena/ui/send-transaction/verify-transaction/index.vue'; import VerifyTransactionSolana from '@/providers/solana/ui/send-transaction/verify-transaction/index.vue'; +import VerifyTransactionMassa from '@/providers/massa/ui/send-transaction/verify-transaction/index.vue'; import { useRoute } from 'vue-router'; import { ProviderName } from '@/types/provider'; import { getNetworkByName } from '@/libs/utils/networks'; @@ -19,6 +20,7 @@ const sendLayouts: Record = { [ProviderName.bitcoin]: VerifyTransactionBTC, [ProviderName.kadena]: VerifyTransactionKadena, [ProviderName.solana]: VerifyTransactionSolana, + [ProviderName.massa]: VerifyTransactionMassa, [ProviderName.enkrypt]: null, }; const layout = shallowRef(); diff --git a/packages/extension/src/ui/provider-pages/routes.ts b/packages/extension/src/ui/provider-pages/routes.ts index 3c50b4f49..6ca72fb7c 100644 --- a/packages/extension/src/ui/provider-pages/routes.ts +++ b/packages/extension/src/ui/provider-pages/routes.ts @@ -6,6 +6,7 @@ import PolkadotUI from '@/providers/polkadot/ui'; import BitcoinUI from '@/providers/bitcoin/ui'; import KadenaUI from '@/providers/kadena/ui'; import SolanaUI from '@/providers/solana/ui'; +import MassaUI from '@/providers/massa/ui'; import EnkryptUI from './enkrypt'; const uiProviders = [ EthereumUI, @@ -14,6 +15,7 @@ const uiProviders = [ EnkryptUI, KadenaUI, SolanaUI, + MassaUI, ]; let uiRoutes: RouteRecordRaw[] = []; uiProviders.forEach(provider => { diff --git a/packages/keyring/package.json b/packages/keyring/package.json index dad1aca49..12327b1bb 100644 --- a/packages/keyring/package.json +++ b/packages/keyring/package.json @@ -25,6 +25,7 @@ "@enkryptcom/signer-bitcoin": "workspace:^", "@enkryptcom/signer-ethereum": "workspace:^", "@enkryptcom/signer-kadena": "workspace:^", + "@enkryptcom/signer-massa": "workspace:^", "@enkryptcom/signer-polkadot": "workspace:^", "@enkryptcom/storage": "workspace:^", "@enkryptcom/types": "workspace:^", diff --git a/packages/keyring/src/index.ts b/packages/keyring/src/index.ts index 29b8ca683..9200005a9 100644 --- a/packages/keyring/src/index.ts +++ b/packages/keyring/src/index.ts @@ -19,6 +19,7 @@ import { PolkadotSigner } from "@enkryptcom/signer-polkadot"; import { EthereumSigner } from "@enkryptcom/signer-ethereum"; import { BitcoinSigner } from "@enkryptcom/signer-bitcoin"; import { KadenaSigner } from "@enkryptcom/signer-kadena"; +import { MassaSigner } from "@enkryptcom/signer-massa"; import assert from "assert"; import configs from "./configs"; import { pathParser } from "./utils"; @@ -51,6 +52,7 @@ class KeyRing { [SignerType.secp256k1btc]: new BitcoinSigner(), [SignerType.ed25519kda]: new KadenaSigner(), [SignerType.ed25519sol]: new KadenaSigner(), + [SignerType.ed25519mas]: new MassaSigner(), }; } diff --git a/packages/keyring/src/utils.ts b/packages/keyring/src/utils.ts index 9bbca8da7..779033717 100644 --- a/packages/keyring/src/utils.ts +++ b/packages/keyring/src/utils.ts @@ -14,5 +14,8 @@ export const pathParser = ( if (type === SignerType.ed25519sol) { return `${basePath}/${index}'/0`; // Solana uses hardened paths } + if (type === SignerType.ed25519mas) { + return `${basePath}/${index}'/0'`; // Massa uses hardened paths with BIP-44 coin type 632 + } return `${basePath}/${index}`; }; diff --git a/packages/signers/massa/package.json b/packages/signers/massa/package.json new file mode 100644 index 000000000..cb4c35f97 --- /dev/null +++ b/packages/signers/massa/package.json @@ -0,0 +1,60 @@ +{ + "name": "@enkryptcom/signer-massa", + "version": "0.0.1", + "description": "Massa blockchain signer", + "type": "module", + "main": "src/index.ts", + "module": "src/index.ts", + "publishConfig": { + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean", + "lint": "prettier --write .", + "test": "vitest run" + }, + "engines": { + "node": ">=14.15.0" + }, + "dependencies": { + "@enkryptcom/types": "workspace:^", + "@enkryptcom/utils": "workspace:^", + "@massalabs/massa-web3": "^5.2.1-dev", + "bip39": "^3.1.0", + "tweetnacl": "^1.0.3", + "varint": "^6.0.0" + }, + "devDependencies": { + "@polkadot/util-crypto": "^13.5.1", + "@types/hdkey": "^2.1.0", + "@types/node": "^22.15.24", + "@types/varint": "^6", + "@typescript-eslint/eslint-plugin": "^8.33.0", + "@typescript-eslint/parser": "^8.33.0", + "eslint": "^9.27.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.1.5", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-module-resolver": "^1.5.0", + "prettier": "^3.5.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tsup": "^8.5.0", + "typescript": "^5.8.3", + "typescript-eslint": "8.33.0", + "vitest": "^3.1.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/enkryptcom/enKrypt/tree/main/packages/signers/massa" + }, + "keywords": [], + "author": "ae.studio", + "license": "MIT" +} diff --git a/packages/signers/massa/src/index.ts b/packages/signers/massa/src/index.ts new file mode 100644 index 000000000..e2dd92def --- /dev/null +++ b/packages/signers/massa/src/index.ts @@ -0,0 +1,65 @@ +import { + KeyPair, + MnemonicWithExtraWord, + SignerInterface, +} from "@enkryptcom/types"; +import { bufferToHex, hexToBuffer } from "@enkryptcom/utils"; +import { mnemonicToSeedSync } from "bip39"; +import { sign as tweetSign } from "tweetnacl"; +import { derivePath } from "./libs/ed25519"; +import { Address, PublicKey, PrivateKey, Signature } from "@massalabs/massa-web3"; + +export class MassaSigner implements SignerInterface { + async generate( + mnemonic: MnemonicWithExtraWord, + derivationPath = "", + ): Promise { + console.log('MassaSigner generate', derivationPath); + const seed = bufferToHex( + mnemonicToSeedSync(mnemonic.mnemonic, mnemonic.extraWord), + true, + ); + const keys = derivePath(derivationPath, seed); + const keyPair = tweetSign.keyPair.fromSeed(keys.key); + + // Prefix the 32-byte private key with version bytes for Massa + const versionBytes = Buffer.from([0]); // KEYS_VERSION_NUMBER = 0 + const privateKeyBytes = Buffer.concat([versionBytes, Buffer.from(keyPair.secretKey.slice(0, 32))]); + const privateKey = PrivateKey.fromBytes(privateKeyBytes); + const publicKey = await PublicKey.fromPrivateKey(privateKey) + const address = Address.fromPublicKey(publicKey); + console.log('address', address.toString()); + console.log('privateKey', privateKey.toString()); + console.log('publicKey', publicKey.toString()); + return { + address: address.toString(), + privateKey: privateKey.toString(), + publicKey: publicKey.toString(), + }; + } + + async verify( + msgHash: string, + sig: string, + publicKey: string, + ): Promise { + try { + console.log('MassaSigner verify publicKey', publicKey); + + const massaPublicKey = PublicKey.fromString(publicKey); + const signature = Signature.fromBytes(hexToBuffer(sig)); + return massaPublicKey.verify(hexToBuffer(msgHash), signature); + } catch (error) { + console.error('Massa signature verification failed:', error); + return false; + } + } + + async sign(msgHash: string, keyPair: KeyPair): Promise { + console.log('MassaSigner sign keyPair', keyPair); + const privateKey = PrivateKey.fromString(keyPair.privateKey); + const sig = await privateKey.sign(hexToBuffer(msgHash)); + + return bufferToHex(sig.toBytes()); + } +} \ No newline at end of file diff --git a/packages/signers/massa/src/libs/ed25519.ts b/packages/signers/massa/src/libs/ed25519.ts new file mode 100644 index 000000000..b0ddea7fa --- /dev/null +++ b/packages/signers/massa/src/libs/ed25519.ts @@ -0,0 +1,81 @@ +import { createHmac } from "crypto"; +import { sign } from "tweetnacl"; + +const ED25519_CURVE = "ed25519 seed"; +const HARDENED_OFFSET = 0x80000000; + +type Hex = string; +type Path = string; + +type Keys = { + key: Buffer; + chainCode: Buffer; +}; + +const getMasterKeyFromSeed = (seed: Hex): Keys => { + const hmac = createHmac("sha512", Buffer.from(ED25519_CURVE, "utf8")); + const I = hmac.update(Buffer.from(seed, "hex")).digest(); + const IL = I.subarray(0, 32); + const IR = I.subarray(32); + return { + key: IL, + chainCode: IR, + }; +}; + +const CKDPriv = ({ key, chainCode }: Keys, index: number): Keys => { + const indexBuffer = Buffer.allocUnsafe(4); + indexBuffer.writeUInt32BE(index, 0); + + const data = Buffer.concat([Buffer.alloc(1, 0), key, indexBuffer]); + + const I = createHmac("sha512", chainCode).update(data).digest(); + const IL = I.subarray(0, 32); + const IR = I.subarray(32); + return { + key: IL, + chainCode: IR, + }; +}; + +const getPublicKey = (privateKey: Buffer, withZeroByte = true): Buffer => { + const keyPair = sign.keyPair.fromSeed(privateKey); + const signPk = keyPair.secretKey.subarray(32); + const zero = Buffer.alloc(1, 0); + return withZeroByte + ? Buffer.concat([zero, Buffer.from(signPk)]) + : Buffer.from(signPk); +}; + +const replaceDerive = (val: string): string => val.replace("'", ""); + +const isValidPath = (path: string): boolean => { + if (!/^m(\/[0-9]+('?))*$/.test(path)) { + return false; + } + return !path + .split("/") + .slice(1) + .map(replaceDerive) + .some(isNaN as any); +}; + +const derivePath = (path: Path, seed: Hex, offset = HARDENED_OFFSET): Keys => { + if (!isValidPath(path)) { + throw new Error("Invalid derivation path"); + } + + const { key, chainCode } = getMasterKeyFromSeed(seed); + const segments = path + .split("/") + .slice(1) + .map(replaceDerive) + .map((el) => parseInt(el, 10)); + + return segments.reduce( + (parentKeys, segment) => CKDPriv(parentKeys, segment + offset), + { key, chainCode }, + ); +}; + +export { derivePath, getPublicKey }; \ No newline at end of file diff --git a/packages/signers/massa/tests/sign.test.ts b/packages/signers/massa/tests/sign.test.ts new file mode 100644 index 000000000..96ce5b369 --- /dev/null +++ b/packages/signers/massa/tests/sign.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect } from "vitest"; +import { blake2AsU8a } from "@polkadot/util-crypto"; +import { bufferToHex } from "@enkryptcom/utils"; +import { MassaSigner } from "../src"; + +describe("Massa signing", () => { + const MNEMONIC = { + mnemonic: + "error fish boy absent drop next ice keep meadow little air include", + }; + + const msg = + "Everything should be made as simple as possible, but not simpler."; + const msgHash = bufferToHex(blake2AsU8a(msg)); + + it("should generate keypair correctly", async () => { + const signer = new MassaSigner(); + const keypair = await signer.generate(MNEMONIC, "m/44'/632'/0'/0'"); + + expect(keypair.address).toBeDefined(); + expect(keypair.privateKey).toBeDefined(); + expect(keypair.publicKey).toBeDefined(); + + // Massa addresses should start with 'AU' + expect(keypair.address?.startsWith('AU')).toBe(true); + }); + + it("should sign and verify correctly", async () => { + const signer = new MassaSigner(); + const keypair = await signer.generate(MNEMONIC, "m/44'/632'/0'/0'"); + const signature = await signer.sign(msgHash, keypair); + + expect(signature).toBeDefined(); + expect(signature.length).toBeGreaterThan(0); + + const isValid = await signer.verify(msgHash, signature, keypair.publicKey); + expect(isValid).toBe(true); + }); + + it("should reject invalid signatures", async () => { + const signer = new MassaSigner(); + const keypair = await signer.generate(MNEMONIC, "m/44'/632'/0'/0'"); + const invalidSignature = "0x" + "0".repeat(128); // Invalid signature + + const isValid = await signer.verify(msgHash, invalidSignature, keypair.publicKey); + expect(isValid).toBe(false); + }); +}); \ No newline at end of file diff --git a/packages/signers/massa/tsconfig.json b/packages/signers/massa/tsconfig.json new file mode 100644 index 000000000..4adf9ef98 --- /dev/null +++ b/packages/signers/massa/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "./tsconfig.paths.json", + "compilerOptions": { + "lib": ["ESNext", "dom"], + "moduleResolution": "node", + "noUnusedLocals": true, + "noUnusedParameters": true, + "removeComments": true, + "sourceMap": true, + "target": "es2015", + "module": "commonjs", + "outDir": "dist", + "resolveJsonModule": true, + "esModuleInterop": true, + "declaration": true, + "downlevelIteration": true + }, + "include": ["src/**/*.ts", "tests/**/*.ts"], + "exclude": [ + "node_modules/**/*", + "_warmup/**/*", + ".vscode/**/*", + "tests/**/*" + ], + "ts-node": { + "require": ["tsconfig-paths/register"], + "files": true + } +} \ No newline at end of file diff --git a/packages/signers/massa/tsconfig.paths.json b/packages/signers/massa/tsconfig.paths.json new file mode 100644 index 000000000..bb0512825 --- /dev/null +++ b/packages/signers/massa/tsconfig.paths.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@src/*": ["src/*"] + } + } +} \ No newline at end of file diff --git a/packages/types/package.json b/packages/types/package.json index 8d9c4de7b..0ed29a450 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -24,6 +24,7 @@ "node": ">=14.15.0" }, "devDependencies": { + "@massalabs/massa-web3": "^5.2.1-dev", "@types/node": "^22.15.24", "@typescript-eslint/eslint-plugin": "^8.34.1", "@typescript-eslint/parser": "^8.34.1", diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index a4ad09e70..5cf205151 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -50,6 +50,7 @@ enum SignerType { secp256k1btc = "secp256k1-btc", // bitcoin ed25519kda = "ed25519-kda", // kadena ed25519sol = "ed25519-sol", // solana + ed25519mas = "ed25519-mas", // massa } interface KeyRecordAdd { diff --git a/packages/types/src/networks.ts b/packages/types/src/networks.ts index 8073dda2c..0e52ed385 100755 --- a/packages/types/src/networks.ts +++ b/packages/types/src/networks.ts @@ -113,6 +113,8 @@ export enum NetworkNames { UnitZeroTestnet = "UnitZeroTestnet", Conflux = "Conflux", Hemi = "Hemi", + Massa = "Massa", + MassaBuildnet = "MassaBuildnet" } export enum CoingeckoPlatform { @@ -181,4 +183,5 @@ export enum CoingeckoPlatform { Taraxa = "taraxa", UnitZero = "units-network", Conflux = "conflux", + Massa = "massa", } diff --git a/yarn.lock b/yarn.lock index d7401176c..3c7c09b97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1763,6 +1763,7 @@ __metadata: "@enkryptcom/signer-bitcoin": "workspace:^" "@enkryptcom/signer-ethereum": "workspace:^" "@enkryptcom/signer-kadena": "workspace:^" + "@enkryptcom/signer-massa": "workspace:^" "@enkryptcom/signer-polkadot": "workspace:^" "@enkryptcom/storage": "workspace:^" "@enkryptcom/types": "workspace:^" @@ -1939,6 +1940,38 @@ __metadata: languageName: unknown linkType: soft +"@enkryptcom/signer-massa@workspace:^, @enkryptcom/signer-massa@workspace:packages/signers/massa": + version: 0.0.0-use.local + resolution: "@enkryptcom/signer-massa@workspace:packages/signers/massa" + dependencies: + "@enkryptcom/types": "workspace:^" + "@enkryptcom/utils": "workspace:^" + "@massalabs/massa-web3": "npm:^5.2.1-dev" + "@polkadot/util-crypto": "npm:^13.5.1" + "@types/hdkey": "npm:^2.1.0" + "@types/node": "npm:^22.15.24" + "@types/varint": "npm:^6" + "@typescript-eslint/eslint-plugin": "npm:^8.33.0" + "@typescript-eslint/parser": "npm:^8.33.0" + bip39: "npm:^3.1.0" + eslint: "npm:^9.27.0" + eslint-config-airbnb-base: "npm:^15.0.0" + eslint-config-prettier: "npm:^10.1.5" + eslint-import-resolver-alias: "npm:^1.1.2" + eslint-plugin-import: "npm:^2.31.0" + eslint-plugin-module-resolver: "npm:^1.5.0" + prettier: "npm:^3.5.3" + ts-node: "npm:^10.9.2" + tsconfig-paths: "npm:^4.2.0" + tsup: "npm:^8.5.0" + tweetnacl: "npm:^1.0.3" + typescript: "npm:^5.8.3" + typescript-eslint: "npm:8.33.0" + varint: "npm:^6.0.0" + vitest: "npm:^3.1.4" + languageName: unknown + linkType: soft + "@enkryptcom/signer-polkadot@workspace:^, @enkryptcom/signer-polkadot@workspace:packages/signers/polkadot": version: 0.0.0-use.local resolution: "@enkryptcom/signer-polkadot@workspace:packages/signers/polkadot" @@ -2037,6 +2070,7 @@ __metadata: version: 0.0.0-use.local resolution: "@enkryptcom/types@workspace:packages/types" dependencies: + "@massalabs/massa-web3": "npm:^5.2.1-dev" "@types/node": "npm:^22.15.24" "@typescript-eslint/eslint-plugin": "npm:^8.34.1" "@typescript-eslint/parser": "npm:^8.34.1" @@ -5763,6 +5797,32 @@ __metadata: languageName: node linkType: hard +"@massalabs/massa-web3@npm:^5.2.1-dev": + version: 5.2.1-dev.20250611112554 + resolution: "@massalabs/massa-web3@npm:5.2.1-dev.20250611112554" + dependencies: + "@noble/ed25519": "npm:^1.7.3" + "@noble/hashes": "npm:^1.2.0" + bs58check: "npm:^4.0.0" + bufferutil: "npm:^4.0.7" + dotenv: "npm:^16.0.3" + eventemitter3: "npm:^5.0.1" + google-protobuf: "npm:^3.21.4" + grpc-web: "npm:^1.5.0" + lodash.isequal: "npm:^4.5.0" + secure-random: "npm:^1.1.2" + tslib: "npm:^2.8.0" + utf-8-validate: "npm:^6.0.2" + varint: "npm:^6.0.0" + dependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/29f69533e5b9469440c6d329824d59600448ac3de40b44340878a4f883f353de7a858ddea69091d20b5fa5d77dca96d9c39c8c994eb9c635a72c2780096126bf + languageName: node + linkType: hard + "@metamask/abi-utils@npm:^3.0.0": version: 3.0.0 resolution: "@metamask/abi-utils@npm:3.0.0" @@ -11479,6 +11539,15 @@ __metadata: languageName: node linkType: hard +"@types/varint@npm:^6": + version: 6.0.3 + resolution: "@types/varint@npm:6.0.3" + dependencies: + "@types/node": "npm:*" + checksum: 10/cfec85c15e24becb360be6466a41c97b5b6370aec49a403ed28ae52e96b7ef692e9e5aee981ccebab3b7afdf9b84d3809e6ab5bb38cf2e926711c6aebeb8a68d + languageName: node + linkType: hard + "@types/w3c-web-usb@npm:^1.0.6": version: 1.0.10 resolution: "@types/w3c-web-usb@npm:1.0.10" @@ -17081,7 +17150,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.4.5, dotenv@npm:^16.5.0": +"dotenv@npm:^16.0.3, dotenv@npm:^16.4.5, dotenv@npm:^16.5.0": version: 16.5.0 resolution: "dotenv@npm:16.5.0" checksum: 10/e68a16834f1a41cc2dfb01563bc150668ad675e6cd09191211467b5c0806b6ecd6ec438e021aa8e01cd0e72d2b70ef4302bec7cc0fe15b6955f85230b62dc8a9 @@ -19530,6 +19599,13 @@ __metadata: languageName: node linkType: hard +"google-protobuf@npm:^3.21.4": + version: 3.21.4 + resolution: "google-protobuf@npm:3.21.4" + checksum: 10/0d87fe8ef221d105cbaa808f4024bd577638524d8e461469e3733f2e4933391ad4da86b7fcbd11e8781bee04eacf2e8ba19aaacd5f9deb336a220485841d980f + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -19635,6 +19711,13 @@ __metadata: languageName: node linkType: hard +"grpc-web@npm:^1.5.0": + version: 1.5.0 + resolution: "grpc-web@npm:1.5.0" + checksum: 10/e9791831cf9fc8762d9a832c3df9d885d7a9a5d98333e51e4920c66679cff5f015b2f286f32366eb66cf5347c0d9f02c54f195c55f5cf334c026bc0398a13ce1 + languageName: node + linkType: hard + "gzip-size@npm:^6.0.0": version: 6.0.0 resolution: "gzip-size@npm:6.0.0" @@ -22085,7 +22168,7 @@ __metadata: languageName: node linkType: hard -"lodash.isequal@npm:4.5.0": +"lodash.isequal@npm:4.5.0, lodash.isequal@npm:^4.5.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" checksum: 10/82fc58a83a1555f8df34ca9a2cd300995ff94018ac12cc47c349655f0ae1d4d92ba346db4c19bbfc90510764e0c00ddcc985a358bdcd4b3b965abf8f2a48a214 @@ -26825,7 +26908,7 @@ __metadata: languageName: node linkType: hard -"secure-random@npm:1.1.2": +"secure-random@npm:1.1.2, secure-random@npm:^1.1.2": version: 1.1.2 resolution: "secure-random@npm:1.1.2" checksum: 10/8b5d32df870346a9400a6a31c0c4da4862df0416289fef7ec8bfda1187cf2ae08a281f57d174287f9a43b5e757478d22631c7dc2582fddcd2cfafc1d92042d72 @@ -29524,6 +29607,16 @@ __metadata: languageName: node linkType: hard +"utf-8-validate@npm:^6.0.2": + version: 6.0.5 + resolution: "utf-8-validate@npm:6.0.5" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10/8c96d342064d3f03d7acf616fe727e484825f4f5f7a455059122787306b2df1a4e23c2d27f16bf7ba21293f4ce6ab3e683b893fe7b4c74ac9d43b871c10001a0 + languageName: node + linkType: hard + "utf-8-validate@npm:^6.0.3": version: 6.0.4 resolution: "utf-8-validate@npm:6.0.4" @@ -29672,6 +29765,13 @@ __metadata: languageName: node linkType: hard +"varint@npm:^6.0.0": + version: 6.0.0 + resolution: "varint@npm:6.0.0" + checksum: 10/7684113c9d497c01e40396e50169c502eb2176203219b96e1c5ac965a3e15b4892bd22b7e48d87148e10fffe638130516b6dbeedd0efde2b2d0395aa1772eea7 + languageName: node + linkType: hard + "varuint-bitcoin@npm:1.1.2, varuint-bitcoin@npm:^1.0.4, varuint-bitcoin@npm:^1.1.2": version: 1.1.2 resolution: "varuint-bitcoin@npm:1.1.2"