Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
18ca58d
Support Massa in browser extension
peterjah Jul 8, 2025
59761a5
add custom token support in asset view
peterjah Jul 29, 2025
1c58e94
support MRC20 sending
peterjah Jul 31, 2025
8ed1760
Merge pull request #739 from massalabs/extension
kvhnuke Aug 20, 2025
83e5aef
devop: migrate crypto to local
kvhnuke Aug 20, 2025
377ce34
fix massa signature verify
peterjah Aug 25, 2025
555d271
Merge pull request #743 from massalabs/extension
kvhnuke Aug 27, 2025
6bab919
fix: tests
kvhnuke Aug 27, 2025
92094ed
Merge pull request #742 from enkryptcom/devop/massa-changes
kvhnuke Aug 27, 2025
8660bcf
fix: tests
kvhnuke Aug 27, 2025
a754ac7
devop: cleanup
kvhnuke Aug 27, 2025
afe65f1
fix: build
kvhnuke Aug 28, 2025
f173a23
devop: cleanup
kvhnuke Aug 28, 2025
95d0f8e
fix: build
kvhnuke Aug 28, 2025
321d659
fix chrome extension build
peterjah Sep 2, 2025
0900c8c
add invalid address error message
peterjah Sep 2, 2025
4b2b694
ensure network is initialized before checking address
peterjah Sep 2, 2025
0115ba6
Merge pull request #746 from massalabs/extension
kvhnuke Sep 2, 2025
da80eaf
rework sent input amount
peterjah Sep 4, 2025
3b8c669
set fiat value max width in sent-input-amount
peterjah Sep 4, 2025
99c6b70
Merge pull request #747 from massalabs/extension
kvhnuke Sep 4, 2025
660ea08
fix: keyring error
kvhnuke Sep 4, 2025
2d62693
fix transaction amount validation
peterjah Sep 9, 2025
381da02
rework activity status check
peterjah Sep 9, 2025
6966188
fix add massa custom token
peterjah Sep 9, 2025
543105b
Merge pull request #749 from massalabs/extension
kvhnuke Sep 9, 2025
776c10b
fix: name issue and blank screen issue
kvhnuke Sep 9, 2025
ac3c8f0
fix: minor
kvhnuke Sep 9, 2025
901ece0
fix: conflicts
kvhnuke Sep 10, 2025
cea48fd
fix: conflicts
kvhnuke Sep 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"husky": "^9.1.7",
"node-notifier": "^10.0.1",
"nodemon": "^3.1.10",
"ultra-runner": "^3.10.5"
"ultra-runner": "^3.10.5",
"vite-plugin-node-polyfills": "0.24.0"
},
"resolutions": {
"@zondax/ledger-cosmos-js/vue": "^3.5.12",
Expand Down
1 change: 1 addition & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@kadena/client": "^1.18.0",
"@kadena/pactjs-cli": "^1.18.0",
"@ledgerhq/hw-transport-webusb": "^6.29.10",
"@massalabs/massa-web3": "^5.2.1-dev",
"@metamask/eth-sig-util": "^8.2.0",
"@metaplex-foundation/mpl-bubblegum": "^5.0.2",
"@metaplex-foundation/umi": "^1.4.1",
Expand Down
1 change: 1 addition & 0 deletions packages/extension/src/libs/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class BackgroundHandler {
[ProviderName.bitcoin]: {},
[ProviderName.kadena]: {},
[ProviderName.solana]: {},
[ProviderName.massa]: {},
};
this.#providers = Providers;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/extension/src/libs/background/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand All @@ -12,6 +13,7 @@ export interface TabProviderType {
| BitcoinProvider
| KadenaProvider
| SolanaProvider
| MassaProvider
>;
}
export interface ProviderType {
Expand All @@ -20,7 +22,8 @@ export interface ProviderType {
| typeof PolkadotProvider
| typeof BitcoinProvider
| typeof KadenaProvider
| typeof SolanaProvider;
| typeof SolanaProvider
| typeof MassaProvider;
}
export interface ExternalMessageOptions {
savePersistentEvents: boolean;
Expand Down
3 changes: 2 additions & 1 deletion packages/extension/src/libs/tokens-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CustomToken,
CustomErc20Token,
TokenType,
CustomMassaToken,
} from './types';
import { NetworkNames } from '@enkryptcom/types';

Expand All @@ -24,7 +25,7 @@ export class TokensState {
*/
async addErc20Token(
chainName: NetworkNames,
token: CustomErc20Token,
token: CustomErc20Token | CustomMassaToken,
): Promise<boolean> {
let state: IState | null = await this.storage.get(StorageKeys.customTokens);

Expand Down
4 changes: 4 additions & 0 deletions packages/extension/src/libs/tokens-state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export interface CustomErc20Token extends CustomToken {
address: `0x${string}`;
}

export interface CustomMassaToken extends CustomToken {
address: `AS${string}`;
}

export type IState = Partial<Record<NetworkNames, CustomToken[]>>;
11 changes: 11 additions & 0 deletions packages/extension/src/libs/utils/initialize-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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';
Expand All @@ -23,6 +24,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,
Expand Down Expand Up @@ -58,6 +62,13 @@ export const initAccounts = async (keyring: KeyRing) => {
signerType: SolanaNetworks.solana.signer[0],
walletType: WalletType.mnemonic,
});
if (ed25519massa.length == 0)
await keyring.saveNewAccount({
basePath: MassaNetworks.Massa.basePath,
name: 'Massa Account 1',
signerType: MassaNetworks.Massa.signer[0],
walletType: WalletType.mnemonic,
});
};
export const onboardInitializeWallets = async (options: {
mnemonic: string;
Expand Down
12 changes: 11 additions & 1 deletion packages/extension/src/libs/utils/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ 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, Record<string, BaseNetwork>> = {
[ProviderName.ethereum]: EthereumNetworks,
[ProviderName.polkadot]: PolkadotNetworks,
[ProviderName.bitcoin]: BitcoinNetworks,
[ProviderName.kadena]: KadenaNetworks,
[ProviderName.solana]: SolanaNetworks,
[ProviderName.massa]: MassaNetworks,
[ProviderName.enkrypt]: {},
};
const getAllNetworks = async (
Expand All @@ -34,7 +37,9 @@ const getAllNetworks = async (
.concat(Object.values(PolkadotNetworks) as BaseNetwork[])
.concat(Object.values(BitcoinNetworks) as BaseNetwork[])
.concat(Object.values(KadenaNetworks) as BaseNetwork[])
.concat(Object.values(SolanaNetworks) as BaseNetwork[]);
.concat(Object.values(SolanaNetworks) as BaseNetwork[])
.concat(Object.values(MassaNetworks) as BaseNetwork[]);

if (!includeCustom) {
return allNetworks;
}
Expand Down Expand Up @@ -67,17 +72,20 @@ const DEFAULT_SUBSTRATE_NETWORK_NAME = NetworkNames.Polkadot;
const DEFAULT_BTC_NETWORK_NAME = NetworkNames.Bitcoin;
const DEFAULT_KADENA_NETWORK_NAME = NetworkNames.Kadena;
const DEFAULT_SOLANA_NETWORK_NAME = NetworkNames.Solana;
const DEFAULT_MASSA_NETWORK_NAME = NetworkNames.Massa;

const DEFAULT_EVM_NETWORK = Ethereum;
const DEFAULT_SUBSTRATE_NETWORK = Polkadot;
const DEFAULT_BTC_NETWORK = Bitcoin;
const DEFAULT_KADENA_NETWORK = Kadena;
const DEFAULT_SOLANA_NETWORK = Solana;
const DEFAULT_MASSA_NETWORK = Massa;

const POPULAR_NAMES = [
NetworkNames.Bitcoin,
NetworkNames.Ethereum,
NetworkNames.Solana,
NetworkNames.Massa,
NetworkNames.Matic,
NetworkNames.Polkadot,
NetworkNames.Binance,
Expand All @@ -100,4 +108,6 @@ export {
DEFAULT_KADENA_NETWORK_NAME,
DEFAULT_SOLANA_NETWORK,
DEFAULT_SOLANA_NETWORK_NAME,
DEFAULT_MASSA_NETWORK,
DEFAULT_MASSA_NETWORK_NAME,
};
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,14 @@ const changeFocus = () => {
justify-content: flex-start;
align-items: center;
flex-direction: row;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

svg {
margin-right: 4px;
flex-shrink: 0;
}

span {
Expand All @@ -157,6 +162,9 @@ const changeFocus = () => {
text-align: center;
letter-spacing: 0.25px;
color: @tertiaryLabel;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/extension/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PolkadotProvider from '@/providers/polkadot';
import BitcoinProvider from '@/providers/bitcoin';
import KadenaProvider from '@/providers/kadena';
import SolanaProvider from '@/providers/solana';
import MassaProvider from '@/providers/massa';
import { ProviderName } from '@/types/provider';

export default {
Expand All @@ -11,4 +12,5 @@ export default {
[ProviderName.bitcoin]: BitcoinProvider,
[ProviderName.kadena]: KadenaProvider,
[ProviderName.solana]: SolanaProvider,
[ProviderName.massa]: MassaProvider,
};
92 changes: 92 additions & 0 deletions packages/extension/src/providers/massa/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { BaseNetwork } from '@/types/base-network';
import getRequestProvider, { RequestClass } from '@enkryptcom/request';
import { MiddlewareFunction, OnMessageResponse } from '@enkryptcom/types';
import Middlewares from './methods';
import EventEmitter from 'eventemitter3';
import {
BackgroundProviderInterface,
ProviderName,
ProviderRPCRequest,
} from '@/types/provider';
import GetUIPath from '@/libs/utils/get-ui-path';
import PublicKeyRing from '@/libs/keyring/public-keyring';
import UIRoutes from './ui/routes/names';
import { RoutesType } from '@/types/ui';
import massaNetworks from './networks';
import { NetworkNames } from '@enkryptcom/types';

export default class MassaProvider
extends EventEmitter
implements BackgroundProviderInterface
{
public network: BaseNetwork;
requestProvider: RequestClass;
middlewares: MiddlewareFunction[] = [];
namespace: string;
KeyRing: PublicKeyRing;
UIRoutes: RoutesType;
toWindow: (message: string) => void;

constructor(
toWindow: (message: string) => void,
network: BaseNetwork = massaNetworks[NetworkNames.Massa],
) {
super();
this.network = network;
this.toWindow = toWindow;
this.setMiddleWares();
this.requestProvider = getRequestProvider(network.node, this.middlewares);
this.requestProvider.on('notification', (notif: any) => {
this.sendNotification(JSON.stringify(notif));
});
this.namespace = ProviderName.massa;
this.KeyRing = new PublicKeyRing();

this.UIRoutes = UIRoutes;
}

private setMiddleWares(): void {
this.middlewares = Middlewares(this).map(mw => mw.bind(this));
}

setRequestProvider(network: BaseNetwork): void {
const prevURL = new URL(this.network.node);
const newURL = new URL(network.node);
this.network = network;
if (prevURL.protocol === newURL.protocol)
this.requestProvider.changeNetwork(network.node);
else
this.requestProvider = getRequestProvider(network.node, this.middlewares);
}

async isPersistentEvent(): Promise<boolean> {
return false;
}

async sendNotification(notif: string): Promise<void> {
return this.toWindow(notif);
}

request(request: ProviderRPCRequest): Promise<OnMessageResponse> {
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);
}

getCurrentNetwork(): BaseNetwork {
return this.network;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import MassaActivity from './massa';

export { MassaActivity };
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BaseNetwork } from '@/types/base-network';
import { Activity } from '@/types/activity';
import { ActivityHandlerType } from '@/libs/activity-state/types';
import ActivityState from '@/libs/activity-state';

const MassaActivity: ActivityHandlerType = async (
network: BaseNetwork,
address: string,
): Promise<Activity[]> => {
try {
// Get activities from local storage
const activityState = new ActivityState();
const activities = await activityState.getAllActivities({
address,
network: network.name,
});

// For now, return local activities
// In the future, this can be extended to fetch from Massa explorer API
return activities;
} catch (error) {
console.error('Error fetching Massa activities:', error);
return [];
}
};

export default MassaActivity;
60 changes: 60 additions & 0 deletions packages/extension/src/providers/massa/libs/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ProviderAPIInterface } from '@/types/provider';
import {
JsonRpcPublicProvider,
MRC20,
NodeStatusInfo,
OperationStatus,
} from '@massalabs/massa-web3';

export default class MassaAPI extends ProviderAPIInterface {
public provider: JsonRpcPublicProvider;
public node: string;

constructor(node: string) {
super(node);
this.node = node;
this.provider = JsonRpcPublicProvider.fromRPCUrl(
node,
) as JsonRpcPublicProvider;
}

public get api() {
return this;
}

async init(): Promise<void> {}

async getBalance(address: string): Promise<string> {
const [account] = await this.provider.balanceOf([address], false);
if (!account) return '0';
return account.balance.toString();
}

async getBalanceMRC20(address: string, contract: string): Promise<string> {
const mrc20 = new MRC20(this.provider, contract);
const balance = await mrc20.balanceOf(address);
return balance.toString();
}

async getMinimalFee(): Promise<string> {
try {
const networkInfo = await this.provider.networkInfos();
return networkInfo.minimalFee.toString();
} catch {
// 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<OperationStatus | null> {
try {
return this.provider.getOperationStatus(opId);
} catch {
return null;
}
}

async getNodeStatus(): Promise<NodeStatusInfo> {
return this.provider.getNodeStatus();
}
}
Loading