Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Solana support #505

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -41,6 +41,7 @@
},
"resolutions": {
"@ledgerhq/compressjs": "https://registry.yarnpkg.com/@favware/skip-dependency/-/skip-dependency-1.2.1.tgz",
"@noble/hashes": "^1.4.0"
"@noble/hashes": "^1.4.0",
"fork-ts-checker-webpack-plugin": "^6.5.3"
}
}
6 changes: 5 additions & 1 deletion packages/extension/configs/rollup.config.base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import nodeResolve from "@rollup/plugin-node-resolve";
import { uglify } from "rollup-plugin-uglify";
import inject from "@rollup/plugin-inject";
import replace from "@rollup/plugin-replace";
import json from "@rollup/plugin-json";
import packageJson from "../package.json" assert { type: "json" };

/** @typedef {import('rollup').InputOptions} InputOptions */
Expand All @@ -27,8 +28,11 @@ const base = {
__VERSION__: JSON.stringify(packageJson.version),
__IS_OPERA__: process.env.BROWSER === "opera-edge",
}),
typescript(),
typescript({
exclude: [/node_modules/],
}),
commonjs(),
json(),
inject({
Buffer: ["buffer", "Buffer"],
}),
Expand Down
13 changes: 12 additions & 1 deletion packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,26 @@
"@kadena/client": "^1.13.0",
"@ledgerhq/hw-transport-webusb": "^6.29.2",
"@metamask/eth-sig-util": "^7.0.3",
"@metaplex-foundation/mpl-bubblegum": "^4.2.0",
"@metaplex-foundation/umi": "^0.9.2",
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
"@rollup/plugin-replace": "^5.0.7",
"@solana-developers/helpers": "^2.4.0",
"@solana/spl-token": "^0.4.8",
"@solana/wallet-standard-features": "^1.2.0",
"@solana/web3.js": "^1.95.2",
"@types/chrome": "^0.0.269",
"@types/events": "^3.0.3",
"@types/less": "^3.0.6",
"@types/lodash": "^4.17.7",
"@types/utf-8-validate": "^5.0.2",
"@vueuse/core": "^10.11.0",
"@wallet-standard/base": "^0.0.0-20240703212708",
"add": "^2.0.6",
"bignumber.js": "^9.1.2",
"bip39": "^3.1.0",
"bitcoinjs-lib": "^6.1.6",
"bs58": "^6.0.0",
"chai": "^4.5.0",
"concurrently": "^8.2.2",
"core-js": "^3.38.0",
Expand Down Expand Up @@ -90,8 +99,10 @@
"@polkadot/wasm-crypto": "^7.3.2",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/bs58": "^4.0.4",
"@types/ethereumjs-abi": "^0.6.5",
"@types/mocha": "^10.0.7",
"@types/url-parse": "^1.4.11",
Expand Down Expand Up @@ -127,7 +138,7 @@
"systeminformation": "^5.23.3",
"ts-mocha": "^10.0.0",
"tsconfig-paths": "^4.2.0",
"typescript": "^4.9.5",
"typescript": "^5.5.4",
"url": "^0.11.4",
"webextension-polyfill": "^0.12.0"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/extension/src/libs/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import {
InternalOnMessageResponse,
Message,
} from "@/types/messenger";
import { RPCRequestType } from "@enkryptcom/types";
import { RPCRequestType, OnMessageResponse } from "@enkryptcom/types";
import { v4 as randomUUID } from "uuid";
import { getCustomError } from "../error";
import KeyRingBase from "../keyring/keyring";
import { sendToWindow } from "@/libs/messenger/extension";
import { ProviderName } from "@/types/provider";
import { OnMessageResponse } from "@enkryptcom/types";
import Providers from "@/providers";
import Browser from "webextension-polyfill";
import TabInfo from "@/libs/utils/tab-info";
Expand Down Expand Up @@ -48,6 +47,7 @@ class BackgroundHandler {
[ProviderName.polkadot]: {},
[ProviderName.bitcoin]: {},
[ProviderName.kadena]: {},
[ProviderName.solana]: {},
};
this.#providers = Providers;
}
Expand Down
10 changes: 8 additions & 2 deletions packages/extension/src/libs/background/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ import BitcoinProvider from "@/providers/bitcoin";
import type EthereumProvider from "@/providers/ethereum";
import type PolkadotProvider from "@/providers/polkadot";
import type KadenaProvider from "@/providers/kadena";
import SolanaProvider from "@/providers/solana";

export interface TabProviderType {
[key: string]: Record<
number,
EthereumProvider | PolkadotProvider | BitcoinProvider | KadenaProvider
| EthereumProvider
| PolkadotProvider
| BitcoinProvider
| KadenaProvider
| SolanaProvider
>;
}
export interface ProviderType {
[key: string]:
| typeof EthereumProvider
| typeof PolkadotProvider
| typeof BitcoinProvider
| typeof KadenaProvider;
| typeof KadenaProvider
| typeof SolanaProvider;
}
export interface ExternalMessageOptions {
savePersistentEvents: boolean;
Expand Down
7 changes: 4 additions & 3 deletions packages/extension/src/libs/cache-fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ const cacheFetch = async (
return fetch(options.url, fetchOptions)
.then((res) => res.json())
.then((json) => {
const jsonstring = JSON.stringify(json);
const jsondata = options.postProcess ? options.postProcess(json) : json;
const jsonstring = JSON.stringify(jsondata);
if (!jsonstring.includes("error")) {
const store: StoredData = {
timestamp: new Date().getTime(),
data: jsonstring,
};
return storage.set(hash, store).then(() => json);
return storage.set(hash, store).then(() => jsondata);
}
return json;
return jsondata;
});
}
};
Expand Down
1 change: 1 addition & 0 deletions packages/extension/src/libs/cache-fetch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface RequestOptions {
url: string;
post?: Record<string, any>;
headers?: Record<string, any>;
postProcess?: (data: any) => any;
}
export interface StoredData {
timestamp: number;
Expand Down
95 changes: 95 additions & 0 deletions packages/extension/src/libs/nft-handlers/simplehash-solana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { NFTCollection, NFTItem, NFTType } from "@/types/nft";
import { NodeType } from "@/types/provider";
import cacheFetch from "../cache-fetch";
import { NetworkNames } from "@enkryptcom/types";
import { SHNFTType, SHResponse, SHSolanaNFTType } from "./types/simplehash";
const SH_ENDPOINT = "https://partners.mewapi.io/nfts/";
const CACHE_TTL = 60 * 1000;
const SolanaTokenPrograms = {
Bubblegum: "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY",
Token: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
};
export default async (
network: NodeType,
address: string
): Promise<NFTCollection[]> => {
const supportedNetworks = {
[NetworkNames.Solana]: "solana",
};
if (!Object.keys(supportedNetworks).includes(network.name))
throw new Error("Simplehash: network not supported");
let allItems: SHSolanaNFTType[] = [];
const fetchAll = (continuation?: string): Promise<void> => {
const query = continuation
? continuation
: `${SH_ENDPOINT}owners_v2?chains=${
supportedNetworks[network.name as keyof typeof supportedNetworks]
}&wallet_addresses=${network.displayAddress(
address
)}&filters=spam_score__lte=50`;
return cacheFetch(
{
url: query,
},
CACHE_TTL
).then((json) => {
const items: SHNFTType[] = (json.result as SHResponse).nfts;
allItems = allItems.concat(items as SHSolanaNFTType[]);
if (json.result.next) return fetchAll(json.result.next);
});
};
await fetchAll();
if (!allItems || !allItems.length) return [];
const collections: Record<string, NFTCollection> = {};
allItems.forEach((item) => {
if (!item.image_url && !item.previews.image_medium_url) return;
if (
item.extra_metadata.token_program !== SolanaTokenPrograms.Bubblegum &&
item.extra_metadata.token_program !== SolanaTokenPrograms.Token
)
return;
if (collections[item.collection.collection_id]) {
const tItem: NFTItem = {
contract: item.contract_address,
id: item.nft_id,
image: item.image_url || item.previews.image_medium_url,
name: item.name,
url: item.collection.marketplace_pages.length
? item.collection.marketplace_pages[0].nft_url
: `https://magiceden.io/item-details/${item.contract_address}`,
type:
item.extra_metadata.token_program === SolanaTokenPrograms.Bubblegum
? NFTType.SolanaBGUM
: NFTType.SolanaToken,
};
collections[item.collection.collection_id].items.push(tItem);
} else {
const ret: NFTCollection = {
name: item.collection.name,
description: item.collection.description,
image:
item.collection.image_url ||
require("@action/assets/common/not-found.jpg"),
contract: item.contract_address,
items: [
{
contract: item.contract_address,
id: item.nft_id,
image: item.image_url || item.previews.image_medium_url,
name: item.name,
url: item.collection.marketplace_pages.length
? item.collection.marketplace_pages[0].nft_url
: `https://magiceden.io/item-details/${item.contract_address}`,
type:
item.extra_metadata.token_program ===
SolanaTokenPrograms.Bubblegum
? NFTType.SolanaBGUM
: NFTType.SolanaToken,
},
],
};
collections[item.collection.collection_id] = ret;
}
});
return Object.values(collections);
};
13 changes: 13 additions & 0 deletions packages/extension/src/libs/nft-handlers/types/simplehash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export interface SHNFTType {
external_url: string;
collection_id: string;
spam_score: number;
marketplace_pages: {
marketplace_id: string;
marketplace_name: string;
collection_url: string;
nft_url: string;
}[];
};
contract: {
name: string;
Expand All @@ -37,6 +43,13 @@ export interface SHOrdinalsNFTType extends SHNFTType {
};
};
}

export interface SHSolanaNFTType extends SHNFTType {
extra_metadata: {
token_program: string;
};
}

export interface SHResponse {
next: string;
previous: string;
Expand Down
9 changes: 9 additions & 0 deletions packages/extension/src/libs/utils/initialize-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import EthereumNetworks from "@/providers/ethereum/networks";
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 { NetworkNames, WalletType } from "@enkryptcom/types";
import { getAccountsByNetworkName } from "@/libs/utils/accounts";
export const initAccounts = async (keyring: KeyRing) => {
const secp256k1btc = await getAccountsByNetworkName(NetworkNames.Bitcoin);
const secp256k1 = await getAccountsByNetworkName(NetworkNames.Ethereum);
const sr25519 = await getAccountsByNetworkName(NetworkNames.Polkadot);
const ed25519kda = await getAccountsByNetworkName(NetworkNames.Kadena);
const ed25519sol = await getAccountsByNetworkName(NetworkNames.Solana);
if (secp256k1.length == 0)
await keyring.saveNewAccount({
basePath: EthereumNetworks.ethereum.basePath,
Expand Down Expand Up @@ -38,6 +40,13 @@ export const initAccounts = async (keyring: KeyRing) => {
signerType: KadenaNetworks.kadena.signer[0],
walletType: WalletType.mnemonic,
});
if (ed25519sol.length == 0)
await keyring.saveNewAccount({
basePath: SolanaNetworks.solana.basePath,
name: "Solana Account 1",
signerType: SolanaNetworks.solana.signer[0],
walletType: WalletType.mnemonic,
});
};
export const onboardInitializeWallets = async (
mnemonic: string,
Expand Down
8 changes: 8 additions & 0 deletions packages/extension/src/libs/utils/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import EthereumNetworks from "@/providers/ethereum/networks";
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 { BaseNetwork } from "@/types/base-network";
import CustomNetworksState from "../custom-networks-state";
import { CustomEvmNetwork } from "@/providers/ethereum/types/custom-evm-network";
import Ethereum from "@/providers/ethereum/networks/eth";
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";

const providerNetworks: Record<ProviderName, Record<string, BaseNetwork>> = {
[ProviderName.ethereum]: EthereumNetworks,
[ProviderName.polkadot]: PolkadotNetworks,
[ProviderName.bitcoin]: BitcoinNetworks,
[ProviderName.kadena]: KadenaNetworks,
[ProviderName.solana]: SolanaNetworks,
[ProviderName.enkrypt]: {},
};
const getAllNetworks = async (): Promise<BaseNetwork[]> => {
Expand All @@ -30,6 +33,7 @@ const getAllNetworks = async (): Promise<BaseNetwork[]> => {
.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(customNetworks);
};
const getNetworkByName = async (
Expand Down Expand Up @@ -58,11 +62,13 @@ const DEFAULT_EVM_NETWORK_NAME = NetworkNames.Ethereum;
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_EVM_NETWORK = Ethereum;
const DEFAULT_SUBSTRATE_NETWORK = Polkadot;
const DEFAULT_BTC_NETWORK = Bitcoin;
const DEFAULT_KADENA_NETWORK = Kadena;
const DEFAULT_SOLANA_NETWORK = Solana;

const POPULAR_NAMES = [
NetworkNames.Bitcoin,
Expand All @@ -87,4 +93,6 @@ export {
DEFAULT_BTC_NETWORK,
DEFAULT_KADENA_NETWORK,
DEFAULT_KADENA_NETWORK_NAME,
DEFAULT_SOLANA_NETWORK,
DEFAULT_SOLANA_NETWORK_NAME,
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ import {
NFTType,
} from "@/types/nft";
import { BitcoinNetwork } from "@/providers/bitcoin/types/bitcoin-network";
import { SolanaNetwork } from "@/providers/solana/types/sol-network";
const props = defineProps({
network: {
type: Object as PropType<EvmNetwork | BitcoinNetwork>,
type: Object as PropType<EvmNetwork | BitcoinNetwork | SolanaNetwork>,
default: () => ({}),
},
address: {
Expand Down
Loading
Loading