From d586da78dda1a98020093f2c08fa5089b0ded45c Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:54:46 -0700 Subject: [PATCH 01/20] devop: sol stuff --- .../extension/src/providers/solana/index.ts | 73 +++ .../extension/src/providers/solana/inject.ts | 143 +++++ .../solana/libs/accounts-state/index.ts | 72 +++ .../solana/libs/accounts-state/types.ts | 6 + .../solana/libs/activity-handlers/index.ts | 3 + .../providers/haskoin/index.ts | 95 +++ .../activity-handlers/providers/ss/index.ts | 108 ++++ .../src/providers/solana/libs/api-ss.ts | 135 +++++ .../src/providers/solana/libs/api.ts | 103 ++++ .../solana/libs/bip322-message-sign.ts | 161 +++++ .../src/providers/solana/libs/blockies.ts | 113 ++++ .../providers/solana/libs/btc-fee-handler.ts | 22 + .../providers/solana/libs/filter-ordinals.ts | 55 ++ .../providers/solana/libs/message-router.ts | 56 ++ .../solana/libs/sign-message-utils.ts | 47 ++ .../providers/solana/libs/ss-fee-handler.ts | 35 ++ .../src/providers/solana/libs/utils.ts | 104 ++++ .../solana/methods/btc_getBalance.ts | 39 ++ .../solana/methods/btc_getNetwork.ts | 34 ++ .../solana/methods/btc_getPublicKey.ts | 33 + .../solana/methods/btc_requestAccounts.ts | 80 +++ .../solana/methods/btc_signMessage.ts | 52 ++ .../providers/solana/methods/btc_signPsbt.ts | 55 ++ .../solana/methods/btc_switchNetwork.ts | 60 ++ .../src/providers/solana/methods/index.ts | 16 + .../solana/networks/bitcoin-testnet.ts | 56 ++ .../src/providers/solana/networks/bitcoin.ts | 51 ++ .../src/providers/solana/networks/dogecoin.ts | 51 ++ .../providers/solana/networks/icons/btc.svg | 12 + .../providers/solana/networks/icons/doge.svg | 1 + .../providers/solana/networks/icons/ltc.svg | 1 + .../providers/solana/networks/icons/tbtc.svg | 13 + .../src/providers/solana/networks/index.ts | 11 + .../src/providers/solana/networks/litecoin.ts | 51 ++ .../tests/bitcoin.address.derivation.mocha.ts | 16 + .../src/providers/solana/types/index.ts | 3 + .../src/providers/solana/types/sol-network.ts | 132 ++++ .../src/providers/solana/types/sol-token.ts | 19 + .../providers/solana/ui/btc-connect-dapp.vue | 173 ++++++ .../providers/solana/ui/btc-sign-message.vue | 119 ++++ .../solana/ui/btc-verify-transaction.vue | 342 +++++++++++ .../src/providers/solana/ui/index.ts | 7 + .../src/providers/solana/ui/libs/signer.ts | 134 +++++ .../src/providers/solana/ui/libs/tx-size.ts | 260 ++++++++ .../src/providers/solana/ui/routes/index.ts | 18 + .../src/providers/solana/ui/routes/names.ts | 22 + .../components/send-address-input.vue | 160 +++++ .../components/send-alert.vue | 72 +++ .../components/send-token-select.vue | 107 ++++ .../solana/ui/send-transaction/index.vue | 564 ++++++++++++++++++ .../verify-transaction/index.vue | 355 +++++++++++ .../solana/ui/styles/common-popup.less | 219 +++++++ .../solana/ui/styles/verify-transaction.less | 479 +++++++++++++++ .../src/providers/solana/ui/types.ts | 47 ++ 54 files changed, 5195 insertions(+) create mode 100644 packages/extension/src/providers/solana/index.ts create mode 100644 packages/extension/src/providers/solana/inject.ts create mode 100644 packages/extension/src/providers/solana/libs/accounts-state/index.ts create mode 100644 packages/extension/src/providers/solana/libs/accounts-state/types.ts create mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/index.ts create mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts create mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts create mode 100644 packages/extension/src/providers/solana/libs/api-ss.ts create mode 100644 packages/extension/src/providers/solana/libs/api.ts create mode 100644 packages/extension/src/providers/solana/libs/bip322-message-sign.ts create mode 100644 packages/extension/src/providers/solana/libs/blockies.ts create mode 100644 packages/extension/src/providers/solana/libs/btc-fee-handler.ts create mode 100644 packages/extension/src/providers/solana/libs/filter-ordinals.ts create mode 100644 packages/extension/src/providers/solana/libs/message-router.ts create mode 100644 packages/extension/src/providers/solana/libs/sign-message-utils.ts create mode 100644 packages/extension/src/providers/solana/libs/ss-fee-handler.ts create mode 100644 packages/extension/src/providers/solana/libs/utils.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_getBalance.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_getNetwork.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_getPublicKey.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_requestAccounts.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_signMessage.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_signPsbt.ts create mode 100644 packages/extension/src/providers/solana/methods/btc_switchNetwork.ts create mode 100644 packages/extension/src/providers/solana/methods/index.ts create mode 100644 packages/extension/src/providers/solana/networks/bitcoin-testnet.ts create mode 100644 packages/extension/src/providers/solana/networks/bitcoin.ts create mode 100644 packages/extension/src/providers/solana/networks/dogecoin.ts create mode 100644 packages/extension/src/providers/solana/networks/icons/btc.svg create mode 100644 packages/extension/src/providers/solana/networks/icons/doge.svg create mode 100644 packages/extension/src/providers/solana/networks/icons/ltc.svg create mode 100644 packages/extension/src/providers/solana/networks/icons/tbtc.svg create mode 100644 packages/extension/src/providers/solana/networks/index.ts create mode 100644 packages/extension/src/providers/solana/networks/litecoin.ts create mode 100644 packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts create mode 100644 packages/extension/src/providers/solana/types/index.ts create mode 100644 packages/extension/src/providers/solana/types/sol-network.ts create mode 100644 packages/extension/src/providers/solana/types/sol-token.ts create mode 100644 packages/extension/src/providers/solana/ui/btc-connect-dapp.vue create mode 100644 packages/extension/src/providers/solana/ui/btc-sign-message.vue create mode 100644 packages/extension/src/providers/solana/ui/btc-verify-transaction.vue create mode 100644 packages/extension/src/providers/solana/ui/index.ts create mode 100644 packages/extension/src/providers/solana/ui/libs/signer.ts create mode 100644 packages/extension/src/providers/solana/ui/libs/tx-size.ts create mode 100644 packages/extension/src/providers/solana/ui/routes/index.ts create mode 100644 packages/extension/src/providers/solana/ui/routes/names.ts create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/index.vue create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue create mode 100644 packages/extension/src/providers/solana/ui/styles/common-popup.less create mode 100644 packages/extension/src/providers/solana/ui/styles/verify-transaction.less create mode 100644 packages/extension/src/providers/solana/ui/types.ts diff --git a/packages/extension/src/providers/solana/index.ts b/packages/extension/src/providers/solana/index.ts new file mode 100644 index 000000000..ef2dc0e31 --- /dev/null +++ b/packages/extension/src/providers/solana/index.ts @@ -0,0 +1,73 @@ +import { BaseNetwork } from "@/types/base-network"; +import getRequestProvider, { RequestClass } from "@enkryptcom/request"; +import Networks from "./networks"; +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 { BitcoinNetwork } from "./types/bitcoin-network"; +class SolanaProvider + extends EventEmitter + implements BackgroundProviderInterface +{ + network: BitcoinNetwork; + requestProvider: RequestClass; + middlewares: MiddlewareFunction[] = []; + namespace: string; + KeyRing: PublicKeyRing; + UIRoutes = UIRoutes; + toWindow: (message: string) => void; + constructor( + toWindow: (message: string) => void, + network: BitcoinNetwork = Networks.bitcoin + ) { + super(); + this.network = network; + this.toWindow = toWindow; + this.setMiddleWares(); + this.requestProvider = getRequestProvider("", this.middlewares); + this.requestProvider.on("notification", (notif: any) => { + this.sendNotification(JSON.stringify(notif)); + }); + this.namespace = ProviderName.bitcoin; + this.KeyRing = new PublicKeyRing(); + } + private setMiddleWares(): void { + this.middlewares = Middlewares.map((mw) => mw.bind(this)); + } + setRequestProvider(network: BaseNetwork): void { + this.network = network as BitcoinNetwork; + 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); + } +} +export default BitcoinProvider; diff --git a/packages/extension/src/providers/solana/inject.ts b/packages/extension/src/providers/solana/inject.ts new file mode 100644 index 000000000..805268e7e --- /dev/null +++ b/packages/extension/src/providers/solana/inject.ts @@ -0,0 +1,143 @@ +import EventEmitter from "eventemitter3"; +import { handleIncomingMessage } from "./libs/message-router"; +import { EthereumRequest, EthereumResponse } from "@/providers/ethereum/types"; +import { + ProviderName, + ProviderOptions, + ProviderType, + ProviderInterface, + SendMessageHandler, +} from "@/types/provider"; +import { EnkryptWindow } from "@/types/globals"; +import { BitcoinNetworks } from "./types"; +import { InternalMethods } from "@/types/messenger"; +import { SettingsType } from "@/libs/settings-state/types"; + +export class Provider extends EventEmitter implements ProviderInterface { + connected: boolean; + name: ProviderName; + type: ProviderType; + version: string = __VERSION__; + autoRefreshOnNetworkChange = false; + networks: typeof BitcoinNetworks; + sendMessageHandler: SendMessageHandler; + constructor(options: ProviderOptions) { + super(); + this.connected = true; + this.name = options.name; + this.type = options.type; + this.networks = BitcoinNetworks; + this.sendMessageHandler = options.sendMessageHandler; + } + + async request(request: EthereumRequest): Promise { + const res = (await this.sendMessageHandler( + this.name, + JSON.stringify(request) + )) as EthereumResponse; + return res; + } + + requestAccounts = async () => { + return this.request({ + method: "btc_requestAccounts", + }); + }; + + getAccounts = async () => { + return this.request({ + method: "btc_requestAccounts", + }); + }; + + getPublicKey = async () => { + return this.request({ + method: "btc_getPublicKey", + }); + }; + + getNetwork = async () => { + return this.request({ + method: "btc_getNetwork", + }); + }; + + switchNetwork = async (network: string) => { + return this.request({ + method: "btc_switchNetwork", + params: [network], + }); + }; + + getBalance = async () => { + return this.request({ + method: "btc_getBalance", + }); + }; + + signPsbt = async (psbtHex: string, options?: any) => { + return this.request({ + method: "btc_signPsbt", + params: [psbtHex, options], + }); + }; + + signMessage = async (text: string, type: string) => { + return this.request({ + method: "btc_signMessage", + params: [text, type], + }); + }; + + getInscriptions = async () => { + return Promise.reject("not implemented"); + }; + + sendBitcoin = async () => { + return Promise.reject("not implemented"); + }; + + sendInscription = async () => { + return Promise.reject("not implemented"); + }; + + inscribeTransfer = async () => { + return Promise.reject("not implemented"); + }; + + pushTx = async () => { + return Promise.reject("not implemented"); + }; + + signPsbts = async () => { + return Promise.reject("not implemented"); + }; + + pushPsbt = async () => { + return Promise.reject("not implemented"); + }; + + isConnected(): boolean { + return this.connected; + } + handleMessage(msg: string): void { + handleIncomingMessage(this, msg); + } +} + +const injectDocument = ( + document: EnkryptWindow | Window, + options: ProviderOptions +): void => { + const provider = new Provider(options); + options + .sendMessageHandler( + ProviderName.enkrypt, + JSON.stringify({ method: InternalMethods.getSettings, params: [] }) + ) + .then((settings: SettingsType) => { + if (settings.btc.injectUnisat) document["unisat"] = provider; + }); + document["enkrypt"]["providers"][options.name] = provider; +}; +export default injectDocument; diff --git a/packages/extension/src/providers/solana/libs/accounts-state/index.ts b/packages/extension/src/providers/solana/libs/accounts-state/index.ts new file mode 100644 index 000000000..54a360903 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/accounts-state/index.ts @@ -0,0 +1,72 @@ +import { InternalStorageNamespace } from "@/types/provider"; +import BrowserStorage from "@/libs/common/browser-storage"; +import { IState, StorageKeys } from "./types"; +class AccountState { + #storage: BrowserStorage; + constructor() { + this.#storage = new BrowserStorage( + InternalStorageNamespace.bitcoinAccountsState + ); + } + async addApprovedAddress(address: string, domain: string): Promise { + address = address.toLowerCase(); + const state = await this.getStateByDomain(domain); + if (state.approvedAccounts.includes(address)) + state.approvedAccounts = state.approvedAccounts.filter( + (add) => add !== address + ); //this will make sure latest address is always infront + state.approvedAccounts.unshift(address); + await this.setState(state, domain); + } + async removeApprovedAddress(address: string, domain: string): Promise { + address = address.toLowerCase(); + const state = await this.getStateByDomain(domain); + if (state.approvedAccounts.includes(address)) { + state.approvedAccounts = state.approvedAccounts.filter( + (a) => a !== address + ); + await this.setState(state, domain); + } + } + async getApprovedAddresses(domain: string): Promise { + const state = await this.getStateByDomain(domain); + if (state.approvedAccounts) return state.approvedAccounts; + return []; + } + async deleteState(domain: string): Promise { + const allStates = await this.getAllStates(); + if (allStates[domain]) { + delete allStates[domain]; + await this.#storage.set(StorageKeys.accountsState, allStates); + } + } + async isConnected(domain: string): Promise { + return this.getStateByDomain(domain).then( + (res) => res.approvedAccounts.length > 0 + ); + } + async deleteAllStates(): Promise { + return await this.#storage.remove(StorageKeys.accountsState); + } + async setState(state: IState, domain: string): Promise { + const allStates = await this.getAllStates(); + allStates[domain] = state; + await this.#storage.set(StorageKeys.accountsState, allStates); + } + async getStateByDomain(domain: string): Promise { + const allStates: Record = await this.getAllStates(); + if (!allStates[domain]) + return { + approvedAccounts: [], + }; + else return allStates[domain]; + } + async getAllStates(): Promise> { + const allStates: Record = await this.#storage.get( + StorageKeys.accountsState + ); + if (!allStates) return {}; + return allStates; + } +} +export default AccountState; diff --git a/packages/extension/src/providers/solana/libs/accounts-state/types.ts b/packages/extension/src/providers/solana/libs/accounts-state/types.ts new file mode 100644 index 000000000..768317475 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/accounts-state/types.ts @@ -0,0 +1,6 @@ +export enum StorageKeys { + accountsState = "bitcoin-accounts-state", +} +export interface IState { + approvedAccounts: string[]; +} diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/index.ts new file mode 100644 index 000000000..dc0e381ea --- /dev/null +++ b/packages/extension/src/providers/solana/libs/activity-handlers/index.ts @@ -0,0 +1,3 @@ +import haskoinHandler from "./providers/haskoin"; +import ssHandler from "./providers/ss"; +export { haskoinHandler, ssHandler }; diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts new file mode 100644 index 000000000..f71f66587 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts @@ -0,0 +1,95 @@ +import MarketData from "@/libs/market-data"; +import { HaskoinTxType } from "@/providers/bitcoin/types"; +import { + Activity, + ActivityStatus, + ActivityType, + BTCRawInfo, +} from "@/types/activity"; +import { BaseNetwork } from "@/types/base-network"; +export default async ( + network: BaseNetwork, + pubkey: string +): Promise => { + return fetch( + `${network.node}address/${network.displayAddress(pubkey)}/transactions/full` + ) + .then((res) => res.json()) + .then(async (txs: HaskoinTxType[]) => { + if ((txs as any).error) return []; + let tokenPrice = "0"; + if (network.coingeckoID) { + const marketData = new MarketData(); + await marketData + .getTokenPrice(network.coingeckoID) + .then((mdata) => (tokenPrice = mdata || "0")); + } + + const address = network.displayAddress(pubkey); + return txs.map((tx) => { + const isIncoming = !tx.inputs.find((i) => i.address === address); + + let toAddress = ""; + let value = 0; + + if (isIncoming) { + const relevantOut = tx.outputs.find((tx) => tx.address === address); + if (relevantOut) { + toAddress = relevantOut.address; + value = relevantOut.value; + } + } else { + const relevantOut = tx.outputs.find((tx) => tx.address !== address); + if (relevantOut) { + toAddress = relevantOut.address; + value = relevantOut.value; + } else { + toAddress = tx.outputs[0].address; + value = Number(tx.outputs[0].value); + } + } + + const rawInfo: BTCRawInfo = { + blockNumber: tx.block.height!, + fee: tx.fee, + inputs: tx.inputs.map((input) => ({ + address: input.address, + value: input.value, + })), + outputs: tx.outputs.map((output) => ({ + address: output.address, + value: output.value, + pkscript: output.pkscript, + })), + transactionHash: tx.txid, + timestamp: tx.time * 1000, + }; + const act: Activity = { + from: tx.inputs[0].address, + isIncoming, + network: network.name, + status: !tx.block.mempool + ? ActivityStatus.success + : ActivityStatus.pending, + timestamp: tx.time * 1000, + to: toAddress, + token: { + decimals: network.decimals, + icon: network.icon, + name: network.name_long, + symbol: network.currencyName, + coingeckoID: network.coingeckoID, + price: tokenPrice, + }, + transactionHash: tx.txid, + type: ActivityType.transaction, + value: value.toString(), + rawInfo: rawInfo, + }; + return act; + }); + }) + .catch(() => { + return []; + }); +}; diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts new file mode 100644 index 000000000..d1dfe3a82 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts @@ -0,0 +1,108 @@ +import MarketData from "@/libs/market-data"; +import { SSTxType } from "@/providers/bitcoin/types"; +import { + Activity, + ActivityStatus, + ActivityType, + BTCRawInfo, +} from "@/types/activity"; +import { BaseNetwork } from "@/types/base-network"; +export default async ( + network: BaseNetwork, + pubkey: string +): Promise => { + return fetch( + `${network.node}/api/v1/account/${network.displayAddress( + pubkey + )}/txs?pageSize=40` + ) + .then((res) => res.json()) + .then(async (txs: { txs: SSTxType[] }) => { + if ((txs as any).message) return []; + let tokenPrice = "0"; + if (network.coingeckoID) { + const marketData = new MarketData(); + await marketData + .getTokenPrice(network.coingeckoID) + .then((mdata) => (tokenPrice = mdata || "0")); + } + + const address = network.displayAddress(pubkey); + const cleanedTxs = txs.txs.map((tx) => { + return { + ...tx, + vin: tx.vin.filter((vi) => vi.addresses), + vout: tx.vout.filter((vo) => vo.addresses), + }; + }); + return cleanedTxs.map((tx) => { + const isIncoming = !tx.vin.find((i) => i.addresses![0] === address); + let toAddress = ""; + let value = 0; + + if (isIncoming) { + const relevantOut = tx.vout.find( + (tx) => tx.addresses![0] === address + ); + if (relevantOut) { + toAddress = relevantOut.addresses![0]; + value = Number(relevantOut.value); + } + } else { + const relevantOut = tx.vout.find( + (tx) => tx.addresses![0] !== address + ); + if (relevantOut) { + toAddress = relevantOut.addresses![0]; + value = Number(relevantOut.value); + } else { + toAddress = tx.vout[0].addresses![0]; + value = Number(tx.vout[0].value); + } + } + + const rawInfo: BTCRawInfo = { + blockNumber: tx.blockHeight!, + fee: Number(tx.fee), + inputs: tx.vin.map((input) => ({ + address: input.addresses![0], + value: Number(input.value), + })), + outputs: tx.vout.map((output) => ({ + address: output.addresses![0], + value: Number(output.value), + pkscript: output.scriptPubKey.hex, + })), + transactionHash: tx.txid, + timestamp: tx.timestamp * 1000, + }; + const act: Activity = { + from: tx.vin[0].addresses![0], + isIncoming, + network: network.name, + status: + tx.blockHeight > 0 + ? ActivityStatus.success + : ActivityStatus.pending, + timestamp: tx.timestamp * 1000, + to: toAddress, + token: { + decimals: network.decimals, + icon: network.icon, + name: network.name_long, + symbol: network.currencyName, + coingeckoID: network.coingeckoID, + price: tokenPrice, + }, + transactionHash: tx.txid, + type: ActivityType.transaction, + value: value.toString(), + rawInfo: rawInfo, + }; + return act; + }); + }) + .catch(() => { + return []; + }); +}; diff --git a/packages/extension/src/providers/solana/libs/api-ss.ts b/packages/extension/src/providers/solana/libs/api-ss.ts new file mode 100644 index 000000000..88b3e5426 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/api-ss.ts @@ -0,0 +1,135 @@ +import { BTCRawInfo } from "@/types/activity"; +import { ProviderAPIInterface } from "@/types/provider"; +import { + BitcoinNetworkInfo, + HaskoinUnspentType, + SSTxType, + SSUnspentType, +} from "../types"; +import { toBN } from "web3-utils"; +import cacheFetch from "@/libs/cache-fetch"; +import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; +import { filterOutOrdinals } from "./filter-ordinals"; + +class API implements ProviderAPIInterface { + node: string; + networkInfo: BitcoinNetworkInfo; + + constructor(node: string, networkInfo: BitcoinNetworkInfo) { + this.node = node; + this.networkInfo = networkInfo; + } + + public get api() { + return this; + } + private getAddress(pubkey: string) { + return getBitcoinAddress(pubkey, this.networkInfo); + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + async init(): Promise {} + async getTransactionStatus(hash: string): Promise { + return fetch(`${this.node}/api/v1/tx/${hash}`) + .then((res) => res.json()) + .then((tx: SSTxType) => { + if ((tx as any).message) return null; + if (tx.blockHeight < 0) return null; + const rawInfo: BTCRawInfo = { + blockNumber: tx.blockHeight, + fee: Number(tx.fee), + inputs: tx.vin + .filter((t) => t.addresses && t.addresses.length) + .map((input) => ({ + address: input.addresses![0], + value: Number(input.value), + })), + outputs: tx.vout + .filter((t) => t.addresses && t.addresses.length) + .map((output) => ({ + address: output.addresses![0], + value: Number(output.value), + pkscript: output.scriptPubKey.hex, + })), + transactionHash: tx.txid, + timestamp: tx.timestamp * 1000, + }; + return rawInfo; + }); + } + async getBalance(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}/api/v1/account/${address}`) + .then((res) => res.json()) + .then((balance: { balance: string; unconfirmedBalance: string }) => { + if ((balance as any).message) return "0"; + return toBN(balance.balance) + .add(toBN(balance.unconfirmedBalance)) + .toString(); + }) + .catch(() => "0"); + } + async broadcastTx(rawtx: string): Promise { + return fetch(`${this.node}/api/v1/send`, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ hex: rawtx }), + }) + .then((res) => res.json()) + .then((response) => { + if (response.error) { + return Promise.reject(response.message); + } + return true; + }); + } + async SSToHaskoinUTXOs( + SSUTXOs: SSUnspentType[], + address: string + ): Promise { + const ret: HaskoinUnspentType[] = []; + for (const utx of SSUTXOs) { + const res = (await cacheFetch({ + url: `${this.node}/api/v1/tx/${utx.txid}`, + })) as SSTxType; + ret.push({ + address, + block: { + height: utx.height, + position: 0, + }, + index: utx.vout, + pkscript: res.vout[utx.vout].scriptPubKey.hex, + txid: utx.txid, + value: Number(utx.value), + raw: res.hex, + }); + } + ret.sort((a, b) => { + return a.value - b.value; + }); + return ret; + } + + async getUTXOs(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}/api/v1/account/${address}/utxos`) + .then((res) => res.json()) + .then(async (utxos: SSUnspentType[]) => { + if ((utxos as any).message || !utxos.length) return []; + return filterOutOrdinals( + address, + this.networkInfo.name, + await this.SSToHaskoinUTXOs(utxos, address) + ).then((futxos) => { + futxos.sort((a, b) => { + return a.value - b.value; + }); + return futxos; + }); + }); + } +} +export default API; diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts new file mode 100644 index 000000000..80dc94e41 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -0,0 +1,103 @@ +import { BTCRawInfo } from "@/types/activity"; +import { ProviderAPIInterface } from "@/types/provider"; +import { + BitcoinNetworkInfo, + HaskoinBalanceType, + HaskoinTxType, + HaskoinUnspentType, +} from "../types"; +import { toBN } from "web3-utils"; +import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; +import { filterOutOrdinals } from "./filter-ordinals"; + +class API implements ProviderAPIInterface { + node: string; + networkInfo: BitcoinNetworkInfo; + + constructor(node: string, networkInfo: BitcoinNetworkInfo) { + this.node = node; + this.networkInfo = networkInfo; + } + + public get api() { + return this; + } + private getAddress(pubkey: string) { + return getBitcoinAddress(pubkey, this.networkInfo); + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + async init(): Promise {} + async getTransactionStatus(hash: string): Promise { + return fetch(`${this.node}transaction/${hash}`) + .then((res) => res.json()) + .then((tx: HaskoinTxType) => { + if ((tx as any).error) return null; + if (tx.block.mempool) return null; + const rawInfo: BTCRawInfo = { + blockNumber: tx.block.height!, + fee: tx.fee, + inputs: tx.inputs.map((input) => ({ + address: input.address, + value: input.value, + })), + outputs: tx.outputs.map((output) => ({ + address: output.address, + value: output.value, + pkscript: output.pkscript, + })), + transactionHash: tx.txid, + timestamp: tx.time * 1000, + }; + return rawInfo; + }); + } + async getBalance(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}address/${address}/balance`) + .then((res) => res.json()) + .then((balance: HaskoinBalanceType) => { + if ((balance as any).error) return "0"; + return toBN(balance.confirmed).addn(balance.unconfirmed).toString(); + }) + .catch(() => "0"); + } + async broadcastTx(rawtx: string): Promise { + return fetch(`${this.node}transactions`, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "text/plain", + }, + body: rawtx, + }) + .then((res) => res.json()) + .then((response) => { + if (response.error) { + if (response.error === "server-error") return true; // haskoin api return error when it timesout or something + return Promise.reject(response.message); + } + return true; + }); + } + async getUTXOs(pubkey: string): Promise { + const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); + return fetch(`${this.node}address/${address}/unspent`) + .then((res) => res.json()) + .then((utxos: HaskoinUnspentType[]) => { + if ((utxos as any).error) return []; + return filterOutOrdinals(address, this.networkInfo.name, utxos).then( + (futxos) => { + futxos.sort((a, b) => { + return a.value - b.value; + }); + return futxos; + } + ); + }) + .catch((e) => { + console.error(e); + return []; + }); + } +} +export default API; diff --git a/packages/extension/src/providers/solana/libs/bip322-message-sign.ts b/packages/extension/src/providers/solana/libs/bip322-message-sign.ts new file mode 100644 index 000000000..60f62d723 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/bip322-message-sign.ts @@ -0,0 +1,161 @@ +/** + * reference: https://github.com/unisat-wallet/wallet-sdk/blob/master/src/message/bip322-simple.ts + * reference: https://github.com/bitcoinjs/varuint-bitcoin/blob/master/index.js + */ + +import { BitcoinNetwork, PaymentType } from "../types/bitcoin-network"; +import { address as BTCAddress, Transaction, Psbt } from "bitcoinjs-lib"; +import { sha256 } from "ethereum-cryptography/sha256"; +import { PSBTSigner } from "../ui/libs/signer"; +import { bufferToHex } from "@enkryptcom/utils"; + +const bip0322_hash = (message: string) => { + const tag = "BIP0322-signed-message"; + const tagHash = sha256(Buffer.from(tag)); + const result = sha256( + Buffer.concat([tagHash, tagHash, Buffer.from(message)]) + ); + return bufferToHex(result, true); +}; + +const MAX_SAFE_INTEGER = 9007199254740991; + +const checkUInt53 = (n: number) => { + if (n < 0 || n > MAX_SAFE_INTEGER || n % 1 !== 0) + throw new RangeError("value out of range"); +}; + +const encodingLength = (number: number) => { + checkUInt53(number); + + return number < 0xfd + ? 1 + : number <= 0xffff + ? 3 + : number <= 0xffffffff + ? 5 + : 9; +}; +export const encode = (number: number, buffer?: Buffer, offset?: number) => { + checkUInt53(number); + + if (!buffer) buffer = Buffer.allocUnsafe(encodingLength(number)); + if (!Buffer.isBuffer(buffer)) + throw new TypeError("buffer must be a Buffer instance"); + if (!offset) offset = 0; + + // 8 bit + if (number < 0xfd) { + buffer.writeUInt8(number, offset); + + // 16 bit + } else if (number <= 0xffff) { + buffer.writeUInt8(0xfd, offset); + buffer.writeUInt16LE(number, offset + 1); + + // 32 bit + } else if (number <= 0xffffffff) { + buffer.writeUInt8(0xfe, offset); + buffer.writeUInt32LE(number, offset + 1); + + // 64 bit + } else { + buffer.writeUInt8(0xff, offset); + buffer.writeUInt32LE(number >>> 0, offset + 1); + buffer.writeUInt32LE((number / 0x100000000) | 0, offset + 5); + } + + return buffer; +}; + +export const decode = (buffer: Buffer, offset: number) => { + if (!Buffer.isBuffer(buffer)) + throw new TypeError("buffer must be a Buffer instance"); + if (!offset) offset = 0; + const first = buffer.readUInt8(offset); + // 8 bit + if (first < 0xfd) { + return first; + // 16 bit + } else if (first === 0xfd) { + return buffer.readUInt16LE(offset + 1); + // 32 bit + } else if (first === 0xfe) { + return buffer.readUInt32LE(offset + 1); + // 64 bit + } else { + const lo = buffer.readUInt32LE(offset + 1); + const hi = buffer.readUInt32LE(offset + 5); + const number = hi * 0x0100000000 + lo; + checkUInt53(number); + return number; + } +}; + +export async function signMessageOfBIP322Simple({ + message, + address, + network, + Signer, +}: { + message: string; + address: string; + network: BitcoinNetwork; + Signer: ReturnType; +}) { + const outputScript = BTCAddress.toOutputScript( + network.displayAddress(address), + network.networkInfo + ); + const addressType = network.networkInfo.paymentType; + const supportedTypes = [PaymentType.P2WPKH]; + if (supportedTypes.includes(addressType) == false) { + throw new Error("Not support address type to sign"); + } + + const prevoutHash = Buffer.from( + "0000000000000000000000000000000000000000000000000000000000000000", + "hex" + ); + const prevoutIndex = 0xffffffff; + const sequence = 0; + const scriptSig = Buffer.concat([ + Buffer.from("0020", "hex"), + Buffer.from(bip0322_hash(message), "hex"), + ]); + + const txToSpend = new Transaction(); + txToSpend.version = 0; + txToSpend.addInput(prevoutHash, prevoutIndex, sequence, scriptSig); + txToSpend.addOutput(outputScript, 0); + + const psbtToSign = new Psbt(); + psbtToSign.setVersion(0); + psbtToSign.addInput({ + hash: txToSpend.getHash(), + index: 0, + sequence: 0, + witnessUtxo: { + script: outputScript, + value: 0, + }, + }); + psbtToSign.addOutput({ script: Buffer.from("6a", "hex"), value: 0 }); + + await psbtToSign.signAllInputsAsync(Signer); + psbtToSign.finalizeAllInputs(); + const txToSign = psbtToSign.extractTransaction(); + + const encodeVarString = (b: Buffer) => { + return Buffer.concat([encode(b.byteLength), b]); + }; + + const len = encode(txToSign.ins[0].witness.length); + const result = Buffer.concat([ + len, + ...txToSign.ins[0].witness.map((w) => encodeVarString(w)), + ]); + const signature = result.toString("base64"); + + return signature; +} diff --git a/packages/extension/src/providers/solana/libs/blockies.ts b/packages/extension/src/providers/solana/libs/blockies.ts new file mode 100644 index 000000000..132603d34 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/blockies.ts @@ -0,0 +1,113 @@ +const randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values + +function seedrand(seed: string) { + for (let i = 0; i < randseed.length; i++) { + randseed[i] = 0; + } + for (let j = 0; j < seed.length; j++) { + randseed[j % 4] = + (randseed[j % 4] << 5) - randseed[j % 4] + seed.charCodeAt(j); + } +} + +function rand() { + // based on Java's String.hashCode(), expanded to 4 32bit values + const t = randseed[0] ^ (randseed[0] << 11); + + randseed[0] = randseed[1]; + randseed[1] = randseed[2]; + randseed[2] = randseed[3]; + randseed[3] = randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8); + + return (randseed[3] >>> 0) / ((1 << 31) >>> 0); +} + +function createColor() { + // saturation is the whole color spectrum + const h = Math.floor(rand() * 360); + // saturation goes from 40 to 100, it avoids greyish colors + const s = rand() * 60 + 40 + "%"; + // lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% + const l = (rand() + rand() + rand() + rand()) * 25 + "%"; + + const color = "hsl(" + h + "," + s + "," + l + ")"; + return color; +} + +function createImageData(size: number): number[] { + const width = size; // Only support square icons for now + const height = size; + + const dataWidth = Math.ceil(width / 2); + const mirrorWidth = width - dataWidth; + + const data = []; + for (let y = 0; y < height; y++) { + let row = []; + for (let x = 0; x < dataWidth; x++) { + // this makes foreground and background color to have a 43% (1/2.3) probability + // spot color has 13% chance + row[x] = Math.floor(rand() * 2.3); + } + const r = row.slice(0, mirrorWidth); + r.reverse(); + row = row.concat(r); + + for (let i = 0; i < row.length; i++) { + data.push(row[i]); + } + } + + return data; +} + +function createCanvas( + imageData: number[], + color: string, + scale: number, + bgcolor: string, + spotcolor: string +): HTMLCanvasElement { + const width = Math.sqrt(imageData.length); + const c = document.createElement("canvas"); + c.width = c.height = width * scale; + const cc = c.getContext("2d"); + if (cc) { + cc.fillStyle = bgcolor; + cc.fillRect(0, 0, c.width, c.height); + cc.fillStyle = color; + + for (let i = 0; i < imageData.length; i++) { + const row = Math.floor(i / width); + const col = i % width; + cc.fillStyle = imageData[i] === 1 ? color : spotcolor; + if (imageData[i]) { + cc.fillRect(col * scale, row * scale, scale, scale); + } + } + } + return c; +} + +type options = { + size?: number; + scale?: number; + color?: string; + bgcolor?: string; + spotcolor?: string; +}; +const createIcon = (address: string, opts?: options): string => { + opts = opts || {}; + const size = opts.size || 8; + const scale = opts.scale || 4; + const seed = address.toLowerCase(); + seedrand(seed); + const color = opts.color || createColor(); + const bgcolor = opts.bgcolor || createColor(); + const spotcolor = opts.spotcolor || createColor(); + const imageData = createImageData(size); + const canvas = createCanvas(imageData, color, scale, bgcolor, spotcolor); + return canvas.toDataURL(); +}; + +export default createIcon; diff --git a/packages/extension/src/providers/solana/libs/btc-fee-handler.ts b/packages/extension/src/providers/solana/libs/btc-fee-handler.ts new file mode 100644 index 000000000..07d8de534 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/btc-fee-handler.ts @@ -0,0 +1,22 @@ +import { GasPriceTypes } from "@/providers/common/types"; + +const BTCFeeHandler = async (): Promise> => { + return fetch(`https://bitcoiner.live/api/fees/estimates/latest`) + .then((res) => res.json()) + .then((json) => { + return { + [GasPriceTypes.FASTEST]: Math.ceil(json.estimates["30"].sat_per_vbyte), + [GasPriceTypes.FAST]: Math.ceil(json.estimates["60"].sat_per_vbyte), + [GasPriceTypes.REGULAR]: Math.ceil(json.estimates["120"].sat_per_vbyte), + [GasPriceTypes.ECONOMY]: Math.ceil(json.estimates["180"].sat_per_vbyte), + }; + }) + .catch(() => ({ + [GasPriceTypes.FASTEST]: 25, + [GasPriceTypes.FAST]: 20, + [GasPriceTypes.REGULAR]: 10, + [GasPriceTypes.ECONOMY]: 5, + })); +}; + +export default BTCFeeHandler; diff --git a/packages/extension/src/providers/solana/libs/filter-ordinals.ts b/packages/extension/src/providers/solana/libs/filter-ordinals.ts new file mode 100644 index 000000000..673cae62a --- /dev/null +++ b/packages/extension/src/providers/solana/libs/filter-ordinals.ts @@ -0,0 +1,55 @@ +import cacheFetch from "@/libs/cache-fetch"; +import { HaskoinUnspentType } from "../types"; +import { NetworkNames } from "@enkryptcom/types"; + +const OrdinalsEndpoint = "https://partners.mewapi.io/ordinals/"; +const CACHE_TTL = 60 * 1000; +const MAX_ITEMS = 100; + +const supportedNetworks = [NetworkNames.Bitcoin]; + +interface OridnalType { + inscriptionId: string; + output: string; +} +export const getAllOrdinals = ( + address: string, + networkName: string, + currentItems: OridnalType[] +): Promise => { + const query = `${OrdinalsEndpoint}${networkName.toLowerCase()}/ordinals/inscriptions?address=${address}&cursor=${ + currentItems.length + }&size=${MAX_ITEMS}`; + return cacheFetch( + { + url: query, + }, + CACHE_TTL + ).then((json) => { + if (json.code !== 0) + throw Promise.reject("Unknown error, cant retrieve ordinals"); + const items: OridnalType[] = json.data.list as OridnalType[]; + currentItems = currentItems.concat(items); + if (json.data.total === currentItems.length) return currentItems; + return getAllOrdinals(address, networkName, currentItems); + }); +}; + +export const filterOutOrdinals = ( + address: string, + networkName: string, + utxos: HaskoinUnspentType[] +): Promise => { + if (!supportedNetworks.includes(networkName as NetworkNames)) + return Promise.resolve(utxos); + return getAllOrdinals(address, networkName, []).then((ordinals) => { + return utxos.filter((utxo) => { + for (const ord of ordinals) { + const [txid, idx] = ord.output.split(":"); + if (utxo.txid === txid && utxo.index === parseInt(idx)) return false; + if (utxo.value <= 1000) return false; // most likely ordinal, safety precaution + } + return true; + }); + }); +}; diff --git a/packages/extension/src/providers/solana/libs/message-router.ts b/packages/extension/src/providers/solana/libs/message-router.ts new file mode 100644 index 000000000..e715f2807 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/message-router.ts @@ -0,0 +1,56 @@ +import { + ProviderMessage, + MessageMethod, + EmitEvent, +} from "@/providers/ethereum/types"; +import { + BitcoinProvider, + EnkryptProviderEventMethods, + handleIncomingMessage as handleIncomingMessageType, +} from "@/types/provider"; +import { NetworkNames } from "@enkryptcom/types"; +const handleIncomingMessage: handleIncomingMessageType = ( + provider, + message +): void => { + try { + const _provider = provider as BitcoinProvider; + const jsonMsg = JSON.parse(message) as ProviderMessage; + if (jsonMsg.method === MessageMethod.changeConnected) { + const isConnected = jsonMsg.params[0] as boolean; + _provider.connected = isConnected; + if (isConnected) { + _provider.emit(EmitEvent.connect); + } else { + _provider.emit(EmitEvent.disconnect); + } + } else if (jsonMsg.method === MessageMethod.changeAddress) { + const address = jsonMsg.params[0] as string; + _provider.emit(EmitEvent.accountsChanged, [address]); + } else if ( + (jsonMsg.method as EnkryptProviderEventMethods) === + EnkryptProviderEventMethods.chainChanged + ) { + if ( + jsonMsg.params[0] === NetworkNames.Bitcoin || + jsonMsg.params[0] === NetworkNames.BitcoinTest + ) { + _provider + .switchNetwork( + jsonMsg.params[0] === NetworkNames.Bitcoin ? "livenet" : "testnet" + ) + .then(() => { + _provider.emit(EmitEvent.networkChanged, [ + jsonMsg.params[0] === NetworkNames.Bitcoin + ? "livenet" + : "testnet", + ]); + }); + } + } + } catch (e) { + console.error(e); + } +}; + +export { handleIncomingMessage }; diff --git a/packages/extension/src/providers/solana/libs/sign-message-utils.ts b/packages/extension/src/providers/solana/libs/sign-message-utils.ts new file mode 100644 index 000000000..465a40a39 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/sign-message-utils.ts @@ -0,0 +1,47 @@ +import { sha256 } from "ethereum-cryptography/sha256"; + +const MAGIC_BYTES = Buffer.from("Bitcoin Signed Message:\n"); + +const varintBufNum = (n: number) => { + let buf; + if (n < 253) { + buf = Buffer.alloc(1); + buf.writeUInt8(n, 0); + } else if (n < 0x10000) { + buf = Buffer.alloc(1 + 2); + buf.writeUInt8(253, 0); + buf.writeUInt16LE(n, 1); + } else if (n < 0x100000000) { + buf = Buffer.alloc(1 + 4); + buf.writeUInt8(254, 0); + buf.writeUInt32LE(n, 1); + } else { + buf = Buffer.alloc(1 + 8); + buf.writeUInt8(255, 0); + buf.writeInt32LE(n & -1, 1); + buf.writeUInt32LE(Math.floor(n / 0x100000000), 5); + } + return buf; +}; + +export const magicHash = (messageBuffer: Buffer) => { + const prefix1 = varintBufNum(MAGIC_BYTES.length); + const prefix2 = varintBufNum(messageBuffer.length); + const buf = Buffer.concat([prefix1, MAGIC_BYTES, prefix2, messageBuffer]); + return Buffer.from(sha256(sha256(buf))); +}; + +export const toCompact = ( + i: number, + signature: Uint8Array, + compressed: boolean +) => { + if (!(i === 0 || i === 1 || i === 2 || i === 3)) { + throw new Error("i must be equal to 0, 1, 2, or 3"); + } + let val = i + 27 + 4; + if (!compressed) { + val = val - 4; + } + return Buffer.concat([Uint8Array.of(val), Uint8Array.from(signature)]); +}; diff --git a/packages/extension/src/providers/solana/libs/ss-fee-handler.ts b/packages/extension/src/providers/solana/libs/ss-fee-handler.ts new file mode 100644 index 000000000..81151b173 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/ss-fee-handler.ts @@ -0,0 +1,35 @@ +import { GasPriceTypes } from "@/providers/common/types"; + +interface FeeType { + fast: { + satsPerKiloByte: number; + }; + average: { + satsPerKiloByte: number; + }; + slow: { + satsPerKiloByte: number; + }; +} +const SSFeeHandler = async ( + url: string +): Promise> => { + return fetch(url) + .then((res) => res.json()) + .then((json: FeeType) => { + if (json.fast.satsPerKiloByte < 0) + json.fast.satsPerKiloByte = json.average.satsPerKiloByte; + if (json.average.satsPerKiloByte < 0) + json.average.satsPerKiloByte = json.slow.satsPerKiloByte; + return { + [GasPriceTypes.FASTEST]: + Math.ceil(json.fast.satsPerKiloByte / 1024) + 5, + [GasPriceTypes.FAST]: Math.ceil(json.fast.satsPerKiloByte / 1024) + 3, + [GasPriceTypes.REGULAR]: + Math.ceil(json.average.satsPerKiloByte / 1024) + 2, + [GasPriceTypes.ECONOMY]: Math.ceil(json.slow.satsPerKiloByte / 1024), + }; + }); +}; + +export default SSFeeHandler; diff --git a/packages/extension/src/providers/solana/libs/utils.ts b/packages/extension/src/providers/solana/libs/utils.ts new file mode 100644 index 000000000..f34045285 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/utils.ts @@ -0,0 +1,104 @@ +import { BitcoinNetworkInfo, HaskoinUnspentType } from "../types"; +import { address as BTCAddress } from "bitcoinjs-lib"; +import { GasPriceTypes } from "@/providers/common/types"; +import { fromBase } from "@enkryptcom/utils"; +import BigNumber from "bignumber.js"; +import { BitcoinNetwork } from "../types/bitcoin-network"; +import { BTCTxInfo } from "../ui/types"; + +const isAddress = (address: string, network: BitcoinNetworkInfo): boolean => { + try { + BTCAddress.toOutputScript(address, network); + return true; + } catch { + return false; + } +}; + +const getTxInfo = ( + utxos: HaskoinUnspentType[], + ordinalUTXO?: HaskoinUnspentType +): BTCTxInfo => { + const txInfo: BTCTxInfo = { + inputs: [], + outputs: [], + }; + utxos.forEach((u) => { + txInfo.inputs.push({ + hash: u.txid, + index: u.index, + raw: u.raw, + witnessUtxo: { + script: u.pkscript, + value: u.value, + }, + }); + }); + if (ordinalUTXO) { + txInfo.inputs.unshift({ + hash: ordinalUTXO.txid, + index: ordinalUTXO.index, + raw: ordinalUTXO.raw, + witnessUtxo: { + script: ordinalUTXO.pkscript, + value: ordinalUTXO.value, + }, + }); + } + return txInfo; +}; + +const getGasCostValues = async ( + network: BitcoinNetwork, + byteSize: number, + nativeVal = "0", + decimals: number, + currencyName: string +) => { + const fees = await network.feeHandler(); + const gasVals = { + [GasPriceTypes.FASTEST]: (byteSize * fees.FASTEST).toString(), + [GasPriceTypes.FAST]: (byteSize * fees.FAST).toString(), + [GasPriceTypes.REGULAR]: (byteSize * fees.REGULAR).toString(), + [GasPriceTypes.ECONOMY]: (byteSize * fees.ECONOMY).toString(), + }; + const getConvertedVal = (type: GasPriceTypes) => + fromBase(gasVals[type], decimals); + + const gasCostValues = { + [GasPriceTypes.ECONOMY]: { + nativeValue: getConvertedVal(GasPriceTypes.ECONOMY), + fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.ECONOMY)) + .times(nativeVal!) + .toString(), + nativeSymbol: currencyName, + fiatSymbol: "USD", + }, + [GasPriceTypes.REGULAR]: { + nativeValue: getConvertedVal(GasPriceTypes.REGULAR), + fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.REGULAR)) + .times(nativeVal!) + .toString(), + nativeSymbol: currencyName, + fiatSymbol: "USD", + }, + [GasPriceTypes.FAST]: { + nativeValue: getConvertedVal(GasPriceTypes.FAST), + fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FAST)) + .times(nativeVal!) + .toString(), + nativeSymbol: currencyName, + fiatSymbol: "USD", + }, + [GasPriceTypes.FASTEST]: { + nativeValue: getConvertedVal(GasPriceTypes.FASTEST), + fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FASTEST)) + .times(nativeVal!) + .toString(), + nativeSymbol: currencyName, + fiatSymbol: "USD", + }, + }; + return gasCostValues; +}; +export { isAddress, getGasCostValues, getTxInfo }; diff --git a/packages/extension/src/providers/solana/methods/btc_getBalance.ts b/packages/extension/src/providers/solana/methods/btc_getBalance.ts new file mode 100644 index 000000000..8e9ac1bd2 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_getBalance.ts @@ -0,0 +1,39 @@ +import { MiddlewareFunction } from "@enkryptcom/types"; +import { ProviderRPCRequest } from "@/types/provider"; +import { getCustomError } from "@/libs/error"; +import BitcoinProvider from ".."; +import AccountState from "../libs/accounts-state"; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_getBalance") return next(); + else { + if (!payload.options || !payload.options.domain) { + return res(getCustomError("btc_getNetwork: invalid domain")); + } + const accountsState = new AccountState(); + + accountsState + .getApprovedAddresses(payload.options!.domain) + .then((accounts) => { + if (!accounts.length) { + return res(null, ""); + } + this.network.api().then((api) => { + api + .getBalance(this.network.displayAddress(accounts[0])) + .then((bal) => { + res(null, { + confirmed: parseInt(bal), + unconfirmed: 0, + total: parseInt(bal), + }); + }); + }); + }); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_getNetwork.ts b/packages/extension/src/providers/solana/methods/btc_getNetwork.ts new file mode 100644 index 000000000..9bb201b7b --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_getNetwork.ts @@ -0,0 +1,34 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction, NetworkNames } from "@enkryptcom/types"; +import BitcoinProvider from ".."; +import AccountState from "../libs/accounts-state"; +import { ProviderRPCRequest } from "@/types/provider"; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_getNetwork") return next(); + else { + if (!payload.options || !payload.options.domain) { + return res(getCustomError("btc_getNetwork: invalid domain")); + } + + const accountsState = new AccountState(); + + accountsState + .getApprovedAddresses(payload.options!.domain) + .then((accounts) => { + if (!accounts.length) { + return res(null, ""); + } + if (this.network.name === NetworkNames.Bitcoin) + return res(null, "livenet"); + if (this.network.name === NetworkNames.BitcoinTest) + return res(null, "testnet"); + res(null, ""); + }); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts b/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts new file mode 100644 index 000000000..f47542501 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts @@ -0,0 +1,33 @@ +import { MiddlewareFunction } from "@enkryptcom/types"; +import { ProviderRPCRequest } from "@/types/provider"; +import AccountState from "../libs/accounts-state"; +import { getCustomError } from "@/libs/error"; +import BitcoinProvider from ".."; + +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_getPublicKey") return next(); + else { + if (payload.options && payload.options.domain) { + const accountsState = new AccountState(); + accountsState + .getApprovedAddresses(payload.options.domain) + .then((accounts) => { + if (accounts.length) { + this.KeyRing.getAccount(accounts[0]).then((pubAccounts) => { + res(null, pubAccounts.address.replace("0x", "")); + }); + } else { + res(null, ""); + } + }); + } else { + res(getCustomError("No domain set!")); + } + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts b/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts new file mode 100644 index 000000000..dcba6d17f --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts @@ -0,0 +1,80 @@ +import { CallbackFunction, MiddlewareFunction } from "@enkryptcom/types"; +import type BitcoinProvider from ".."; +import { ProviderRPCRequest } from "@/types/provider"; +import { WindowPromise } from "@/libs/window-promise"; +import AccountState from "../libs/accounts-state"; +import { getCustomError } from "@/libs/error"; +let isAccountAccessPending = false; +const pendingPromises: { + payload: ProviderRPCRequest; + res: CallbackFunction; +}[] = []; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_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 accountsState = new AccountState(); + accountsState + .getApprovedAddresses(_payload.options.domain) + .then((accounts) => { + if (accounts.length) { + _res(null, [ + accounts.map((acc) => this.network.displayAddress(acc))[0], + ]); + handleRemainingPromises(); + } else { + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.btcConnectDApp.path), + JSON.stringify({ + ..._payload, + params: [this.network.name], + }) + ) + .then(({ error, result }) => { + if (error) _res(error as any); + const accounts = JSON.parse(result || "[]"); + _res( + null, + accounts.map((acc: string) => + this.network.displayAddress(acc) + ) + ); + }) + .finally(handleRemainingPromises); + } + }); + } else { + _res(getCustomError("No domain set!")); + } + }; + handleAccountAccess(payload, res); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_signMessage.ts b/packages/extension/src/providers/solana/methods/btc_signMessage.ts new file mode 100644 index 000000000..aa555b97a --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_signMessage.ts @@ -0,0 +1,52 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import BitcoinProvider from ".."; +import { WindowPromise } from "@/libs/window-promise"; +import { ProviderRPCRequest } from "@/types/provider"; +import AccountState from "../libs/accounts-state"; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_signMessage") return next(); + else { + if (!payload.params || payload.params.length < 2) { + return res(getCustomError("btc_signMessage: invalid params")); + } + if (!payload.options || !payload.options.domain) { + return res(getCustomError("btc_signMessage: invalid domain")); + } + const msg = payload.params[0] as string; + const type = payload.params[1] as string; + const accountsState = new AccountState(); + + accountsState + .getApprovedAddresses(payload.options!.domain) + .then((accounts) => { + if (!accounts.length) { + return res(null, ""); + } + this.KeyRing.getAccount(accounts[0]).then((acc) => { + if (!acc) + return res(getCustomError("btc_signMessage: account not found")); + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.btcSign.path), + JSON.stringify({ + ...payload, + params: [msg, type, acc, this.network.name], + }), + true + ) + .then(({ error, result }) => { + if (error) return res(error); + res(null, JSON.parse(result as string)); + }); + }); + }); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_signPsbt.ts b/packages/extension/src/providers/solana/methods/btc_signPsbt.ts new file mode 100644 index 000000000..c63e52815 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_signPsbt.ts @@ -0,0 +1,55 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import BitcoinProvider from ".."; +import { WindowPromise } from "@/libs/window-promise"; +import { SignPSBTOptions } from "../types"; +import AccountState from "../libs/accounts-state"; +import { ProviderRPCRequest } from "@/types/provider"; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_signPsbt") return next(); + else { + if (!payload.params || payload.params.length < 2) { + return res( + getCustomError("btc_signPsbt: invalid request not enough params") + ); + } + if (!payload.options || !payload.options.domain) { + return res(getCustomError("btc_signPsbt: invalid domain")); + } + const psbt = payload.params[0] as string; + const options = payload.params[1] as SignPSBTOptions; + const accountsState = new AccountState(); + + accountsState + .getApprovedAddresses(payload.options!.domain) + .then((accounts) => { + if (!accounts.length) { + return res(null, ""); + } + this.KeyRing.getAccount(accounts[0]).then((acc) => { + if (!acc) + return res(getCustomError("btc_signPsbt: account not found")); + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.btcSendTransaction.path), + JSON.stringify({ + ...payload, + params: [psbt, options, acc, this.network.name], + }), + true + ) + .then(({ error, result }) => { + if (error) return res(error); + res(null, JSON.parse(result as string)); + }); + }); + }); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts b/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts new file mode 100644 index 000000000..a272e19d0 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts @@ -0,0 +1,60 @@ +import { getCustomError } from "@/libs/error"; +import { sendToBackgroundFromBackground } from "@/libs/messenger/extension"; +import { InternalMethods } from "@/types/messenger"; +import { ProviderRPCRequest } from "@/types/provider"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import BTCNetworks from "../networks"; +import DomainState from "@/libs/domain-state"; +import BitcoinProvider from ".."; +import { BitcoinNetworks } from "../types"; +import { trackNetworkSelected } from "@/libs/metrics"; +import { NetworkChangeEvents } from "@/libs/metrics/types"; +const method: MiddlewareFunction = function ( + this: BitcoinProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "btc_switchNetwork") return next(); + else { + if ( + !payload.params || + payload.params.length < 1 || + !Object.keys(BitcoinNetworks).includes(payload.params[0]) + ) { + return res(getCustomError("btc_switchNetwork: invalid params")); + } + const internalName = + BitcoinNetworks[payload.params![0] as keyof typeof BitcoinNetworks]; + const allNetworks = Object.values(BTCNetworks); + const validNetwork = allNetworks.find((net) => net.name === internalName); + if (validNetwork) { + trackNetworkSelected(NetworkChangeEvents.NetworkChangeAPI, { + provider: validNetwork.provider, + network: validNetwork.name, + }); + sendToBackgroundFromBackground({ + message: JSON.stringify({ + method: InternalMethods.changeNetwork, + params: [validNetwork.name], + }), + provider: validNetwork.provider, + tabId: payload.options?.tabId, + }).then(() => { + const domainState = new DomainState(); + domainState + .setSelectedNetwork(validNetwork.name) + .then(() => res(null, true)); + }); + } else { + return res( + getCustomError( + `btc_switchNetwork: porvided network ${ + payload.params![0] + } not supported` + ) + ); + } + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/index.ts b/packages/extension/src/providers/solana/methods/index.ts new file mode 100644 index 000000000..f2818c463 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/index.ts @@ -0,0 +1,16 @@ +import btcRequestAccounts from "./btc_requestAccounts"; +import btcSignMessage from "./btc_signMessage"; +import btcGetBalance from "./btc_getBalance"; +import btcSwitchNetwork from "./btc_switchNetwork"; +import btcGetPublicKey from "./btc_getPublicKey"; +import btcSignPsbt from "./btc_signPsbt"; +import btcGetNetwork from "./btc_getNetwork"; +export default [ + btcRequestAccounts, + btcSignMessage, + btcGetBalance, + btcSwitchNetwork, + btcGetPublicKey, + btcSignPsbt, + btcGetNetwork, +]; diff --git a/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts b/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts new file mode 100644 index 000000000..9ec11d360 --- /dev/null +++ b/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts @@ -0,0 +1,56 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { haskoinHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import { GasPriceTypes } from "@/providers/common/types"; +import HaskoinAPI from "../libs/api"; + +const bitcoinOptions: BitcoinNetworkOptions = { + name: NetworkNames.BitcoinTest, + name_long: "Bitcoin Testnet", + homePage: "https://bitcoin.org/en/", + blockExplorerTX: "https://www.blockchain.com/btc-testnet/tx/[[txHash]]", + blockExplorerAddr: + "https://www.blockchain.com/btc-testnet/address/[[address]]", + isTestNetwork: true, + currencyName: "tBTC", + currencyNameLong: "Test Bitcoin", + icon: require("./icons/tbtc.svg"), + decimals: 8, + dust: 0.00000546, + node: "https://partners.mewapi.io/nodes/hk/btct/", + activityHandler: wrapActivityHandler(haskoinHandler), + basePath: "m/49'/1'/0'/0", + coingeckoID: "bitcoin", + apiType: HaskoinAPI, + feeHandler: () => + Promise.resolve({ + [GasPriceTypes.FASTEST]: 25, + [GasPriceTypes.FAST]: 20, + [GasPriceTypes.REGULAR]: 10, + [GasPriceTypes.ECONOMY]: 5, + }), + networkInfo: { + name: NetworkNames.BitcoinTest, + messagePrefix: "\x18Bitcoin Signed Message:\n", + bech32: "tb", + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, + dustThreshold: null, + paymentType: PaymentType.P2WPKH, + maxFeeRate: 5000 * 2, + }, +}; + +const bitcoin = new BitcoinNetwork(bitcoinOptions); + +export default bitcoin; diff --git a/packages/extension/src/providers/solana/networks/bitcoin.ts b/packages/extension/src/providers/solana/networks/bitcoin.ts new file mode 100644 index 000000000..ccca0156e --- /dev/null +++ b/packages/extension/src/providers/solana/networks/bitcoin.ts @@ -0,0 +1,51 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { haskoinHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import BTCFeeHandler from "../libs/btc-fee-handler"; +import HaskoinAPI from "../libs/api"; +import shNFTHandler from "@/libs/nft-handlers/simplehash-ordinals"; + +const bitcoinOptions: BitcoinNetworkOptions = { + name: NetworkNames.Bitcoin, + name_long: "Bitcoin", + homePage: "https://bitcoin.org/en/", + blockExplorerTX: "https://mempool.space/tx/[[txHash]]", + blockExplorerAddr: "https://mempool.space/address/[[address]]", + isTestNetwork: false, + currencyName: "BTC", + currencyNameLong: "Bitcoin", + icon: require("./icons/btc.svg"), + decimals: 8, + node: "https://partners.mewapi.io/nodes/hk/btc/", + coingeckoID: "bitcoin", + activityHandler: wrapActivityHandler(haskoinHandler), + basePath: "m/49'/0'/0'/0", + feeHandler: BTCFeeHandler, + apiType: HaskoinAPI, + dust: 0.00000546, + NFTHandler: shNFTHandler, + networkInfo: { + name: NetworkNames.Bitcoin, + messagePrefix: "\x18Bitcoin Signed Message:\n", + bech32: "bc", + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + dustThreshold: null, + paymentType: PaymentType.P2WPKH, + maxFeeRate: 5000, + }, +}; + +const bitcoin = new BitcoinNetwork(bitcoinOptions); + +export default bitcoin; diff --git a/packages/extension/src/providers/solana/networks/dogecoin.ts b/packages/extension/src/providers/solana/networks/dogecoin.ts new file mode 100644 index 000000000..35937b318 --- /dev/null +++ b/packages/extension/src/providers/solana/networks/dogecoin.ts @@ -0,0 +1,51 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { ssHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import SSFeeHandler from "../libs/ss-fee-handler"; +import SSApi from "../libs/api-ss"; + +const dogeOptions: BitcoinNetworkOptions = { + name: NetworkNames.Dogecoin, + name_long: "Dogecoin", + homePage: "https://dogecoin.com/", + blockExplorerTX: "https://dogechain.info/tx/[[txHash]]", + blockExplorerAddr: "https://dogechain.info/address/[[address]]", + isTestNetwork: false, + currencyName: "Doge", + currencyNameLong: "Dogecoin", + icon: require("./icons/doge.svg"), + decimals: 8, + node: "https://partners.mewapi.io/nodes/ss/doge", + coingeckoID: "dogecoin", + apiType: SSApi, + dust: 0.01, + activityHandler: wrapActivityHandler(ssHandler), + basePath: "m/44'/3'/0'/0", + feeHandler: () => { + return SSFeeHandler("https://partners.mewapi.io/nodes/ss/doge/api/v1/fees"); + }, + networkInfo: { + name: NetworkNames.Dogecoin, + messagePrefix: "\x19Dogecoin Signed Message:\n", + bech32: "dc", + bip32: { + public: 0x02facafd, + private: 0x02fac398, + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e, + dustThreshold: null, + paymentType: PaymentType.P2PKH, + maxFeeRate: 100000 * 10, + }, +}; + +const dogecoin = new BitcoinNetwork(dogeOptions); + +export default dogecoin; diff --git a/packages/extension/src/providers/solana/networks/icons/btc.svg b/packages/extension/src/providers/solana/networks/icons/btc.svg new file mode 100644 index 000000000..f5889766e --- /dev/null +++ b/packages/extension/src/providers/solana/networks/icons/btc.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/packages/extension/src/providers/solana/networks/icons/doge.svg b/packages/extension/src/providers/solana/networks/icons/doge.svg new file mode 100644 index 000000000..c435731dc --- /dev/null +++ b/packages/extension/src/providers/solana/networks/icons/doge.svg @@ -0,0 +1 @@ +Dogecoin (DOGE) \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/icons/ltc.svg b/packages/extension/src/providers/solana/networks/icons/ltc.svg new file mode 100644 index 000000000..13e76a40e --- /dev/null +++ b/packages/extension/src/providers/solana/networks/icons/ltc.svg @@ -0,0 +1 @@ +litecoin-ltc-logo \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/icons/tbtc.svg b/packages/extension/src/providers/solana/networks/icons/tbtc.svg new file mode 100644 index 000000000..9323b5d8a --- /dev/null +++ b/packages/extension/src/providers/solana/networks/icons/tbtc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/index.ts b/packages/extension/src/providers/solana/networks/index.ts new file mode 100644 index 000000000..2cf52f449 --- /dev/null +++ b/packages/extension/src/providers/solana/networks/index.ts @@ -0,0 +1,11 @@ +import btcNode from "./bitcoin"; +import btcTestNode from "./bitcoin-testnet"; +import ltcNode from "./litecoin"; +import dogeNode from "./dogecoin"; + +export default { + bitcoin: btcNode, + bitcoinTest: btcTestNode, + litecoin: ltcNode, + dogecoin: dogeNode, +}; diff --git a/packages/extension/src/providers/solana/networks/litecoin.ts b/packages/extension/src/providers/solana/networks/litecoin.ts new file mode 100644 index 000000000..a8dedbd5c --- /dev/null +++ b/packages/extension/src/providers/solana/networks/litecoin.ts @@ -0,0 +1,51 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { + BitcoinNetwork, + BitcoinNetworkOptions, + PaymentType, +} from "../types/bitcoin-network"; +import { ssHandler } from "../libs/activity-handlers"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import SSFeeHandler from "../libs/ss-fee-handler"; +import SSApi from "../libs/api-ss"; + +const litecoinOptions: BitcoinNetworkOptions = { + name: NetworkNames.Litecoin, + name_long: "Litecoin", + homePage: "https://litecoin.org/", + blockExplorerTX: "https://explorer.btc.com/ltc/transaction/[[txHash]]", + blockExplorerAddr: "https://explorer.btc.com/ltc/address/[[address]]", + isTestNetwork: false, + currencyName: "LTC", + currencyNameLong: "Litecoin", + icon: require("./icons/ltc.svg"), + decimals: 8, + node: "https://partners.mewapi.io/nodes/ss/ltc", + coingeckoID: "litecoin", + dust: 0.0001, + apiType: SSApi, + activityHandler: wrapActivityHandler(ssHandler), + basePath: "m/49'/2'/0'/0", + feeHandler: () => { + return SSFeeHandler("https://partners.mewapi.io/nodes/ss/ltc/api/v1/fees"); + }, + networkInfo: { + name: NetworkNames.Litecoin, + messagePrefix: "\x19Litecoin Signed Message:\n", + bech32: "ltc", + bip32: { + public: 0x019da462, + private: 0x019d9cfe, + }, + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0, + dustThreshold: null, + paymentType: PaymentType.P2WPKH, + maxFeeRate: 5000 * 2, + }, +}; + +const litecoin = new BitcoinNetwork(litecoinOptions); + +export default litecoin; diff --git a/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts b/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts new file mode 100644 index 000000000..82b3e9506 --- /dev/null +++ b/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts @@ -0,0 +1,16 @@ +import { expect } from "chai"; +import bitcoinNetworks from "../networks"; +const pubkey = + "0x021aa21d5f77b1be591d0a0a847cb7412a344f4e768b93d55b3eeab3b7e8a4a252"; +describe("Should derive proper bitcoin addresses", () => { + it("should derive segwit address", async () => { + const bitcoinMain = bitcoinNetworks.bitcoin; + expect(bitcoinMain.displayAddress(pubkey)).to.be.eq( + "bc1qnjmf6vcjpyru5t8y2936260mrqa305qactwds2" + ); + const bitcoinTest = bitcoinNetworks.bitcoinTest; + expect(bitcoinTest.displayAddress(pubkey)).to.be.eq( + "tb1qnjmf6vcjpyru5t8y2936260mrqa305qajd47te" + ); + }); +}); diff --git a/packages/extension/src/providers/solana/types/index.ts b/packages/extension/src/providers/solana/types/index.ts new file mode 100644 index 000000000..04213aac7 --- /dev/null +++ b/packages/extension/src/providers/solana/types/index.ts @@ -0,0 +1,3 @@ +import type { Provider as InjectedProvider } from "../inject"; + +export { InjectedProvider }; diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts new file mode 100644 index 000000000..167faa525 --- /dev/null +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -0,0 +1,132 @@ +import { BaseNetwork, BaseNetworkOptions } from "@/types/base-network"; +import BitcoinAPI from "@/providers/bitcoin/libs/api"; +import { AssetsType } from "@/types/provider"; +import { BaseToken, BaseTokenOptions } from "@/types/base-token"; +import { ProviderName } from "@/types/provider"; +import { NetworkNames, SignerType } from "@enkryptcom/types"; +import createIcon from "../libs/blockies"; +import { Activity } from "@/types/activity"; +import { + formatFiatValue, + formatFloatingPointValue, +} from "@/libs/utils/number-formatter"; +import MarketData from "@/libs/market-data"; +import BigNumber from "bignumber.js"; +import { CoinGeckoTokenMarket } from "@/libs/market-data/types"; +import Sparkline from "@/libs/sparkline"; +import { SOLToken } from "./sol-token"; +import { NFTCollection } from "@/types/nft"; +import { fromBase } from "@enkryptcom/utils"; + +export interface SolanaNetworkOptions { + name: NetworkNames; + name_long: string; + homePage: string; + blockExplorerTX: string; + blockExplorerAddr: string; + isTestNetwork: boolean; + currencyName: string; + currencyNameLong: string; + icon: string; + decimals: number; + node: string; + coingeckoID?: string; + basePath: string; + dust: number; + NFTHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + activityHandler: ( + network: BaseNetwork, + address: string + ) => Promise; +} + +export const getAddress = (pubkey: string) => { + return pubkey as string; +}; +export class BitcoinNetwork extends BaseNetwork { + public assets: BaseToken[] = []; + private activityHandler: ( + network: BaseNetwork, + address: string + ) => Promise; + NFTHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; + constructor(options: SolanaNetworkOptions) { + const api = async () => { + const api = new Api(options.node); + await api.init(); + return api as BitcoinAPI; + }; + + const baseOptions: BaseNetworkOptions = { + identicon: createIcon, + signer: [SignerType.secp256k1btc], + provider: ProviderName.bitcoin, + displayAddress: (pubkey: string) => getAddress(pubkey), + api, + ...options, + }; + super(baseOptions); + this.activityHandler = options.activityHandler; + this.NFTHandler = options.NFTHandler; + } + + public async getAllTokens(pubkey: string): Promise { + const assets = await this.getAllTokenInfo(pubkey); + return assets.map((token) => { + const bTokenOptions: BaseTokenOptions = { + decimals: token.decimals, + icon: token.icon, + name: token.name, + symbol: token.symbol, + balance: token.balance, + price: token.value, + coingeckoID: this.coingeckoID, + }; + return new BTCToken(bTokenOptions); + }); + } + + public async getAllTokenInfo(pubkey: string): Promise { + const balance = await (await this.api()).getBalance(pubkey); + let marketData: (CoinGeckoTokenMarket | null)[] = []; + if (this.coingeckoID) { + const market = new MarketData(); + marketData = await market.getMarketData([this.coingeckoID]); + } + const userBalance = fromBase(balance, this.decimals); + const usdBalance = new BigNumber(userBalance).times( + marketData.length ? marketData[0]!.current_price : 0 + ); + const nativeAsset: AssetsType = { + balance: balance, + balancef: formatFloatingPointValue(userBalance).value, + balanceUSD: usdBalance.toNumber(), + balanceUSDf: formatFiatValue(usdBalance.toString()).value, + icon: this.icon, + name: this.name_long, + symbol: this.currencyName, + value: marketData.length ? marketData[0]!.current_price.toString() : "0", + valuef: formatFiatValue( + marketData.length ? marketData[0]!.current_price.toString() : "0" + ).value, + contract: "", + decimals: this.decimals, + sparkline: marketData.length + ? new Sparkline(marketData[0]!.sparkline_in_7d.price, 25).dataValues + : "", + priceChangePercentage: marketData.length + ? marketData[0]!.price_change_percentage_7d_in_currency + : 0, + }; + return [nativeAsset]; + } + public getAllActivity(address: string): Promise { + return this.activityHandler(this, address); + } +} diff --git a/packages/extension/src/providers/solana/types/sol-token.ts b/packages/extension/src/providers/solana/types/sol-token.ts new file mode 100644 index 000000000..296e08103 --- /dev/null +++ b/packages/extension/src/providers/solana/types/sol-token.ts @@ -0,0 +1,19 @@ +import { BaseToken, BaseTokenOptions } from "@/types/base-token"; +import SolanaAPI from "@/providers/bitcoin/libs/api"; + +export class SOLToken extends BaseToken { + constructor(options: BaseTokenOptions) { + super(options); + } + + public async getLatestUserBalance( + api: SolanaAPI, + pubkey: string + ): Promise { + return api.getBalance(pubkey); + } + + public async send(): Promise { + throw new Error("EVM-send is not implemented here"); + } +} diff --git a/packages/extension/src/providers/solana/ui/btc-connect-dapp.vue b/packages/extension/src/providers/solana/ui/btc-connect-dapp.vue new file mode 100644 index 000000000..0dd5ec9b8 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/btc-connect-dapp.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/btc-sign-message.vue b/packages/extension/src/providers/solana/ui/btc-sign-message.vue new file mode 100644 index 000000000..219ff065e --- /dev/null +++ b/packages/extension/src/providers/solana/ui/btc-sign-message.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/btc-verify-transaction.vue b/packages/extension/src/providers/solana/ui/btc-verify-transaction.vue new file mode 100644 index 000000000..484cda6c5 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/btc-verify-transaction.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/index.ts b/packages/extension/src/providers/solana/ui/index.ts new file mode 100644 index 000000000..865942ef0 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/index.ts @@ -0,0 +1,7 @@ +import { ProviderName, UIExportOptions } from "@/types/provider"; +import getRoutes from "./routes"; +const uiExport: UIExportOptions = { + providerName: ProviderName.bitcoin, + routes: getRoutes(ProviderName.bitcoin), +}; +export default uiExport; diff --git a/packages/extension/src/providers/solana/ui/libs/signer.ts b/packages/extension/src/providers/solana/ui/libs/signer.ts new file mode 100644 index 000000000..161fdc71d --- /dev/null +++ b/packages/extension/src/providers/solana/ui/libs/signer.ts @@ -0,0 +1,134 @@ +import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger"; +import { SignerTransactionOptions, SignerMessageOptions } from "../types"; +import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; +import { hexToBuffer, bufferToHex } from "@enkryptcom/utils"; +import { Psbt } from "bitcoinjs-lib"; +import { BitcoinNetwork, PaymentType } from "../../types/bitcoin-network"; +import { EnkryptAccount } from "@enkryptcom/types"; +import { signMessageOfBIP322Simple } from "../../libs/bip322-message-sign"; +import { magicHash, toCompact } from "../../libs/sign-message-utils"; + +const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => { + return { + publicKey: hexToBuffer(account.address), + network: network.networkInfo, + sign: (hash: Buffer): Promise => { + return sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [bufferToHex(hash), account], + }).then((res) => { + if (res.error) { + return Promise.reject({ + error: res.error, + }); + } else { + return hexToBuffer(JSON.parse(res.result!)).subarray(0, 64); + } + }); + }, + }; +}; + +const TransactionSigner = ( + options: SignerTransactionOptions +): Promise => { + const { account, network, payload } = options; + if (account.isHardware) { + throw new Error("btc-hardware not implemented"); + } else { + const tx = new Psbt({ + network: network.networkInfo, + maximumFeeRate: network.networkInfo.maxFeeRate, + }); + payload.inputs + .map((u) => { + const res: { + hash: string; + index: number; + witnessUtxo?: { script: Buffer; value: number }; + nonWitnessUtxo?: Buffer; + } = { + hash: u.hash, + index: u.index, + }; + if (network.networkInfo.paymentType === PaymentType.P2WPKH) { + res.witnessUtxo = { + script: Buffer.from(u.witnessUtxo.script, "hex"), + value: u.witnessUtxo.value, + }; + } else if (network.networkInfo.paymentType === PaymentType.P2PKH) { + res.nonWitnessUtxo = Buffer.from(u.raw, "hex"); + } + return res; + }) + .forEach((input) => tx.addInput(input)); + payload.outputs.forEach((output) => tx.addOutput(output)); + const signer = PSBTSigner(account, network); + return tx.signAllInputsAsync(signer).then(() => { + tx.finalizeAllInputs(); + return tx; + }); + } +}; + +const MessageSigner = ( + options: SignerMessageOptions +): Promise => { + const { account, payload, network } = options; + if (account.isHardware) { + throw new Error("btc-hardware not implemented"); + } else { + if (options.type === "bip322-simple") { + const signer = PSBTSigner(account, network); + return signMessageOfBIP322Simple({ + address: account.address, + message: payload.toString(), + network: network, + Signer: signer, + }) + .then((sig) => { + return { + result: JSON.stringify(sig), + }; + }) + .catch((e) => { + return { + error: e.message, + }; + }); + } else { + const signer = { + sign: ( + hash: Buffer + ): Promise<{ signature: Buffer; recovery: number }> => { + return sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [bufferToHex(hash), account], + }).then((res) => { + if (res.error) { + return Promise.reject({ + error: res.error, + }); + } else { + const sigBuffer = hexToBuffer(JSON.parse(res.result!)); + return { + signature: sigBuffer.subarray(0, 64), + recovery: sigBuffer[64], + }; + } + }); + }, + }; + const mHash = magicHash(payload); + return signer.sign(mHash).then((sig) => { + return { + result: JSON.stringify( + toCompact(sig.recovery, sig.signature, true).toString("base64") + ), + }; + }); + } + } +}; + +export { TransactionSigner, MessageSigner, PSBTSigner }; diff --git a/packages/extension/src/providers/solana/ui/libs/tx-size.ts b/packages/extension/src/providers/solana/ui/libs/tx-size.ts new file mode 100644 index 000000000..4dcb707eb --- /dev/null +++ b/packages/extension/src/providers/solana/ui/libs/tx-size.ts @@ -0,0 +1,260 @@ +// https://github.com/jlopp/bitcoin-transaction-size-calculator/blob/master/index.html + +import { toBN } from "web3-utils"; +import { PaymentType } from "../../types/bitcoin-network"; + +enum InputScriptType { + P2PKH = "P2PKH", + P2SH = "P2SH", + "P2SH-P2WPKH" = "P2SH-P2WPKH", + "P2SH-P2WSH" = "P2SH-P2WSH", + P2WPKH = "P2WPKH", + P2WSH = "P2WSH", + P2TR = "P2TR", +} +const P2PKH_IN_SIZE = 148; +const P2PKH_OUT_SIZE = 34; + +const P2SH_OUT_SIZE = 32; +const P2SH_P2WPKH_OUT_SIZE = 32; +const P2SH_P2WSH_OUT_SIZE = 32; + +// All segwit input sizes are reduced by 1 WU to account for the witness item counts being added for every input per the transaction header +const P2SH_P2WPKH_IN_SIZE = 90.75; + +const P2WPKH_IN_SIZE = 67.75; +const P2WPKH_OUT_SIZE = 31; + +const P2WSH_OUT_SIZE = 43; +const P2TR_OUT_SIZE = 43; + +const P2TR_IN_SIZE = 57.25; + +const PUBKEY_SIZE = 33; +const SIGNATURE_SIZE = 72; + +const getSizeOfVarInt = (length: number) => { + if (length < 253) { + return 1; + } else if (length < 65535) { + return 3; + } else if (length < 4294967295) { + return 5; + } else if (length < toBN("18446744073709551615").toNumber()) { + return 9; + } else { + alert("Invalid var int"); + } +}; + +const getSizeOfScriptLengthElement = (length: number) => { + if (length < 75) { + return 1; + } else if (length <= 255) { + return 2; + } else if (length <= 65535) { + return 3; + } else if (length <= 4294967295) { + return 5; + } else { + alert("Size of redeem script is too large"); + } +}; + +const getTxOverheadExtraRawBytes = ( + input_script: InputScriptType, + input_count: number +) => { + let witness_bytes = 0; + // Returns the remaining 3/4 bytes per witness bytes + if ( + input_script !== InputScriptType.P2PKH && + input_script !== InputScriptType.P2SH + ) { + // Transactions with segwit inputs have extra overhead + witness_bytes = + 0.25 + // segwit marker + 0.25 + // segwit flag + input_count / 4; // witness element count per input + } + return witness_bytes * 3; +}; + +const getTxOverheadVBytes = ( + input_script: InputScriptType, + input_count: number, + output_count: number +) => { + let witness_vbytes = 0; + if ( + input_script != InputScriptType.P2PKH && + input_script != InputScriptType.P2SH + ) { + // Transactions with segwit inputs have extra overhead + witness_vbytes = + 0.25 + // segwit marker + 0.25 + // segwit flag + input_count / 4; // witness element count per input + } + return ( + 4 + // nVersion + getSizeOfVarInt(input_count)! + // number of inputs + getSizeOfVarInt(output_count)! + // number of outputs + 4 + // nLockTime + witness_vbytes + ); +}; + +interface calcInputType { + input_script?: InputScriptType; + input_n?: number; + input_m?: number; + input_count: number; +} +interface calcOutputType { + p2pkh_output_count?: number; + p2sh_output_count?: number; + p2sh_p2wpkh_output_count?: number; + p2sh_p2wsh_output_count?: number; + p2wpkh_output_count?: number; + p2wsh_output_count?: number; + p2tr_output_count?: number; +} +const calculateSize = ( + inputOptions: calcInputType, + outputOptions: calcOutputType +) => { + const defaultInputOptions = { + input_script: InputScriptType.P2WPKH, + input_m: 1, + input_n: 1, + }; + const defaultOutputOptions = { + p2pkh_output_count: 0, + p2sh_output_count: 0, + p2sh_p2wpkh_output_count: 0, + p2sh_p2wsh_output_count: 0, + p2wpkh_output_count: 0, + p2wsh_output_count: 0, + p2tr_output_count: 0, + }; + const _inputOptions = { ...defaultInputOptions, ...inputOptions }; + const _outputOptions = { ...defaultOutputOptions, ...outputOptions }; + const { input_script, input_n, input_m, input_count } = _inputOptions; + const { + p2pkh_output_count, + p2sh_output_count, + p2sh_p2wpkh_output_count, + p2sh_p2wsh_output_count, + p2wpkh_output_count, + p2wsh_output_count, + p2tr_output_count, + } = _outputOptions; + + const output_count = + p2pkh_output_count + + p2sh_output_count + + p2sh_p2wpkh_output_count + + p2sh_p2wsh_output_count + + p2wpkh_output_count + + p2wsh_output_count + + p2tr_output_count; + // In most cases the input size is predictable. For multisig inputs we need to perform a detailed calculation + let inputSize = 0; // in virtual bytes + let inputWitnessSize = 0; + let redeemScriptSize = 0; + let scriptSigSize = 0; + switch (input_script) { + case "P2PKH": + inputSize = P2PKH_IN_SIZE; + break; + case "P2SH-P2WPKH": + inputSize = P2SH_P2WPKH_IN_SIZE; + inputWitnessSize = 107; // size(signature) + signature + size(pubkey) + pubkey + break; + case "P2WPKH": + inputSize = P2WPKH_IN_SIZE; + inputWitnessSize = 107; // size(signature) + signature + size(pubkey) + pubkey + break; + case "P2TR": // Only consider the cooperative taproot signing path; assume multisig is done via aggregate signatures + inputSize = P2TR_IN_SIZE; + inputWitnessSize = 65; // getSizeOfVarInt(schnorrSignature) + schnorrSignature; + break; + case "P2SH": + redeemScriptSize = + 1 + // OP_M + input_n * (1 + PUBKEY_SIZE) + // OP_PUSH33 + 1 + // OP_N + 1; // OP_CHECKMULTISIG + scriptSigSize = + 1 + // size(0) + input_m * (1 + SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature + getSizeOfScriptLengthElement(redeemScriptSize)! + + redeemScriptSize; + inputSize = 32 + 4 + getSizeOfVarInt(scriptSigSize)! + scriptSigSize + 4; + break; + case "P2SH-P2WSH": + case "P2WSH": + redeemScriptSize = + 1 + // OP_M + input_n * (1 + PUBKEY_SIZE) + // OP_PUSH33 + 1 + // OP_N + 1; // OP_CHECKMULTISIG + inputWitnessSize = + 1 + // size(0) + input_m * (1 + SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature + getSizeOfScriptLengthElement(redeemScriptSize)! + + redeemScriptSize; + inputSize = + 36 + // outpoint (spent UTXO ID) + inputWitnessSize / 4 + // witness program + 4; // nSequence + if (input_script == "P2SH-P2WSH") { + inputSize += 32 + 3; // P2SH wrapper (redeemscript hash) + overhead? + } + } + const txVBytes = + getTxOverheadVBytes(input_script, input_count, output_count) + + inputSize * input_count + + P2PKH_OUT_SIZE * p2pkh_output_count + + P2SH_OUT_SIZE * p2sh_output_count + + P2SH_P2WPKH_OUT_SIZE * p2sh_p2wpkh_output_count + + P2SH_P2WSH_OUT_SIZE * p2sh_p2wsh_output_count + + P2WPKH_OUT_SIZE * p2wpkh_output_count + + P2WSH_OUT_SIZE * p2wsh_output_count + + P2TR_OUT_SIZE * p2tr_output_count; + const txBytes = + getTxOverheadExtraRawBytes(input_script, input_count)! + + txVBytes + + (inputWitnessSize * input_count * 3) / 4; + const txWeight = txVBytes * 4; + return { + txVBytes, + txBytes, + txWeight, + }; +}; +const calculateSizeBasedOnType = ( + numInputs: number, + numOutputs: number, + type: PaymentType +): number => { + const output: calcOutputType = {}; + if (type === PaymentType.P2PKH) { + output.p2pkh_output_count = numOutputs; + } else { + output.p2wpkh_output_count = numOutputs; + } + const size = calculateSize( + { + input_script: + type === PaymentType.P2PKH + ? InputScriptType.P2PKH + : InputScriptType.P2WPKH, + input_count: numInputs, + }, + output + ); + return type === PaymentType.P2PKH ? size.txBytes : size.txVBytes; +}; +export { InputScriptType, calculateSize, calculateSizeBasedOnType }; diff --git a/packages/extension/src/providers/solana/ui/routes/index.ts b/packages/extension/src/providers/solana/ui/routes/index.ts new file mode 100644 index 000000000..fc3707ea1 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/routes/index.ts @@ -0,0 +1,18 @@ +import btcSign from "../btc-sign-message.vue"; +import btcSendTransaction from "../btc-verify-transaction.vue"; +import btcConnectDApp from "../btc-connect-dapp.vue"; +import btcHWVerify from "../send-transaction/verify-transaction/index.vue"; +import { RouteRecordRaw } from "vue-router"; +import RouteNames from "./names"; +const routes = Object.assign({}, RouteNames); +routes.btcSign.component = btcSign; +routes.btcSendTransaction.component = btcSendTransaction; +routes.btcConnectDApp.component = btcConnectDApp; +routes.btcHWVerify.component = btcHWVerify; +export default (namespace: string): RouteRecordRaw[] => { + return Object.values(routes).map((route) => { + route.path = `/${namespace}/${route.path}`; + route.name = `${namespace}-${String(route.name)}`; + return route; + }); +}; diff --git a/packages/extension/src/providers/solana/ui/routes/names.ts b/packages/extension/src/providers/solana/ui/routes/names.ts new file mode 100644 index 000000000..021efe31d --- /dev/null +++ b/packages/extension/src/providers/solana/ui/routes/names.ts @@ -0,0 +1,22 @@ +export default { + btcSign: { + path: "btc-sign", + name: "btcSign", + component: {}, + }, + btcSendTransaction: { + path: "btc-send-transaction", + name: "btcSendTransaction", + component: {}, + }, + btcConnectDApp: { + path: "btc-connect-dapp", + name: "btcConnectDApp", + component: {}, + }, + btcHWVerify: { + path: "btc-hw-verify", + name: "btcHWVerify", + component: {}, + }, +}; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue new file mode 100644 index 000000000..9e39d9328 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue new file mode 100644 index 000000000..9f33862f8 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue new file mode 100644 index 000000000..b86d1f202 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue new file mode 100644 index 000000000..8d309b56d --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -0,0 +1,564 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue new file mode 100644 index 000000000..081c6d6fb --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/styles/common-popup.less b/packages/extension/src/providers/solana/ui/styles/common-popup.less new file mode 100644 index 000000000..f8d68b8e2 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/styles/common-popup.less @@ -0,0 +1,219 @@ +@import "~@action/styles/theme.less"; + +.common-popup { + width: 100%; + height: 100%; + + &__header { + padding: 28px 0 8px 0; + } + + &__content { + height: calc(~"100% - 52px"); + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + } + + &__wrap { + width: 100%; + } + + &__network { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + position: absolute; + right: 56px; + top: 28px; + + img { + width: 16px; + height: 16px; + margin-right: 8px; + } + + p { + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 16px; + text-align: right; + letter-spacing: 0.5px; + color: @primaryLabel; + margin: 0; + } + } + + h2 { + font-style: normal; + font-weight: 700; + font-size: 34px; + line-height: 40px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0 0 16px 0; + } + + &__block { + background: @lightBg; + border: 1px solid @gray01; + box-sizing: border-box; + border-radius: 12px; + padding: 10px 16px; + width: 100%; + margin: 0 0 16px 0; + + &.no-inset { + margin: 0; + } + + &.no-padding { + padding: 0; + } + } + + &__message { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0 0 6px 0; + height: auto; + max-height: 180px; + overflow: auto; + word-break: break-all; + } + + &__account { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + + img { + width: 32px; + height: 32px; + margin-right: 12px; + border-radius: 100%; + } + + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + p { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0; + word-break: break-all; + } + } + } + + &__info { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + padding: 6px 0; + margin-bottom: 6px; + + img { + width: 32px; + height: 32px; + margin-right: 12px; + } + + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + p { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0; + word-break: break-all; + } + } + } + + &__buttons { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + width: 100%; + box-sizing: border-box; + position: absolute; + left: 0; + bottom: 0; + font-size: 0; + padding: 24px; + box-sizing: border-box; + background-color: @white; + &-cancel { + width: 172px; + } + &-send { + width: 232px; + } + + &.border { + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.05), + 0px 0px 1px rgba(0, 0, 0, 0.25); + } + } + + &__scroll-area { + position: relative; + margin: auto; + width: calc(~"100% + 53px"); + height: calc(~"100% - 88px"); + margin: 0; + padding: 0 53px 0 0 !important; + margin-right: -53px; + box-sizing: border-box; + + &.ps--active-y { + padding-bottom: 0 !important; + } + + & > .ps__rail-y { + right: 0 !important; + } + } +} + +.ps--active-y { + .common-popup__content { + height: auto; + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/solana/ui/styles/verify-transaction.less b/packages/extension/src/providers/solana/ui/styles/verify-transaction.less new file mode 100644 index 000000000..fa241147e --- /dev/null +++ b/packages/extension/src/providers/solana/ui/styles/verify-transaction.less @@ -0,0 +1,479 @@ +.provider-verify-transaction { + width: 100%; + height: 100%; + padding-top: 44px; + padding-bottom: 76px; + box-sizing: border-box; + &__logo { + margin-bottom: 8px; + } + &__network { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + position: absolute; + right: 56px; + top: 54px; + + img { + width: 16px; + height: 16px; + margin-right: 8px; + } + + p { + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 16px; + text-align: right; + letter-spacing: 0.5px; + color: @primaryLabel; + margin: 0; + } + } + h2 { + font-style: normal; + font-weight: 700; + font-size: 34px; + line-height: 40px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0 0 16px 0; + } + &__block { + background: @lightBg; + border: 1px solid @gray01; + box-sizing: border-box; + border-radius: 12px; + padding: 10px 16px; + width: 100%; + margin: 0 0 12px 0; + } + &__amount { + display: flex; + justify-content: flex-start; + align-items: flex-start; + flex-direction: row; + + img { + box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.16); + width: 32px; + height: 32px; + margin-right: 12px; + border-radius: 100%; + } + + &-info { + h4 { + font-style: normal; + font-weight: 700; + font-size: 24px; + line-height: 32px; + color: @primaryLabel; + margin: 0; + + span { + font-variant: small-caps; + } + } + + p { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @secondaryLabel; + margin: 0; + } + } + } + &__account { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + + &.from { + margin-bottom: 12px; + } + + img { + width: 32px; + height: 32px; + margin-right: 12px; + border-radius: 100%; + } + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + h6 { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0; + } + + div { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + + p { + &:first-child { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0 8px 0 0; + + span { + font-variant: small-caps; + } + } + + &:last-child { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @tertiaryLabel; + margin: 0; + word-break: break-all; + } + } + } + + &-to { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + word-break: break-all; +} + } + } + &__error { + margin: 12px 0 0 0; + border-radius: 10px; + padding: 0 0 0 44px; + position: relative; + box-sizing: border-box; + svg { + position: absolute; + left: 0; + top: 0; + } + p { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @error; + margin: 0; + a { + color: @error; + &:hover { + text-decoration: none; + } + } + } + } + &__message { + background: @lightBg; + border: 1px solid @gray01; + box-sizing: border-box; + border-radius: 12px; + padding: 10px 16px; + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0 0 6px 0; + height: auto; + max-height: 180px; + overflow: auto; + word-break: break-all; + } + &__info { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + padding: 6px 0; + margin-bottom: 6px; + img { + width: 32px; + height: 32px; + margin-right: 12px; + } + &-info { + h4 { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @primaryLabel; + margin: 0; + } + + p { + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @tertiaryLabel; + margin: 0; + word-break: break-all; + } + } + } + &__data { + text-align: center; + padding-top: 4px; + padding-bottom: 20px; + &-link { + border-radius: 6px; + transition: background 300ms ease-in-out; + display: inline-block; + cursor: pointer; + text-decoration: none; + padding: 4px 24px 4px 8px; + position: relative; + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.8px; + color: @primaryLabel; + + &:hover { + background: rgba(0, 0, 0, 0.04); + } + + svg { + position: absolute; + right: 4px; + top: 4px; + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -o-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; + } + + &.open { + svg { + position: absolute; + right: 4px; + top: 4px; + -moz-transform: rotate(180deg); + -webkit-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); + } + } + } + + &-text { + padding-top: 12px; + text-align: left; + font-family: 'SF Mono', 'Segoe UI Mono', 'Menlo', 'Consolas'; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + + p { + font-family: 'SF Mono', 'Segoe UI Mono', 'Menlo', 'Consolas'; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + color: @secondaryLabel; + margin: 0; + + a { + color: @secondaryLabel; + } + } + + li { + list-style: none; + } + } + } + &__buttons { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + width: 100%; + box-sizing: border-box; + position: absolute; + left: 0; + bottom: 0; + font-size: 0; + padding: 24px; + box-sizing: border-box; + background-color: @white; + &-cancel { + width: 172px; + } + &-send { + width: 232px; + } + + &.border { + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.05), + 0px 0px 1px rgba(0, 0, 0, 0.25); + } + } + + &__scroll-area { + position: relative; + margin: auto; + width: calc(~"100% + 16px"); + max-height: 350px; + margin: 0; + padding: 0 16px 0 0 !important; + margin-right: -16px; + box-sizing: border-box; + + &.ps--active-y { + padding-bottom: 0 !important; + } + + & > .ps__rail-y { + right: 0 !important; + } + } + + &__hw { + padding: 2px 0 2px 44px; + position: relative; + + svg { + position: absolute; + left: 0; + top: 50%; + margin-top: -16px; + } + + p { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @secondaryLabel; + margin: 0; + } + } + + &__fee { + height: 40px; + background: @lightBg; + margin: 0 0 12px 0; + box-sizing: border-box; + border: 1px solid @gray01; + box-sizing: border-box; + border-radius: 10px; + width: 100%; + padding: 16px 10px; + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + position: relative; + cursor: pointer; + text-decoration: none; + + &-value { + display: flex; + justify-content: flex-start; + align-items: center; + flex-direction: row; + + &-fiat { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @secondaryLabel; + margin: 0 8px 0 0; + } + + &-crypto { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @tertiaryLabel; + margin: 0; + + span { + font-variant: small-caps; + } + } + } + } + + &__error { + margin: 0 0 8px 0; + background: @error01; + border-radius: 10px; + padding: 12px 16px 12px 57px; + position: relative; + box-sizing: border-box; + + svg { + position: absolute; + left: 16px; + top: 50%; + margin-top: -12px; + } + p { + font-weight: 400; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: @error; + margin: 0; + } + } +} \ No newline at end of file diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts new file mode 100644 index 000000000..2020c148c --- /dev/null +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -0,0 +1,47 @@ +import { ToTokenData } from "@/ui/action/types/token"; +import { EnkryptAccount } from "@enkryptcom/types"; +import { GasPriceTypes } from "@/providers/common/types"; +import { BitcoinNetwork } from "../types/bitcoin-network"; +import { NFTItemWithCollectionName } from "@/types/nft"; + +export interface GasFeeInfo { + nativeValue: string; + fiatValue: string; + nativeSymbol: string; + fiatSymbol: string; +} +export interface BTCTxInfo { + inputs: any[]; + outputs: { address: string; value: number }[]; +} +export interface GasFeeType { + [GasPriceTypes.ECONOMY]: GasFeeInfo; + [GasPriceTypes.REGULAR]: GasFeeInfo; + [GasPriceTypes.FAST]: GasFeeInfo; + [GasPriceTypes.FASTEST]: GasFeeInfo; +} + +export interface VerifyTransactionParams { + isNFT: boolean; + NFTData?: NFTItemWithCollectionName; + fromAddress: string; + fromAddressName: string; + toAddress: string; + toToken: ToTokenData; + gasFee: GasFeeInfo; + gasPriceType: GasPriceTypes; + TxInfo: string; +} + +export interface SignerTransactionOptions { + payload: BTCTxInfo; + network: BitcoinNetwork; + account: EnkryptAccount; +} + +export interface SignerMessageOptions { + payload: Buffer; + network: BitcoinNetwork; + account: EnkryptAccount; + type: string; +} From d1b0c01922fc71506afc91fc1d11439283453c27 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:14:36 -0700 Subject: [PATCH 02/20] devop: sol --- packages/extension/src/providers/solana/types/sol-network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index 167faa525..eeb1e37c9 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -88,7 +88,7 @@ export class BitcoinNetwork extends BaseNetwork { price: token.value, coingeckoID: this.coingeckoID, }; - return new BTCToken(bTokenOptions); + return new SOLToken(bTokenOptions); }); } From e6dcc32b0c96b1d9381188c35773a5a268c0c35c Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:42:06 -0700 Subject: [PATCH 03/20] devop: sol stuff --- packages/extension/package.json | 2 + .../extension/src/libs/background/index.ts | 4 +- .../extension/src/libs/background/types.ts | 10 +- .../src/libs/utils/initialize-wallet.ts | 9 + packages/extension/src/libs/utils/networks.ts | 8 + packages/extension/src/providers/index.ts | 2 + .../extension/src/providers/solana/index.ts | 13 +- .../solana/libs/activity-handlers/index.ts | 3 - .../providers/haskoin/index.ts | 95 ----------- .../activity-handlers/providers/ss/index.ts | 108 ------------ .../src/providers/solana/libs/api-ss.ts | 135 --------------- .../src/providers/solana/libs/api.ts | 92 ++-------- .../solana/libs/bip322-message-sign.ts | 161 ------------------ .../providers/solana/libs/btc-fee-handler.ts | 22 --- .../providers/solana/libs/filter-ordinals.ts | 55 ------ .../solana/libs/sign-message-utils.ts | 47 ----- .../providers/solana/libs/ss-fee-handler.ts | 35 ---- .../solana/networks/bitcoin-testnet.ts | 56 ------ .../src/providers/solana/networks/bitcoin.ts | 51 ------ .../src/providers/solana/networks/dogecoin.ts | 51 ------ .../providers/solana/networks/icons/btc.svg | 12 -- .../providers/solana/networks/icons/doge.svg | 1 - .../providers/solana/networks/icons/ltc.svg | 1 - .../providers/solana/networks/icons/sol.svg | 2 + .../providers/solana/networks/icons/tbtc.svg | 13 -- .../src/providers/solana/networks/index.ts | 10 +- .../src/providers/solana/networks/litecoin.ts | 51 ------ .../src/providers/solana/networks/solana.ts | 24 +++ .../tests/bitcoin.address.derivation.mocha.ts | 16 -- .../src/providers/solana/types/sol-network.ts | 21 +-- packages/extension/src/types/activity.ts | 8 + packages/extension/src/types/base-network.ts | 7 +- packages/extension/src/types/provider.ts | 9 +- packages/keyring/src/index.ts | 1 + packages/keyring/src/utils.ts | 4 +- packages/storage/src/local-forage.ts | 5 - packages/types/src/index.ts | 1 + packages/types/src/networks.ts | 1 + yarn.lock | 97 ++++++++++- 39 files changed, 210 insertions(+), 1033 deletions(-) delete mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/index.ts delete mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts delete mode 100644 packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts delete mode 100644 packages/extension/src/providers/solana/libs/api-ss.ts delete mode 100644 packages/extension/src/providers/solana/libs/bip322-message-sign.ts delete mode 100644 packages/extension/src/providers/solana/libs/btc-fee-handler.ts delete mode 100644 packages/extension/src/providers/solana/libs/filter-ordinals.ts delete mode 100644 packages/extension/src/providers/solana/libs/sign-message-utils.ts delete mode 100644 packages/extension/src/providers/solana/libs/ss-fee-handler.ts delete mode 100644 packages/extension/src/providers/solana/networks/bitcoin-testnet.ts delete mode 100644 packages/extension/src/providers/solana/networks/bitcoin.ts delete mode 100644 packages/extension/src/providers/solana/networks/dogecoin.ts delete mode 100644 packages/extension/src/providers/solana/networks/icons/btc.svg delete mode 100644 packages/extension/src/providers/solana/networks/icons/doge.svg delete mode 100644 packages/extension/src/providers/solana/networks/icons/ltc.svg create mode 100644 packages/extension/src/providers/solana/networks/icons/sol.svg delete mode 100644 packages/extension/src/providers/solana/networks/icons/tbtc.svg delete mode 100644 packages/extension/src/providers/solana/networks/litecoin.ts create mode 100644 packages/extension/src/providers/solana/networks/solana.ts delete mode 100644 packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts diff --git a/packages/extension/package.json b/packages/extension/package.json index b1b6ae323..b72b252e3 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -48,6 +48,7 @@ "bignumber.js": "^9.1.2", "bip39": "^3.1.0", "bitcoinjs-lib": "^6.1.6", + "bs58": "^6.0.0", "chai": "^4.4.1", "concurrently": "^8.2.2", "core-js": "^3.37.1", @@ -92,6 +93,7 @@ "@rollup/plugin-inject": "^5.0.5", "@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.6", "@types/url-parse": "^1.4.11", diff --git a/packages/extension/src/libs/background/index.ts b/packages/extension/src/libs/background/index.ts index 8e67dcb08..668c65cf1 100644 --- a/packages/extension/src/libs/background/index.ts +++ b/packages/extension/src/libs/background/index.ts @@ -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"; @@ -48,6 +47,7 @@ class BackgroundHandler { [ProviderName.polkadot]: {}, [ProviderName.bitcoin]: {}, [ProviderName.kadena]: {}, + [ProviderName.solana]: {}, }; this.#providers = Providers; } diff --git a/packages/extension/src/libs/background/types.ts b/packages/extension/src/libs/background/types.ts index 5e59210d6..49ddf0487 100644 --- a/packages/extension/src/libs/background/types.ts +++ b/packages/extension/src/libs/background/types.ts @@ -2,11 +2,16 @@ 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 { @@ -14,7 +19,8 @@ export interface ProviderType { | typeof EthereumProvider | typeof PolkadotProvider | typeof BitcoinProvider - | typeof KadenaProvider; + | typeof KadenaProvider + | typeof SolanaProvider; } 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 e1b861c07..d5d3df83b 100644 --- a/packages/extension/src/libs/utils/initialize-wallet.ts +++ b/packages/extension/src/libs/utils/initialize-wallet.ts @@ -3,6 +3,7 @@ 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) => { @@ -10,6 +11,7 @@ export const initAccounts = async (keyring: KeyRing) => { 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, @@ -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, diff --git a/packages/extension/src/libs/utils/networks.ts b/packages/extension/src/libs/utils/networks.ts index 021c37e52..ee18b4290 100644 --- a/packages/extension/src/libs/utils/networks.ts +++ b/packages/extension/src/libs/utils/networks.ts @@ -4,6 +4,7 @@ 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"; @@ -11,12 +12,14 @@ 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.ethereum]: EthereumNetworks, [ProviderName.polkadot]: PolkadotNetworks, [ProviderName.bitcoin]: BitcoinNetworks, [ProviderName.kadena]: KadenaNetworks, + [ProviderName.solana]: SolanaNetworks, [ProviderName.enkrypt]: {}, }; const getAllNetworks = async (): Promise => { @@ -30,6 +33,7 @@ const getAllNetworks = async (): Promise => { .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 ( @@ -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, @@ -87,4 +93,6 @@ export { DEFAULT_BTC_NETWORK, DEFAULT_KADENA_NETWORK, DEFAULT_KADENA_NETWORK_NAME, + DEFAULT_SOLANA_NETWORK, + DEFAULT_SOLANA_NETWORK_NAME, }; diff --git a/packages/extension/src/providers/index.ts b/packages/extension/src/providers/index.ts index 738d6fcec..f6f0e5aba 100644 --- a/packages/extension/src/providers/index.ts +++ b/packages/extension/src/providers/index.ts @@ -2,6 +2,7 @@ import EthereumProvider from "@/providers/ethereum"; import PolkadotProvider from "@/providers/polkadot"; import BitcoinProvider from "@/providers/bitcoin"; import KadenaProvider from "@/providers/kadena"; +import SolanaProvider from "@/providers/solana"; import { ProviderName } from "@/types/provider"; export default { @@ -9,4 +10,5 @@ export default { [ProviderName.polkadot]: PolkadotProvider, [ProviderName.bitcoin]: BitcoinProvider, [ProviderName.kadena]: KadenaProvider, + [ProviderName.solana]: SolanaProvider, }; diff --git a/packages/extension/src/providers/solana/index.ts b/packages/extension/src/providers/solana/index.ts index ef2dc0e31..b937a2828 100644 --- a/packages/extension/src/providers/solana/index.ts +++ b/packages/extension/src/providers/solana/index.ts @@ -1,6 +1,5 @@ import { BaseNetwork } from "@/types/base-network"; import getRequestProvider, { RequestClass } from "@enkryptcom/request"; -import Networks from "./networks"; import { MiddlewareFunction, OnMessageResponse } from "@enkryptcom/types"; import Middlewares from "./methods"; import EventEmitter from "eventemitter3"; @@ -12,12 +11,14 @@ import { import GetUIPath from "@/libs/utils/get-ui-path"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import UIRoutes from "./ui/routes/names"; -import { BitcoinNetwork } from "./types/bitcoin-network"; +import { SolanaNetwork } from "./types/sol-network"; +import Networks from "./networks"; + class SolanaProvider extends EventEmitter implements BackgroundProviderInterface { - network: BitcoinNetwork; + network: SolanaNetwork; requestProvider: RequestClass; middlewares: MiddlewareFunction[] = []; namespace: string; @@ -26,7 +27,7 @@ class SolanaProvider toWindow: (message: string) => void; constructor( toWindow: (message: string) => void, - network: BitcoinNetwork = Networks.bitcoin + network: SolanaNetwork = Networks.solana ) { super(); this.network = network; @@ -43,7 +44,7 @@ class SolanaProvider this.middlewares = Middlewares.map((mw) => mw.bind(this)); } setRequestProvider(network: BaseNetwork): void { - this.network = network as BitcoinNetwork; + this.network = network as SolanaNetwork; this.requestProvider.changeNetwork(network.node); } async isPersistentEvent(): Promise { @@ -70,4 +71,4 @@ class SolanaProvider return GetUIPath(page, this.namespace); } } -export default BitcoinProvider; +export default SolanaProvider; diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/index.ts deleted file mode 100644 index dc0e381ea..000000000 --- a/packages/extension/src/providers/solana/libs/activity-handlers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import haskoinHandler from "./providers/haskoin"; -import ssHandler from "./providers/ss"; -export { haskoinHandler, ssHandler }; diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts deleted file mode 100644 index f71f66587..000000000 --- a/packages/extension/src/providers/solana/libs/activity-handlers/providers/haskoin/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -import MarketData from "@/libs/market-data"; -import { HaskoinTxType } from "@/providers/bitcoin/types"; -import { - Activity, - ActivityStatus, - ActivityType, - BTCRawInfo, -} from "@/types/activity"; -import { BaseNetwork } from "@/types/base-network"; -export default async ( - network: BaseNetwork, - pubkey: string -): Promise => { - return fetch( - `${network.node}address/${network.displayAddress(pubkey)}/transactions/full` - ) - .then((res) => res.json()) - .then(async (txs: HaskoinTxType[]) => { - if ((txs as any).error) return []; - let tokenPrice = "0"; - if (network.coingeckoID) { - const marketData = new MarketData(); - await marketData - .getTokenPrice(network.coingeckoID) - .then((mdata) => (tokenPrice = mdata || "0")); - } - - const address = network.displayAddress(pubkey); - return txs.map((tx) => { - const isIncoming = !tx.inputs.find((i) => i.address === address); - - let toAddress = ""; - let value = 0; - - if (isIncoming) { - const relevantOut = tx.outputs.find((tx) => tx.address === address); - if (relevantOut) { - toAddress = relevantOut.address; - value = relevantOut.value; - } - } else { - const relevantOut = tx.outputs.find((tx) => tx.address !== address); - if (relevantOut) { - toAddress = relevantOut.address; - value = relevantOut.value; - } else { - toAddress = tx.outputs[0].address; - value = Number(tx.outputs[0].value); - } - } - - const rawInfo: BTCRawInfo = { - blockNumber: tx.block.height!, - fee: tx.fee, - inputs: tx.inputs.map((input) => ({ - address: input.address, - value: input.value, - })), - outputs: tx.outputs.map((output) => ({ - address: output.address, - value: output.value, - pkscript: output.pkscript, - })), - transactionHash: tx.txid, - timestamp: tx.time * 1000, - }; - const act: Activity = { - from: tx.inputs[0].address, - isIncoming, - network: network.name, - status: !tx.block.mempool - ? ActivityStatus.success - : ActivityStatus.pending, - timestamp: tx.time * 1000, - to: toAddress, - token: { - decimals: network.decimals, - icon: network.icon, - name: network.name_long, - symbol: network.currencyName, - coingeckoID: network.coingeckoID, - price: tokenPrice, - }, - transactionHash: tx.txid, - type: ActivityType.transaction, - value: value.toString(), - rawInfo: rawInfo, - }; - return act; - }); - }) - .catch(() => { - return []; - }); -}; diff --git a/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts b/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts deleted file mode 100644 index d1dfe3a82..000000000 --- a/packages/extension/src/providers/solana/libs/activity-handlers/providers/ss/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import MarketData from "@/libs/market-data"; -import { SSTxType } from "@/providers/bitcoin/types"; -import { - Activity, - ActivityStatus, - ActivityType, - BTCRawInfo, -} from "@/types/activity"; -import { BaseNetwork } from "@/types/base-network"; -export default async ( - network: BaseNetwork, - pubkey: string -): Promise => { - return fetch( - `${network.node}/api/v1/account/${network.displayAddress( - pubkey - )}/txs?pageSize=40` - ) - .then((res) => res.json()) - .then(async (txs: { txs: SSTxType[] }) => { - if ((txs as any).message) return []; - let tokenPrice = "0"; - if (network.coingeckoID) { - const marketData = new MarketData(); - await marketData - .getTokenPrice(network.coingeckoID) - .then((mdata) => (tokenPrice = mdata || "0")); - } - - const address = network.displayAddress(pubkey); - const cleanedTxs = txs.txs.map((tx) => { - return { - ...tx, - vin: tx.vin.filter((vi) => vi.addresses), - vout: tx.vout.filter((vo) => vo.addresses), - }; - }); - return cleanedTxs.map((tx) => { - const isIncoming = !tx.vin.find((i) => i.addresses![0] === address); - let toAddress = ""; - let value = 0; - - if (isIncoming) { - const relevantOut = tx.vout.find( - (tx) => tx.addresses![0] === address - ); - if (relevantOut) { - toAddress = relevantOut.addresses![0]; - value = Number(relevantOut.value); - } - } else { - const relevantOut = tx.vout.find( - (tx) => tx.addresses![0] !== address - ); - if (relevantOut) { - toAddress = relevantOut.addresses![0]; - value = Number(relevantOut.value); - } else { - toAddress = tx.vout[0].addresses![0]; - value = Number(tx.vout[0].value); - } - } - - const rawInfo: BTCRawInfo = { - blockNumber: tx.blockHeight!, - fee: Number(tx.fee), - inputs: tx.vin.map((input) => ({ - address: input.addresses![0], - value: Number(input.value), - })), - outputs: tx.vout.map((output) => ({ - address: output.addresses![0], - value: Number(output.value), - pkscript: output.scriptPubKey.hex, - })), - transactionHash: tx.txid, - timestamp: tx.timestamp * 1000, - }; - const act: Activity = { - from: tx.vin[0].addresses![0], - isIncoming, - network: network.name, - status: - tx.blockHeight > 0 - ? ActivityStatus.success - : ActivityStatus.pending, - timestamp: tx.timestamp * 1000, - to: toAddress, - token: { - decimals: network.decimals, - icon: network.icon, - name: network.name_long, - symbol: network.currencyName, - coingeckoID: network.coingeckoID, - price: tokenPrice, - }, - transactionHash: tx.txid, - type: ActivityType.transaction, - value: value.toString(), - rawInfo: rawInfo, - }; - return act; - }); - }) - .catch(() => { - return []; - }); -}; diff --git a/packages/extension/src/providers/solana/libs/api-ss.ts b/packages/extension/src/providers/solana/libs/api-ss.ts deleted file mode 100644 index 88b3e5426..000000000 --- a/packages/extension/src/providers/solana/libs/api-ss.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { BTCRawInfo } from "@/types/activity"; -import { ProviderAPIInterface } from "@/types/provider"; -import { - BitcoinNetworkInfo, - HaskoinUnspentType, - SSTxType, - SSUnspentType, -} from "../types"; -import { toBN } from "web3-utils"; -import cacheFetch from "@/libs/cache-fetch"; -import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; -import { filterOutOrdinals } from "./filter-ordinals"; - -class API implements ProviderAPIInterface { - node: string; - networkInfo: BitcoinNetworkInfo; - - constructor(node: string, networkInfo: BitcoinNetworkInfo) { - this.node = node; - this.networkInfo = networkInfo; - } - - public get api() { - return this; - } - private getAddress(pubkey: string) { - return getBitcoinAddress(pubkey, this.networkInfo); - } - // eslint-disable-next-line @typescript-eslint/no-empty-function - async init(): Promise {} - async getTransactionStatus(hash: string): Promise { - return fetch(`${this.node}/api/v1/tx/${hash}`) - .then((res) => res.json()) - .then((tx: SSTxType) => { - if ((tx as any).message) return null; - if (tx.blockHeight < 0) return null; - const rawInfo: BTCRawInfo = { - blockNumber: tx.blockHeight, - fee: Number(tx.fee), - inputs: tx.vin - .filter((t) => t.addresses && t.addresses.length) - .map((input) => ({ - address: input.addresses![0], - value: Number(input.value), - })), - outputs: tx.vout - .filter((t) => t.addresses && t.addresses.length) - .map((output) => ({ - address: output.addresses![0], - value: Number(output.value), - pkscript: output.scriptPubKey.hex, - })), - transactionHash: tx.txid, - timestamp: tx.timestamp * 1000, - }; - return rawInfo; - }); - } - async getBalance(pubkey: string): Promise { - const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); - return fetch(`${this.node}/api/v1/account/${address}`) - .then((res) => res.json()) - .then((balance: { balance: string; unconfirmedBalance: string }) => { - if ((balance as any).message) return "0"; - return toBN(balance.balance) - .add(toBN(balance.unconfirmedBalance)) - .toString(); - }) - .catch(() => "0"); - } - async broadcastTx(rawtx: string): Promise { - return fetch(`${this.node}/api/v1/send`, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ hex: rawtx }), - }) - .then((res) => res.json()) - .then((response) => { - if (response.error) { - return Promise.reject(response.message); - } - return true; - }); - } - async SSToHaskoinUTXOs( - SSUTXOs: SSUnspentType[], - address: string - ): Promise { - const ret: HaskoinUnspentType[] = []; - for (const utx of SSUTXOs) { - const res = (await cacheFetch({ - url: `${this.node}/api/v1/tx/${utx.txid}`, - })) as SSTxType; - ret.push({ - address, - block: { - height: utx.height, - position: 0, - }, - index: utx.vout, - pkscript: res.vout[utx.vout].scriptPubKey.hex, - txid: utx.txid, - value: Number(utx.value), - raw: res.hex, - }); - } - ret.sort((a, b) => { - return a.value - b.value; - }); - return ret; - } - - async getUTXOs(pubkey: string): Promise { - const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); - return fetch(`${this.node}/api/v1/account/${address}/utxos`) - .then((res) => res.json()) - .then(async (utxos: SSUnspentType[]) => { - if ((utxos as any).message || !utxos.length) return []; - return filterOutOrdinals( - address, - this.networkInfo.name, - await this.SSToHaskoinUTXOs(utxos, address) - ).then((futxos) => { - futxos.sort((a, b) => { - return a.value - b.value; - }); - return futxos; - }); - }); - } -} -export default API; diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts index 80dc94e41..5abc5900d 100644 --- a/packages/extension/src/providers/solana/libs/api.ts +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -1,103 +1,33 @@ -import { BTCRawInfo } from "@/types/activity"; +import { SOLRawInfo } from "@/types/activity"; import { ProviderAPIInterface } from "@/types/provider"; -import { - BitcoinNetworkInfo, - HaskoinBalanceType, - HaskoinTxType, - HaskoinUnspentType, -} from "../types"; -import { toBN } from "web3-utils"; -import { getAddress as getBitcoinAddress } from "../types/bitcoin-network"; -import { filterOutOrdinals } from "./filter-ordinals"; +import { getAddress as getSolAddress } from "../types/sol-network"; class API implements ProviderAPIInterface { node: string; - networkInfo: BitcoinNetworkInfo; - constructor(node: string, networkInfo: BitcoinNetworkInfo) { + constructor(node: string) { this.node = node; - this.networkInfo = networkInfo; } public get api() { return this; } private getAddress(pubkey: string) { - return getBitcoinAddress(pubkey, this.networkInfo); + return getSolAddress(pubkey); } // eslint-disable-next-line @typescript-eslint/no-empty-function async init(): Promise {} - async getTransactionStatus(hash: string): Promise { - return fetch(`${this.node}transaction/${hash}`) - .then((res) => res.json()) - .then((tx: HaskoinTxType) => { - if ((tx as any).error) return null; - if (tx.block.mempool) return null; - const rawInfo: BTCRawInfo = { - blockNumber: tx.block.height!, - fee: tx.fee, - inputs: tx.inputs.map((input) => ({ - address: input.address, - value: input.value, - })), - outputs: tx.outputs.map((output) => ({ - address: output.address, - value: output.value, - pkscript: output.pkscript, - })), - transactionHash: tx.txid, - timestamp: tx.time * 1000, - }; - return rawInfo; - }); + async getTransactionStatus(hash: string): Promise { + console.log(hash, "gettxstatus"); + return null; } async getBalance(pubkey: string): Promise { - const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); - return fetch(`${this.node}address/${address}/balance`) - .then((res) => res.json()) - .then((balance: HaskoinBalanceType) => { - if ((balance as any).error) return "0"; - return toBN(balance.confirmed).addn(balance.unconfirmed).toString(); - }) - .catch(() => "0"); + console.log(pubkey, "getbalance"); + return "0"; } async broadcastTx(rawtx: string): Promise { - return fetch(`${this.node}transactions`, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "text/plain", - }, - body: rawtx, - }) - .then((res) => res.json()) - .then((response) => { - if (response.error) { - if (response.error === "server-error") return true; // haskoin api return error when it timesout or something - return Promise.reject(response.message); - } - return true; - }); - } - async getUTXOs(pubkey: string): Promise { - const address = pubkey.length < 64 ? pubkey : this.getAddress(pubkey); - return fetch(`${this.node}address/${address}/unspent`) - .then((res) => res.json()) - .then((utxos: HaskoinUnspentType[]) => { - if ((utxos as any).error) return []; - return filterOutOrdinals(address, this.networkInfo.name, utxos).then( - (futxos) => { - futxos.sort((a, b) => { - return a.value - b.value; - }); - return futxos; - } - ); - }) - .catch((e) => { - console.error(e); - return []; - }); + console.log(rawtx, "broadcasttx"); + return true; } } export default API; diff --git a/packages/extension/src/providers/solana/libs/bip322-message-sign.ts b/packages/extension/src/providers/solana/libs/bip322-message-sign.ts deleted file mode 100644 index 60f62d723..000000000 --- a/packages/extension/src/providers/solana/libs/bip322-message-sign.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * reference: https://github.com/unisat-wallet/wallet-sdk/blob/master/src/message/bip322-simple.ts - * reference: https://github.com/bitcoinjs/varuint-bitcoin/blob/master/index.js - */ - -import { BitcoinNetwork, PaymentType } from "../types/bitcoin-network"; -import { address as BTCAddress, Transaction, Psbt } from "bitcoinjs-lib"; -import { sha256 } from "ethereum-cryptography/sha256"; -import { PSBTSigner } from "../ui/libs/signer"; -import { bufferToHex } from "@enkryptcom/utils"; - -const bip0322_hash = (message: string) => { - const tag = "BIP0322-signed-message"; - const tagHash = sha256(Buffer.from(tag)); - const result = sha256( - Buffer.concat([tagHash, tagHash, Buffer.from(message)]) - ); - return bufferToHex(result, true); -}; - -const MAX_SAFE_INTEGER = 9007199254740991; - -const checkUInt53 = (n: number) => { - if (n < 0 || n > MAX_SAFE_INTEGER || n % 1 !== 0) - throw new RangeError("value out of range"); -}; - -const encodingLength = (number: number) => { - checkUInt53(number); - - return number < 0xfd - ? 1 - : number <= 0xffff - ? 3 - : number <= 0xffffffff - ? 5 - : 9; -}; -export const encode = (number: number, buffer?: Buffer, offset?: number) => { - checkUInt53(number); - - if (!buffer) buffer = Buffer.allocUnsafe(encodingLength(number)); - if (!Buffer.isBuffer(buffer)) - throw new TypeError("buffer must be a Buffer instance"); - if (!offset) offset = 0; - - // 8 bit - if (number < 0xfd) { - buffer.writeUInt8(number, offset); - - // 16 bit - } else if (number <= 0xffff) { - buffer.writeUInt8(0xfd, offset); - buffer.writeUInt16LE(number, offset + 1); - - // 32 bit - } else if (number <= 0xffffffff) { - buffer.writeUInt8(0xfe, offset); - buffer.writeUInt32LE(number, offset + 1); - - // 64 bit - } else { - buffer.writeUInt8(0xff, offset); - buffer.writeUInt32LE(number >>> 0, offset + 1); - buffer.writeUInt32LE((number / 0x100000000) | 0, offset + 5); - } - - return buffer; -}; - -export const decode = (buffer: Buffer, offset: number) => { - if (!Buffer.isBuffer(buffer)) - throw new TypeError("buffer must be a Buffer instance"); - if (!offset) offset = 0; - const first = buffer.readUInt8(offset); - // 8 bit - if (first < 0xfd) { - return first; - // 16 bit - } else if (first === 0xfd) { - return buffer.readUInt16LE(offset + 1); - // 32 bit - } else if (first === 0xfe) { - return buffer.readUInt32LE(offset + 1); - // 64 bit - } else { - const lo = buffer.readUInt32LE(offset + 1); - const hi = buffer.readUInt32LE(offset + 5); - const number = hi * 0x0100000000 + lo; - checkUInt53(number); - return number; - } -}; - -export async function signMessageOfBIP322Simple({ - message, - address, - network, - Signer, -}: { - message: string; - address: string; - network: BitcoinNetwork; - Signer: ReturnType; -}) { - const outputScript = BTCAddress.toOutputScript( - network.displayAddress(address), - network.networkInfo - ); - const addressType = network.networkInfo.paymentType; - const supportedTypes = [PaymentType.P2WPKH]; - if (supportedTypes.includes(addressType) == false) { - throw new Error("Not support address type to sign"); - } - - const prevoutHash = Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ); - const prevoutIndex = 0xffffffff; - const sequence = 0; - const scriptSig = Buffer.concat([ - Buffer.from("0020", "hex"), - Buffer.from(bip0322_hash(message), "hex"), - ]); - - const txToSpend = new Transaction(); - txToSpend.version = 0; - txToSpend.addInput(prevoutHash, prevoutIndex, sequence, scriptSig); - txToSpend.addOutput(outputScript, 0); - - const psbtToSign = new Psbt(); - psbtToSign.setVersion(0); - psbtToSign.addInput({ - hash: txToSpend.getHash(), - index: 0, - sequence: 0, - witnessUtxo: { - script: outputScript, - value: 0, - }, - }); - psbtToSign.addOutput({ script: Buffer.from("6a", "hex"), value: 0 }); - - await psbtToSign.signAllInputsAsync(Signer); - psbtToSign.finalizeAllInputs(); - const txToSign = psbtToSign.extractTransaction(); - - const encodeVarString = (b: Buffer) => { - return Buffer.concat([encode(b.byteLength), b]); - }; - - const len = encode(txToSign.ins[0].witness.length); - const result = Buffer.concat([ - len, - ...txToSign.ins[0].witness.map((w) => encodeVarString(w)), - ]); - const signature = result.toString("base64"); - - return signature; -} diff --git a/packages/extension/src/providers/solana/libs/btc-fee-handler.ts b/packages/extension/src/providers/solana/libs/btc-fee-handler.ts deleted file mode 100644 index 07d8de534..000000000 --- a/packages/extension/src/providers/solana/libs/btc-fee-handler.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GasPriceTypes } from "@/providers/common/types"; - -const BTCFeeHandler = async (): Promise> => { - return fetch(`https://bitcoiner.live/api/fees/estimates/latest`) - .then((res) => res.json()) - .then((json) => { - return { - [GasPriceTypes.FASTEST]: Math.ceil(json.estimates["30"].sat_per_vbyte), - [GasPriceTypes.FAST]: Math.ceil(json.estimates["60"].sat_per_vbyte), - [GasPriceTypes.REGULAR]: Math.ceil(json.estimates["120"].sat_per_vbyte), - [GasPriceTypes.ECONOMY]: Math.ceil(json.estimates["180"].sat_per_vbyte), - }; - }) - .catch(() => ({ - [GasPriceTypes.FASTEST]: 25, - [GasPriceTypes.FAST]: 20, - [GasPriceTypes.REGULAR]: 10, - [GasPriceTypes.ECONOMY]: 5, - })); -}; - -export default BTCFeeHandler; diff --git a/packages/extension/src/providers/solana/libs/filter-ordinals.ts b/packages/extension/src/providers/solana/libs/filter-ordinals.ts deleted file mode 100644 index 673cae62a..000000000 --- a/packages/extension/src/providers/solana/libs/filter-ordinals.ts +++ /dev/null @@ -1,55 +0,0 @@ -import cacheFetch from "@/libs/cache-fetch"; -import { HaskoinUnspentType } from "../types"; -import { NetworkNames } from "@enkryptcom/types"; - -const OrdinalsEndpoint = "https://partners.mewapi.io/ordinals/"; -const CACHE_TTL = 60 * 1000; -const MAX_ITEMS = 100; - -const supportedNetworks = [NetworkNames.Bitcoin]; - -interface OridnalType { - inscriptionId: string; - output: string; -} -export const getAllOrdinals = ( - address: string, - networkName: string, - currentItems: OridnalType[] -): Promise => { - const query = `${OrdinalsEndpoint}${networkName.toLowerCase()}/ordinals/inscriptions?address=${address}&cursor=${ - currentItems.length - }&size=${MAX_ITEMS}`; - return cacheFetch( - { - url: query, - }, - CACHE_TTL - ).then((json) => { - if (json.code !== 0) - throw Promise.reject("Unknown error, cant retrieve ordinals"); - const items: OridnalType[] = json.data.list as OridnalType[]; - currentItems = currentItems.concat(items); - if (json.data.total === currentItems.length) return currentItems; - return getAllOrdinals(address, networkName, currentItems); - }); -}; - -export const filterOutOrdinals = ( - address: string, - networkName: string, - utxos: HaskoinUnspentType[] -): Promise => { - if (!supportedNetworks.includes(networkName as NetworkNames)) - return Promise.resolve(utxos); - return getAllOrdinals(address, networkName, []).then((ordinals) => { - return utxos.filter((utxo) => { - for (const ord of ordinals) { - const [txid, idx] = ord.output.split(":"); - if (utxo.txid === txid && utxo.index === parseInt(idx)) return false; - if (utxo.value <= 1000) return false; // most likely ordinal, safety precaution - } - return true; - }); - }); -}; diff --git a/packages/extension/src/providers/solana/libs/sign-message-utils.ts b/packages/extension/src/providers/solana/libs/sign-message-utils.ts deleted file mode 100644 index 465a40a39..000000000 --- a/packages/extension/src/providers/solana/libs/sign-message-utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { sha256 } from "ethereum-cryptography/sha256"; - -const MAGIC_BYTES = Buffer.from("Bitcoin Signed Message:\n"); - -const varintBufNum = (n: number) => { - let buf; - if (n < 253) { - buf = Buffer.alloc(1); - buf.writeUInt8(n, 0); - } else if (n < 0x10000) { - buf = Buffer.alloc(1 + 2); - buf.writeUInt8(253, 0); - buf.writeUInt16LE(n, 1); - } else if (n < 0x100000000) { - buf = Buffer.alloc(1 + 4); - buf.writeUInt8(254, 0); - buf.writeUInt32LE(n, 1); - } else { - buf = Buffer.alloc(1 + 8); - buf.writeUInt8(255, 0); - buf.writeInt32LE(n & -1, 1); - buf.writeUInt32LE(Math.floor(n / 0x100000000), 5); - } - return buf; -}; - -export const magicHash = (messageBuffer: Buffer) => { - const prefix1 = varintBufNum(MAGIC_BYTES.length); - const prefix2 = varintBufNum(messageBuffer.length); - const buf = Buffer.concat([prefix1, MAGIC_BYTES, prefix2, messageBuffer]); - return Buffer.from(sha256(sha256(buf))); -}; - -export const toCompact = ( - i: number, - signature: Uint8Array, - compressed: boolean -) => { - if (!(i === 0 || i === 1 || i === 2 || i === 3)) { - throw new Error("i must be equal to 0, 1, 2, or 3"); - } - let val = i + 27 + 4; - if (!compressed) { - val = val - 4; - } - return Buffer.concat([Uint8Array.of(val), Uint8Array.from(signature)]); -}; diff --git a/packages/extension/src/providers/solana/libs/ss-fee-handler.ts b/packages/extension/src/providers/solana/libs/ss-fee-handler.ts deleted file mode 100644 index 81151b173..000000000 --- a/packages/extension/src/providers/solana/libs/ss-fee-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { GasPriceTypes } from "@/providers/common/types"; - -interface FeeType { - fast: { - satsPerKiloByte: number; - }; - average: { - satsPerKiloByte: number; - }; - slow: { - satsPerKiloByte: number; - }; -} -const SSFeeHandler = async ( - url: string -): Promise> => { - return fetch(url) - .then((res) => res.json()) - .then((json: FeeType) => { - if (json.fast.satsPerKiloByte < 0) - json.fast.satsPerKiloByte = json.average.satsPerKiloByte; - if (json.average.satsPerKiloByte < 0) - json.average.satsPerKiloByte = json.slow.satsPerKiloByte; - return { - [GasPriceTypes.FASTEST]: - Math.ceil(json.fast.satsPerKiloByte / 1024) + 5, - [GasPriceTypes.FAST]: Math.ceil(json.fast.satsPerKiloByte / 1024) + 3, - [GasPriceTypes.REGULAR]: - Math.ceil(json.average.satsPerKiloByte / 1024) + 2, - [GasPriceTypes.ECONOMY]: Math.ceil(json.slow.satsPerKiloByte / 1024), - }; - }); -}; - -export default SSFeeHandler; diff --git a/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts b/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts deleted file mode 100644 index 9ec11d360..000000000 --- a/packages/extension/src/providers/solana/networks/bitcoin-testnet.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { NetworkNames } from "@enkryptcom/types"; -import { - BitcoinNetwork, - BitcoinNetworkOptions, - PaymentType, -} from "../types/bitcoin-network"; -import { haskoinHandler } from "../libs/activity-handlers"; -import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; -import { GasPriceTypes } from "@/providers/common/types"; -import HaskoinAPI from "../libs/api"; - -const bitcoinOptions: BitcoinNetworkOptions = { - name: NetworkNames.BitcoinTest, - name_long: "Bitcoin Testnet", - homePage: "https://bitcoin.org/en/", - blockExplorerTX: "https://www.blockchain.com/btc-testnet/tx/[[txHash]]", - blockExplorerAddr: - "https://www.blockchain.com/btc-testnet/address/[[address]]", - isTestNetwork: true, - currencyName: "tBTC", - currencyNameLong: "Test Bitcoin", - icon: require("./icons/tbtc.svg"), - decimals: 8, - dust: 0.00000546, - node: "https://partners.mewapi.io/nodes/hk/btct/", - activityHandler: wrapActivityHandler(haskoinHandler), - basePath: "m/49'/1'/0'/0", - coingeckoID: "bitcoin", - apiType: HaskoinAPI, - feeHandler: () => - Promise.resolve({ - [GasPriceTypes.FASTEST]: 25, - [GasPriceTypes.FAST]: 20, - [GasPriceTypes.REGULAR]: 10, - [GasPriceTypes.ECONOMY]: 5, - }), - networkInfo: { - name: NetworkNames.BitcoinTest, - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "tb", - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, - dustThreshold: null, - paymentType: PaymentType.P2WPKH, - maxFeeRate: 5000 * 2, - }, -}; - -const bitcoin = new BitcoinNetwork(bitcoinOptions); - -export default bitcoin; diff --git a/packages/extension/src/providers/solana/networks/bitcoin.ts b/packages/extension/src/providers/solana/networks/bitcoin.ts deleted file mode 100644 index ccca0156e..000000000 --- a/packages/extension/src/providers/solana/networks/bitcoin.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NetworkNames } from "@enkryptcom/types"; -import { - BitcoinNetwork, - BitcoinNetworkOptions, - PaymentType, -} from "../types/bitcoin-network"; -import { haskoinHandler } from "../libs/activity-handlers"; -import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; -import BTCFeeHandler from "../libs/btc-fee-handler"; -import HaskoinAPI from "../libs/api"; -import shNFTHandler from "@/libs/nft-handlers/simplehash-ordinals"; - -const bitcoinOptions: BitcoinNetworkOptions = { - name: NetworkNames.Bitcoin, - name_long: "Bitcoin", - homePage: "https://bitcoin.org/en/", - blockExplorerTX: "https://mempool.space/tx/[[txHash]]", - blockExplorerAddr: "https://mempool.space/address/[[address]]", - isTestNetwork: false, - currencyName: "BTC", - currencyNameLong: "Bitcoin", - icon: require("./icons/btc.svg"), - decimals: 8, - node: "https://partners.mewapi.io/nodes/hk/btc/", - coingeckoID: "bitcoin", - activityHandler: wrapActivityHandler(haskoinHandler), - basePath: "m/49'/0'/0'/0", - feeHandler: BTCFeeHandler, - apiType: HaskoinAPI, - dust: 0.00000546, - NFTHandler: shNFTHandler, - networkInfo: { - name: NetworkNames.Bitcoin, - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "bc", - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, - dustThreshold: null, - paymentType: PaymentType.P2WPKH, - maxFeeRate: 5000, - }, -}; - -const bitcoin = new BitcoinNetwork(bitcoinOptions); - -export default bitcoin; diff --git a/packages/extension/src/providers/solana/networks/dogecoin.ts b/packages/extension/src/providers/solana/networks/dogecoin.ts deleted file mode 100644 index 35937b318..000000000 --- a/packages/extension/src/providers/solana/networks/dogecoin.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NetworkNames } from "@enkryptcom/types"; -import { - BitcoinNetwork, - BitcoinNetworkOptions, - PaymentType, -} from "../types/bitcoin-network"; -import { ssHandler } from "../libs/activity-handlers"; -import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; -import SSFeeHandler from "../libs/ss-fee-handler"; -import SSApi from "../libs/api-ss"; - -const dogeOptions: BitcoinNetworkOptions = { - name: NetworkNames.Dogecoin, - name_long: "Dogecoin", - homePage: "https://dogecoin.com/", - blockExplorerTX: "https://dogechain.info/tx/[[txHash]]", - blockExplorerAddr: "https://dogechain.info/address/[[address]]", - isTestNetwork: false, - currencyName: "Doge", - currencyNameLong: "Dogecoin", - icon: require("./icons/doge.svg"), - decimals: 8, - node: "https://partners.mewapi.io/nodes/ss/doge", - coingeckoID: "dogecoin", - apiType: SSApi, - dust: 0.01, - activityHandler: wrapActivityHandler(ssHandler), - basePath: "m/44'/3'/0'/0", - feeHandler: () => { - return SSFeeHandler("https://partners.mewapi.io/nodes/ss/doge/api/v1/fees"); - }, - networkInfo: { - name: NetworkNames.Dogecoin, - messagePrefix: "\x19Dogecoin Signed Message:\n", - bech32: "dc", - bip32: { - public: 0x02facafd, - private: 0x02fac398, - }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e, - dustThreshold: null, - paymentType: PaymentType.P2PKH, - maxFeeRate: 100000 * 10, - }, -}; - -const dogecoin = new BitcoinNetwork(dogeOptions); - -export default dogecoin; diff --git a/packages/extension/src/providers/solana/networks/icons/btc.svg b/packages/extension/src/providers/solana/networks/icons/btc.svg deleted file mode 100644 index f5889766e..000000000 --- a/packages/extension/src/providers/solana/networks/icons/btc.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/packages/extension/src/providers/solana/networks/icons/doge.svg b/packages/extension/src/providers/solana/networks/icons/doge.svg deleted file mode 100644 index c435731dc..000000000 --- a/packages/extension/src/providers/solana/networks/icons/doge.svg +++ /dev/null @@ -1 +0,0 @@ -Dogecoin (DOGE) \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/icons/ltc.svg b/packages/extension/src/providers/solana/networks/icons/ltc.svg deleted file mode 100644 index 13e76a40e..000000000 --- a/packages/extension/src/providers/solana/networks/icons/ltc.svg +++ /dev/null @@ -1 +0,0 @@ -litecoin-ltc-logo \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/icons/sol.svg b/packages/extension/src/providers/solana/networks/icons/sol.svg new file mode 100644 index 000000000..7e8c45276 --- /dev/null +++ b/packages/extension/src/providers/solana/networks/icons/sol.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/icons/tbtc.svg b/packages/extension/src/providers/solana/networks/icons/tbtc.svg deleted file mode 100644 index 9323b5d8a..000000000 --- a/packages/extension/src/providers/solana/networks/icons/tbtc.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/extension/src/providers/solana/networks/index.ts b/packages/extension/src/providers/solana/networks/index.ts index 2cf52f449..a78731fca 100644 --- a/packages/extension/src/providers/solana/networks/index.ts +++ b/packages/extension/src/providers/solana/networks/index.ts @@ -1,11 +1,5 @@ -import btcNode from "./bitcoin"; -import btcTestNode from "./bitcoin-testnet"; -import ltcNode from "./litecoin"; -import dogeNode from "./dogecoin"; +import solanaNode from "./solana"; export default { - bitcoin: btcNode, - bitcoinTest: btcTestNode, - litecoin: ltcNode, - dogecoin: dogeNode, + solana: solanaNode, }; diff --git a/packages/extension/src/providers/solana/networks/litecoin.ts b/packages/extension/src/providers/solana/networks/litecoin.ts deleted file mode 100644 index a8dedbd5c..000000000 --- a/packages/extension/src/providers/solana/networks/litecoin.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NetworkNames } from "@enkryptcom/types"; -import { - BitcoinNetwork, - BitcoinNetworkOptions, - PaymentType, -} from "../types/bitcoin-network"; -import { ssHandler } from "../libs/activity-handlers"; -import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; -import SSFeeHandler from "../libs/ss-fee-handler"; -import SSApi from "../libs/api-ss"; - -const litecoinOptions: BitcoinNetworkOptions = { - name: NetworkNames.Litecoin, - name_long: "Litecoin", - homePage: "https://litecoin.org/", - blockExplorerTX: "https://explorer.btc.com/ltc/transaction/[[txHash]]", - blockExplorerAddr: "https://explorer.btc.com/ltc/address/[[address]]", - isTestNetwork: false, - currencyName: "LTC", - currencyNameLong: "Litecoin", - icon: require("./icons/ltc.svg"), - decimals: 8, - node: "https://partners.mewapi.io/nodes/ss/ltc", - coingeckoID: "litecoin", - dust: 0.0001, - apiType: SSApi, - activityHandler: wrapActivityHandler(ssHandler), - basePath: "m/49'/2'/0'/0", - feeHandler: () => { - return SSFeeHandler("https://partners.mewapi.io/nodes/ss/ltc/api/v1/fees"); - }, - networkInfo: { - name: NetworkNames.Litecoin, - messagePrefix: "\x19Litecoin Signed Message:\n", - bech32: "ltc", - bip32: { - public: 0x019da462, - private: 0x019d9cfe, - }, - pubKeyHash: 0x30, - scriptHash: 0x32, - wif: 0xb0, - dustThreshold: null, - paymentType: PaymentType.P2WPKH, - maxFeeRate: 5000 * 2, - }, -}; - -const litecoin = new BitcoinNetwork(litecoinOptions); - -export default litecoin; diff --git a/packages/extension/src/providers/solana/networks/solana.ts b/packages/extension/src/providers/solana/networks/solana.ts new file mode 100644 index 000000000..e5a2cc12b --- /dev/null +++ b/packages/extension/src/providers/solana/networks/solana.ts @@ -0,0 +1,24 @@ +import { NetworkNames } from "@enkryptcom/types"; +import { SolanaNetwork, SolanaNetworkOptions } from "../types/sol-network"; +import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; + +const solanaOptions: SolanaNetworkOptions = { + name: NetworkNames.Solana, + name_long: "Solana", + homePage: "https://solana.com/", + blockExplorerTX: "https://solscan.io/tx/[[txHash]]", + blockExplorerAddr: "https://solscan.io/account/[[address]]", + isTestNetwork: false, + currencyName: "SOL", + currencyNameLong: "Solana", + icon: require("./icons/sol.svg"), + decimals: 8, + node: "https://node.mewapi.io/ws/sol", + coingeckoID: "solana", + activityHandler: wrapActivityHandler(() => Promise.resolve([])), + basePath: "m/44'/501'", +}; + +const bitcoin = new SolanaNetwork(solanaOptions); + +export default bitcoin; diff --git a/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts b/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts deleted file mode 100644 index 82b3e9506..000000000 --- a/packages/extension/src/providers/solana/tests/bitcoin.address.derivation.mocha.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect } from "chai"; -import bitcoinNetworks from "../networks"; -const pubkey = - "0x021aa21d5f77b1be591d0a0a847cb7412a344f4e768b93d55b3eeab3b7e8a4a252"; -describe("Should derive proper bitcoin addresses", () => { - it("should derive segwit address", async () => { - const bitcoinMain = bitcoinNetworks.bitcoin; - expect(bitcoinMain.displayAddress(pubkey)).to.be.eq( - "bc1qnjmf6vcjpyru5t8y2936260mrqa305qactwds2" - ); - const bitcoinTest = bitcoinNetworks.bitcoinTest; - expect(bitcoinTest.displayAddress(pubkey)).to.be.eq( - "tb1qnjmf6vcjpyru5t8y2936260mrqa305qajd47te" - ); - }); -}); diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index eeb1e37c9..25366a41c 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -1,5 +1,5 @@ import { BaseNetwork, BaseNetworkOptions } from "@/types/base-network"; -import BitcoinAPI from "@/providers/bitcoin/libs/api"; +import SolAPI from "@/providers/solana/libs/api"; import { AssetsType } from "@/types/provider"; import { BaseToken, BaseTokenOptions } from "@/types/base-token"; import { ProviderName } from "@/types/provider"; @@ -16,7 +16,8 @@ import { CoinGeckoTokenMarket } from "@/libs/market-data/types"; import Sparkline from "@/libs/sparkline"; import { SOLToken } from "./sol-token"; import { NFTCollection } from "@/types/nft"; -import { fromBase } from "@enkryptcom/utils"; +import { fromBase, hexToBuffer } from "@enkryptcom/utils"; +import bs58 from "bs58"; export interface SolanaNetworkOptions { name: NetworkNames; @@ -32,7 +33,6 @@ export interface SolanaNetworkOptions { node: string; coingeckoID?: string; basePath: string; - dust: number; NFTHandler?: ( network: BaseNetwork, address: string @@ -44,9 +44,10 @@ export interface SolanaNetworkOptions { } export const getAddress = (pubkey: string) => { - return pubkey as string; + return bs58.encode(hexToBuffer(pubkey)); }; -export class BitcoinNetwork extends BaseNetwork { + +export class SolanaNetwork extends BaseNetwork { public assets: BaseToken[] = []; private activityHandler: ( network: BaseNetwork, @@ -58,16 +59,16 @@ export class BitcoinNetwork extends BaseNetwork { ) => Promise; constructor(options: SolanaNetworkOptions) { const api = async () => { - const api = new Api(options.node); + const api = new SolAPI(options.node); await api.init(); - return api as BitcoinAPI; + return api as SolAPI; }; const baseOptions: BaseNetworkOptions = { identicon: createIcon, - signer: [SignerType.secp256k1btc], - provider: ProviderName.bitcoin, - displayAddress: (pubkey: string) => getAddress(pubkey), + signer: [SignerType.ed25519sol], + provider: ProviderName.solana, + displayAddress: getAddress, api, ...options, }; diff --git a/packages/extension/src/types/activity.ts b/packages/extension/src/types/activity.ts index 804fdff8d..cb0a716f0 100644 --- a/packages/extension/src/types/activity.ts +++ b/packages/extension/src/types/activity.ts @@ -16,6 +16,13 @@ interface BTCOuts extends BTCIns { pkscript: string; } +interface SOLRawInfo { + blockNumber: number; + transactionHash: string; + timestamp: number | undefined; + fee: number; +} + interface BTCRawInfo { blockNumber: number; transactionHash: string; @@ -133,4 +140,5 @@ export { SwapRawInfo, KadenaRawInfo, KadenaDBInfo, + SOLRawInfo, }; diff --git a/packages/extension/src/types/base-network.ts b/packages/extension/src/types/base-network.ts index a1d54bad8..1b1197bdd 100644 --- a/packages/extension/src/types/base-network.ts +++ b/packages/extension/src/types/base-network.ts @@ -2,6 +2,7 @@ import EvmAPI from "@/providers/ethereum/libs/api"; import SubstrateAPI from "@/providers/polkadot/libs/api"; import BitcoinAPI from "@/providers/bitcoin/libs/api"; import KadenaAPI from "@/providers/kadena/libs/api"; +import SolanaAPI from "@/providers/solana/libs/api"; import { AssetsType, ProviderName } from "@/types/provider"; import { CoingeckoPlatform, SignerType, NetworkNames } from "@enkryptcom/types"; import { Activity } from "./activity"; @@ -36,7 +37,8 @@ export interface BaseNetworkOptions { | Promise | Promise | Promise - | Promise; + | Promise + | Promise; customTokens?: boolean; } @@ -64,7 +66,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 bfc4905cd..97ac31b34 100644 --- a/packages/extension/src/types/provider.ts +++ b/packages/extension/src/types/provider.ts @@ -21,6 +21,7 @@ import { EthereumRawInfo, SubscanExtrinsicInfo, KadenaRawInfo, + SOLRawInfo, } from "./activity"; export enum ProviderName { @@ -29,6 +30,7 @@ export enum ProviderName { bitcoin = "bitcoin", polkadot = "polkadot", kadena = "kadena", + solana = "solana", } export enum InternalStorageNamespace { keyring = "KeyRing", @@ -123,7 +125,12 @@ export abstract class ProviderAPIInterface { abstract getTransactionStatus( hash: string ): Promise< - EthereumRawInfo | SubscanExtrinsicInfo | BTCRawInfo | KadenaRawInfo | null + | EthereumRawInfo + | SubscanExtrinsicInfo + | BTCRawInfo + | KadenaRawInfo + | SOLRawInfo + | null >; } diff --git a/packages/keyring/src/index.ts b/packages/keyring/src/index.ts index e4d0e82c1..27748e549 100644 --- a/packages/keyring/src/index.ts +++ b/packages/keyring/src/index.ts @@ -49,6 +49,7 @@ class KeyRing { [SignerType.sr25519]: new PolkadotSigner(SignerType.sr25519), [SignerType.secp256k1btc]: new BitcoinSigner(), [SignerType.ed25519kda]: new KadenaSigner(), + [SignerType.ed25519sol]: new KadenaSigner(), }; } diff --git a/packages/keyring/src/utils.ts b/packages/keyring/src/utils.ts index 3e12f2143..43a584417 100644 --- a/packages/keyring/src/utils.ts +++ b/packages/keyring/src/utils.ts @@ -11,6 +11,8 @@ export const pathParser = ( ) { return index === 0 ? "" : `//${--index}`; // polkadotjs extension use "" for 0 index/root } - + if (type === SignerType.ed25519sol) { + return `${basePath}/${index}'/0`; // Solana uses hardened paths + } return `${basePath}/${index}`; }; diff --git a/packages/storage/src/local-forage.ts b/packages/storage/src/local-forage.ts index e2f1774e9..c8ab2b4f7 100644 --- a/packages/storage/src/local-forage.ts +++ b/packages/storage/src/local-forage.ts @@ -54,11 +54,6 @@ class LocalForage implements BrowserStorageArea { }) .then(() => storeOb); } - - // setDriver(driver: LocalForageDriver) { - // this.storage.defineDriver(driver); - // this.storage.setDriver(driver._driver); - // } } export default LocalForage; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index aa6303360..0865c8f9d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -48,6 +48,7 @@ enum SignerType { secp256k1 = "secp256k1", // ethereum secp256k1btc = "secp256k1-btc", // bitcoin ed25519kda = "ed25519-kda", // kadena + ed25519sol = "ed25519-sol", // solana } interface KeyRecordAdd { diff --git a/packages/types/src/networks.ts b/packages/types/src/networks.ts index fcfccfbb3..effd5183a 100644 --- a/packages/types/src/networks.ts +++ b/packages/types/src/networks.ts @@ -71,6 +71,7 @@ export enum NetworkNames { RolluxTest = "TRLX", Rollux = "RLX", CagaAnkara = "CagaAnkara", + Solana = "SOL", } export enum CoingeckoPlatform { diff --git a/yarn.lock b/yarn.lock index 2574f2fba..5dbceca5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2783,6 +2783,7 @@ __metadata: "@rollup/plugin-node-resolve": ^15.2.3 "@rollup/plugin-replace": ^5.0.7 "@rollup/plugin-typescript": ^11.1.6 + "@types/bs58": ^4.0.4 "@types/chrome": ^0.0.268 "@types/ethereumjs-abi": ^0.6.5 "@types/events": ^3.0.3 @@ -2806,6 +2807,7 @@ __metadata: bignumber.js: ^9.1.2 bip39: ^3.1.0 bitcoinjs-lib: ^6.1.6 + bs58: ^6.0.0 buffer: ^6.0.3 chai: ^4.4.1 concurrently: ^8.2.2 @@ -2842,6 +2844,7 @@ __metadata: stream-http: ^3.2.0 switch-ts: ^1.1.1 systeminformation: ^5.22.11 + terser-webpack-plugin: ^5.3.10 ts-mocha: ^10.0.0 tsconfig-paths: ^4.2.0 typescript: ^4.9.5 @@ -4606,6 +4609,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.6 + resolution: "@jridgewell/source-map@npm:0.3.6" + dependencies: + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 + checksum: c9dc7d899397df95e3c9ec287b93c0b56f8e4453cd20743e2b9c8e779b1949bc3cccf6c01bb302779e46560eb45f62ea38d19fedd25370d814734268450a9f30 + languageName: node + linkType: hard + "@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" @@ -4623,7 +4636,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -8738,6 +8751,16 @@ __metadata: languageName: node linkType: hard +"@types/bs58@npm:^4.0.4": + version: 4.0.4 + resolution: "@types/bs58@npm:4.0.4" + dependencies: + "@types/node": "*" + base-x: ^3.0.6 + checksum: 9cac5a00343756f887ad906c10c71464620d352af40ff61373dcede434880a769be8b78380b890e9b13506851af4611ab59e7d4b7159af04f445e1fd5c34e3d0 + languageName: node + linkType: hard + "@types/bs58check@npm:^2.1.0": version: 2.1.0 resolution: "@types/bs58check@npm:2.1.0" @@ -10850,6 +10873,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.2": + version: 8.12.0 + resolution: "acorn@npm:8.12.0" + bin: + acorn: bin/acorn + checksum: ae142de8739ef15a5d936c550c1d267fc4dedcdbe62ad1aa2c0009afed1de84dd0a584684a5d200bb55d8db14f3e09a95c6e92a5303973c04b9a7413c36d1df0 + languageName: node + linkType: hard + "add@npm:^2.0.6": version: 2.0.6 resolution: "add@npm:2.0.6" @@ -11604,7 +11636,7 @@ __metadata: languageName: node linkType: hard -"base-x@npm:^3.0.2, base-x@npm:^3.0.5, base-x@npm:^3.0.8, base-x@npm:^3.0.9": +"base-x@npm:^3.0.2, base-x@npm:^3.0.5, base-x@npm:^3.0.6, base-x@npm:^3.0.8, base-x@npm:^3.0.9": version: 3.0.9 resolution: "base-x@npm:3.0.9" dependencies: @@ -11620,6 +11652,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^5.0.0": + version: 5.0.0 + resolution: "base-x@npm:5.0.0" + checksum: fa82bc9a963f7a765a3287ba632661669fe553d06ee0d4d4e282640335bff30ec685e3c3b1714e265f697b234facd02a310f1e2465db88f4f1a448e6267fbc65 + languageName: node + linkType: hard + "base32.js@npm:^0.1.0": version: 0.1.0 resolution: "base32.js@npm:0.1.0" @@ -12316,6 +12355,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^6.0.0": + version: 6.0.0 + resolution: "bs58@npm:6.0.0" + dependencies: + base-x: ^5.0.0 + checksum: 820334f9513bba6195136dfc9dfbd1f5aded6c7864639f3ee7b63c2d9d6f9f2813b9949b1f6beb9c161237be2a461097444c2ff587c8c3b824fe18878fa22448 + languageName: node + linkType: hard + "bs58check@npm:2.1.2, bs58check@npm:<3.0.0, bs58check@npm:^2.0.0, bs58check@npm:^2.1.1, bs58check@npm:^2.1.2": version: 2.1.2 resolution: "bs58check@npm:2.1.2" @@ -24605,6 +24653,15 @@ __metadata: languageName: node linkType: hard +"serialize-javascript@npm:^6.0.1": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" + dependencies: + randombytes: ^2.1.0 + checksum: c4839c6206c1d143c0f80763997a361310305751171dd95e4b57efee69b8f6edd8960a0b7fbfc45042aadff98b206d55428aee0dc276efe54f100899c7fa8ab7 + languageName: node + linkType: hard + "serve-index@npm:^1.9.1": version: 1.9.1 resolution: "serve-index@npm:1.9.1" @@ -25811,6 +25868,28 @@ __metadata: languageName: node linkType: hard +"terser-webpack-plugin@npm:^5.3.10": + version: 5.3.10 + resolution: "terser-webpack-plugin@npm:5.3.10" + dependencies: + "@jridgewell/trace-mapping": ^0.3.20 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.1 + terser: ^5.26.0 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: bd6e7596cf815f3353e2a53e79cbdec959a1b0276f5e5d4e63e9d7c3c5bb5306df567729da287d1c7b39d79093e56863c569c42c6c24cc34c76aa313bd2cbcea + languageName: node + linkType: hard + "terser@npm:^5.10.0, terser@npm:^5.7.2": version: 5.12.1 resolution: "terser@npm:5.12.1" @@ -25825,6 +25904,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.26.0": + version: 5.31.1 + resolution: "terser@npm:5.31.1" + dependencies: + "@jridgewell/source-map": ^0.3.3 + acorn: ^8.8.2 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: 6ab57e62e9cd690dc99b3d0ee2e07289cd3408109a950c7118bf39e32851a5bf08b67fe19e0ac43a5a98813792ac78101bf25e5aa524f05ae8bb4e0131d0feef + languageName: node + linkType: hard + "testrpc@npm:0.0.1": version: 0.0.1 resolution: "testrpc@npm:0.0.1" From ea979a06b714e4cd40ff3397d05a23d120bbf98a Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:58:04 -0700 Subject: [PATCH 04/20] devop: update --- yarn.lock | 67 +------------------------------------------------------ 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/yarn.lock b/yarn.lock index d9d89c9f9..699ee6699 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2844,7 +2844,6 @@ __metadata: stream-http: ^3.2.0 switch-ts: ^1.1.1 systeminformation: ^5.22.11 - terser-webpack-plugin: ^5.3.10 ts-mocha: ^10.0.0 tsconfig-paths: ^4.2.0 typescript: ^4.9.5 @@ -4609,16 +4608,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/source-map@npm:^0.3.3": - version: 0.3.6 - resolution: "@jridgewell/source-map@npm:0.3.6" - dependencies: - "@jridgewell/gen-mapping": ^0.3.5 - "@jridgewell/trace-mapping": ^0.3.25 - checksum: c9dc7d899397df95e3c9ec287b93c0b56f8e4453cd20743e2b9c8e779b1949bc3cccf6c01bb302779e46560eb45f62ea38d19fedd25370d814734268450a9f30 - languageName: node - linkType: hard - "@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" @@ -4636,7 +4625,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -10838,15 +10827,6 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.8.2": - version: 8.12.0 - resolution: "acorn@npm:8.12.0" - bin: - acorn: bin/acorn - checksum: ae142de8739ef15a5d936c550c1d267fc4dedcdbe62ad1aa2c0009afed1de84dd0a584684a5d200bb55d8db14f3e09a95c6e92a5303973c04b9a7413c36d1df0 - languageName: node - linkType: hard - "add@npm:^2.0.6": version: 2.0.6 resolution: "add@npm:2.0.6" @@ -24618,15 +24598,6 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.1": - version: 6.0.2 - resolution: "serialize-javascript@npm:6.0.2" - dependencies: - randombytes: ^2.1.0 - checksum: c4839c6206c1d143c0f80763997a361310305751171dd95e4b57efee69b8f6edd8960a0b7fbfc45042aadff98b206d55428aee0dc276efe54f100899c7fa8ab7 - languageName: node - linkType: hard - "serve-index@npm:^1.9.1": version: 1.9.1 resolution: "serve-index@npm:1.9.1" @@ -25833,28 +25804,6 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.10": - version: 5.3.10 - resolution: "terser-webpack-plugin@npm:5.3.10" - dependencies: - "@jridgewell/trace-mapping": ^0.3.20 - jest-worker: ^27.4.5 - schema-utils: ^3.1.1 - serialize-javascript: ^6.0.1 - terser: ^5.26.0 - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@swc/core": - optional: true - esbuild: - optional: true - uglify-js: - optional: true - checksum: bd6e7596cf815f3353e2a53e79cbdec959a1b0276f5e5d4e63e9d7c3c5bb5306df567729da287d1c7b39d79093e56863c569c42c6c24cc34c76aa313bd2cbcea - languageName: node - linkType: hard - "terser@npm:^5.10.0, terser@npm:^5.7.2": version: 5.12.1 resolution: "terser@npm:5.12.1" @@ -25869,20 +25818,6 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.26.0": - version: 5.31.1 - resolution: "terser@npm:5.31.1" - dependencies: - "@jridgewell/source-map": ^0.3.3 - acorn: ^8.8.2 - commander: ^2.20.0 - source-map-support: ~0.5.20 - bin: - terser: bin/terser - checksum: 6ab57e62e9cd690dc99b3d0ee2e07289cd3408109a950c7118bf39e32851a5bf08b67fe19e0ac43a5a98813792ac78101bf25e5aa524f05ae8bb4e0131d0feef - languageName: node - linkType: hard - "testrpc@npm:0.0.1": version: 0.0.1 resolution: "testrpc@npm:0.0.1" From 958e59e5953d34d3a6cbe137f103623f2d86d20c Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:22:40 -0700 Subject: [PATCH 05/20] devop: solana tokens --- packages/extension/package.json | 2 + .../src/libs/keyring/public-keyring.ts | 10 + .../libs/assets-handlers/assetinfo-mew.ts | 19 +- .../libs/assets-handlers/solanachain.ts | 58 ++++ .../libs/assets-handlers/token-lists.ts | 6 +- .../libs/assets-handlers/tomochain.ts | 2 +- .../assets-handlers/types/tokenbalance-mew.ts | 3 +- .../src/providers/solana/libs/api.ts | 41 ++- .../src/providers/solana/networks/solana.ts | 6 +- .../src/providers/solana/types/sol-network.ts | 79 +++-- .../src/providers/solana/types/sol-token.ts | 2 +- packages/types/src/networks.ts | 1 + yarn.lock | 302 +++++++++++++++++- 13 files changed, 485 insertions(+), 46 deletions(-) create mode 100644 packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts diff --git a/packages/extension/package.json b/packages/extension/package.json index 211835aff..506c44793 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -38,6 +38,8 @@ "@ledgerhq/hw-transport-webusb": "^6.29.2", "@metamask/eth-sig-util": "^7.0.3", "@rollup/plugin-replace": "^5.0.7", + "@solana/spl-token": "^0.4.8", + "@solana/web3.js": "^1.95.2", "@types/chrome": "^0.0.269", "@types/events": "^3.0.3", "@types/less": "^3.0.6", diff --git a/packages/extension/src/libs/keyring/public-keyring.ts b/packages/extension/src/libs/keyring/public-keyring.ts index c9a2048e8..a815015e8 100644 --- a/packages/extension/src/libs/keyring/public-keyring.ts +++ b/packages/extension/src/libs/keyring/public-keyring.ts @@ -77,6 +77,16 @@ class PublicKeyRing { walletType: WalletType.mnemonic, isHardware: false, }; + allKeys["7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE"] = { + address: "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE", + basePath: "m/501'/2'/0'/1", + name: "fake sol account #1", + pathIndex: 0, + publicKey: "0x0", + signerType: SignerType.ed25519sol, + walletType: WalletType.mnemonic, + isHardware: false, + }; } return allKeys; } diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts index f84545f31..7e9a738cf 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts @@ -21,6 +21,7 @@ import { getKnownNetworkTokens } from "./token-lists"; import { CoingeckoPlatform, NetworkNames } from "@enkryptcom/types"; import { NATIVE_TOKEN_ADDRESS } from "../common"; import getTomoBalances from "./tomochain"; +import getSolBalances from "./solanachain"; import { CoinGeckoTokenMarket } from "@/libs/market-data/types"; const API_ENPOINT = "https://tokenbalance.mewapi.io/"; @@ -135,14 +136,21 @@ const supportedNetworks: Record = { tbName: "degen", cgPlatform: CoingeckoPlatform.Degen, }, + [NetworkNames.Solana]: { + tbName: "", + cgPlatform: CoingeckoPlatform.Solana, + }, }; const getTokens = ( - chain: SupportedNetworkNames, + network: BaseNetwork, address: string ): Promise => { + const chain = network.name as SupportedNetworkNames; if (chain === NetworkNames.TomoChain) { return getTomoBalances(chain, address); + } else if (chain === NetworkNames.Solana) { + return getSolBalances(network, address); } let url = ""; if (chain === NetworkNames.Ethereum || chain === NetworkNames.Binance) @@ -178,7 +186,7 @@ export default ( if (!Object.keys(supportedNetworks).includes(network.name)) throw new Error("TOKENBALANCE-MEW: network not supported"); const networkName = network.name as SupportedNetworkNames; - return getTokens(networkName, address).then(async (tokens) => { + return getTokens(network, address).then(async (tokens) => { const balances: Record = tokens.reduce( (obj, cur) => ({ ...obj, [cur.contract]: cur }), {} @@ -197,6 +205,7 @@ export default ( (obj, cur) => ({ ...obj, [cur.contract]: null }), {} as Record ); + console.log(marketInfo); if (network.coingeckoID) { const nativeMarket = await marketData.getMarketData([ network.coingeckoID, @@ -224,11 +233,11 @@ export default ( const tokenInfo: Record = await getKnownNetworkTokens( network.name ); - + console.log(tokenInfo); tokenInfo[NATIVE_TOKEN_ADDRESS] = { chainId: (network as EvmNetwork).chainID, name: network.name_long, - decimals: 18, + decimals: network.decimals, address: NATIVE_TOKEN_ADDRESS, logoURI: network.icon, symbol: network.currencyName, @@ -237,6 +246,7 @@ export default ( const unknownTokens: string[] = []; let nativeAsset: AssetsType | null = null; for (const [address, market] of Object.entries(marketInfo)) { + console.log(address, market, tokenInfo[address]); if (market && tokenInfo[address]) { const userBalance = fromBase( balances[address].balance, @@ -279,6 +289,7 @@ export default ( const promises = unknownTokens.map((t) => api.getTokenInfo(t)); await Promise.all(promises).then((tokenMeta) => { tokenMeta.forEach((tInfo, idx) => { + if (tInfo.symbol === "UNKNWN") return; const userBalance = fromBase( balances[unknownTokens[idx]].balance, tInfo.decimals diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts new file mode 100644 index 000000000..babb42ba5 --- /dev/null +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts @@ -0,0 +1,58 @@ +import { TokenBalance } from "./types/tokenbalance-mew"; +import { NATIVE_TOKEN_ADDRESS } from "../common"; +import { numberToHex } from "@enkryptcom/utils"; +import { BaseNetwork } from "@/types/base-network"; +import { + Connection, + GetProgramAccountsFilter, + PublicKey, +} from "@solana/web3.js"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { BNType } from "@/providers/common/types"; +import { toBN } from "web3-utils"; + +const getBalances = (network: BaseNetwork, address: string) => { + const solConnection = new Connection(network.node); + const filters: GetProgramAccountsFilter[] = [ + { + dataSize: 165, + }, + { + memcmp: { + offset: 32, + bytes: address, + }, + }, + ]; + return solConnection + .getParsedProgramAccounts(TOKEN_PROGRAM_ID, { filters: filters }) + .then((accounts) => { + const balances: TokenBalance[] = []; + const balanceObj = {} as Record; + accounts.forEach((acc) => { + const balance = numberToHex( + (acc.account.data as any).parsed.info.tokenAmount.amount + ); + const contract = (acc.account.data as any).parsed.info.mint; + if (!balanceObj[contract]) balanceObj[contract] = toBN(0); + balanceObj[contract] = balanceObj[contract].add(toBN(balance)); + }); + Object.keys(balanceObj).forEach((contract) => { + balances.push({ + balance: numberToHex(balanceObj[contract]), + contract, + }); + }); + return solConnection + .getBalance(new PublicKey(address)) + .then((balance) => { + balances.unshift({ + balance: numberToHex(balance), + contract: NATIVE_TOKEN_ADDRESS, + }); + return balances; + }); + }); +}; + +export default getBalances; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts index 48243f0e3..6e38d2c0a 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts @@ -30,6 +30,7 @@ const TokenList: Record = { [NetworkNames.Blast]: `https://tokens.coingecko.com/${CoingeckoPlatform.Blast}/all.json`, [NetworkNames.Sanko]: `https://tokens.coingecko.com/${CoingeckoPlatform.Sanko}/all.json`, [NetworkNames.Degen]: `https://tokens.coingecko.com/${CoingeckoPlatform.Degen}/all.json`, + [NetworkNames.Solana]: `https://tokens.coingecko.com/${CoingeckoPlatform.Solana}/all.json`, }; const getKnownNetworkTokens = async ( @@ -46,7 +47,10 @@ const getKnownNetworkTokens = async ( const tokens: CGToken[] = json.tokens; const tObject: Record = {}; tokens.forEach((t) => { - t.address = t.address.toLowerCase(); + t.address = + networkName !== NetworkNames.Solana + ? t.address.toLowerCase() + : t.address; tObject[t.address] = t; }); return tObject; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/tomochain.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/tomochain.ts index 064e1968a..ea20a2a56 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/tomochain.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/tomochain.ts @@ -1,6 +1,6 @@ import { SupportedNetworkNames, TokenBalance } from "./types/tokenbalance-mew"; import { NATIVE_TOKEN_ADDRESS } from "../common"; -import { numberToHex } from "web3-utils"; +import { numberToHex } from "@enkryptcom/utils"; interface TokenBalanceType { token: string; diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts index 89093b4b4..9a8130872 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts @@ -52,4 +52,5 @@ export type SupportedNetworkNames = | NetworkNames.Rollux | NetworkNames.Sanko | NetworkNames.Degen - | NetworkNames.Blast; + | NetworkNames.Blast + | NetworkNames.Solana; diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts index 5abc5900d..62b7a2916 100644 --- a/packages/extension/src/providers/solana/libs/api.ts +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -1,12 +1,18 @@ import { SOLRawInfo } from "@/types/activity"; import { ProviderAPIInterface } from "@/types/provider"; import { getAddress as getSolAddress } from "../types/sol-network"; +import { Connection, PublicKey } from "@solana/web3.js"; +import { numberToHex } from "@enkryptcom/utils"; +import { ERC20TokenInfo } from "@/providers/ethereum/types"; +import cacheFetch from "@/libs/cache-fetch"; class API implements ProviderAPIInterface { node: string; + web3: Connection; constructor(node: string) { this.node = node; + this.web3 = new Connection(this.node); } public get api() { @@ -22,12 +28,43 @@ class API implements ProviderAPIInterface { return null; } async getBalance(pubkey: string): Promise { - console.log(pubkey, "getbalance"); - return "0"; + const balance = await this.web3.getBalance( + new PublicKey(this.getAddress(pubkey)) + ); + return numberToHex(balance); } async broadcastTx(rawtx: string): Promise { console.log(rawtx, "broadcasttx"); return true; } + getTokenInfo = async (contractAddress: string): Promise => { + const allTokensResponse = await cacheFetch( + { + url: "https://utl.solcast.dev/solana-tokenlist.json", + }, + 10 * 60 * 1000 + ); + const allTokens = allTokensResponse.tokens as { + address: string; + decimals: number; + name: string; + symbol: string; + }[]; + for (const t of allTokens) { + if (t.address === contractAddress) { + console.log(t, contractAddress); + return { + name: t.name, + symbol: t.symbol, + decimals: t.decimals, + }; + } + } + return { + name: "Unknown", + symbol: "UNKNWN", + decimals: 9, + }; + }; } export default API; diff --git a/packages/extension/src/providers/solana/networks/solana.ts b/packages/extension/src/providers/solana/networks/solana.ts index e5a2cc12b..9e9098465 100644 --- a/packages/extension/src/providers/solana/networks/solana.ts +++ b/packages/extension/src/providers/solana/networks/solana.ts @@ -1,6 +1,7 @@ import { NetworkNames } from "@enkryptcom/types"; import { SolanaNetwork, SolanaNetworkOptions } from "../types/sol-network"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; +import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; const solanaOptions: SolanaNetworkOptions = { name: NetworkNames.Solana, @@ -12,11 +13,12 @@ const solanaOptions: SolanaNetworkOptions = { currencyName: "SOL", currencyNameLong: "Solana", icon: require("./icons/sol.svg"), - decimals: 8, - node: "https://node.mewapi.io/ws/sol", + decimals: 9, + node: "https://nodes.mewapi.io/rpc/sol", coingeckoID: "solana", activityHandler: wrapActivityHandler(() => Promise.resolve([])), basePath: "m/44'/501'", + assetsInfoHandler, }; const bitcoin = new SolanaNetwork(solanaOptions); diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index 25366a41c..1161c5955 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -18,6 +18,7 @@ import { SOLToken } from "./sol-token"; import { NFTCollection } from "@/types/nft"; import { fromBase, hexToBuffer } from "@enkryptcom/utils"; import bs58 from "bs58"; +import getBalances from "@/providers/ethereum/libs/assets-handlers/solanachain"; export interface SolanaNetworkOptions { name: NetworkNames; @@ -41,9 +42,14 @@ export interface SolanaNetworkOptions { network: BaseNetwork, address: string ) => Promise; + assetsInfoHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; } export const getAddress = (pubkey: string) => { + if (pubkey.length === 44) return pubkey; return bs58.encode(hexToBuffer(pubkey)); }; @@ -57,6 +63,10 @@ export class SolanaNetwork extends BaseNetwork { network: BaseNetwork, address: string ) => Promise; + assetsInfoHandler?: ( + network: BaseNetwork, + address: string + ) => Promise; constructor(options: SolanaNetworkOptions) { const api = async () => { const api = new SolAPI(options.node); @@ -75,6 +85,7 @@ export class SolanaNetwork extends BaseNetwork { super(baseOptions); this.activityHandler = options.activityHandler; this.NFTHandler = options.NFTHandler; + this.assetsInfoHandler = options.assetsInfoHandler; } public async getAllTokens(pubkey: string): Promise { @@ -94,38 +105,44 @@ export class SolanaNetwork extends BaseNetwork { } public async getAllTokenInfo(pubkey: string): Promise { - const balance = await (await this.api()).getBalance(pubkey); - let marketData: (CoinGeckoTokenMarket | null)[] = []; - if (this.coingeckoID) { - const market = new MarketData(); - marketData = await market.getMarketData([this.coingeckoID]); + if (this.assetsInfoHandler) { + return this.assetsInfoHandler(this, getAddress(pubkey)); + } else { + const balance = await (await this.api()).getBalance(pubkey); + let marketData: (CoinGeckoTokenMarket | null)[] = []; + if (this.coingeckoID) { + const market = new MarketData(); + marketData = await market.getMarketData([this.coingeckoID]); + } + const userBalance = fromBase(balance, this.decimals); + const usdBalance = new BigNumber(userBalance).times( + marketData.length ? marketData[0]!.current_price : 0 + ); + const nativeAsset: AssetsType = { + balance: balance, + balancef: formatFloatingPointValue(userBalance).value, + balanceUSD: usdBalance.toNumber(), + balanceUSDf: formatFiatValue(usdBalance.toString()).value, + icon: this.icon, + name: this.name_long, + symbol: this.currencyName, + value: marketData.length + ? marketData[0]!.current_price.toString() + : "0", + valuef: formatFiatValue( + marketData.length ? marketData[0]!.current_price.toString() : "0" + ).value, + contract: "", + decimals: this.decimals, + sparkline: marketData.length + ? new Sparkline(marketData[0]!.sparkline_in_7d.price, 25).dataValues + : "", + priceChangePercentage: marketData.length + ? marketData[0]!.price_change_percentage_7d_in_currency + : 0, + }; + return [nativeAsset]; } - const userBalance = fromBase(balance, this.decimals); - const usdBalance = new BigNumber(userBalance).times( - marketData.length ? marketData[0]!.current_price : 0 - ); - const nativeAsset: AssetsType = { - balance: balance, - balancef: formatFloatingPointValue(userBalance).value, - balanceUSD: usdBalance.toNumber(), - balanceUSDf: formatFiatValue(usdBalance.toString()).value, - icon: this.icon, - name: this.name_long, - symbol: this.currencyName, - value: marketData.length ? marketData[0]!.current_price.toString() : "0", - valuef: formatFiatValue( - marketData.length ? marketData[0]!.current_price.toString() : "0" - ).value, - contract: "", - decimals: this.decimals, - sparkline: marketData.length - ? new Sparkline(marketData[0]!.sparkline_in_7d.price, 25).dataValues - : "", - priceChangePercentage: marketData.length - ? marketData[0]!.price_change_percentage_7d_in_currency - : 0, - }; - return [nativeAsset]; } public getAllActivity(address: string): Promise { return this.activityHandler(this, address); diff --git a/packages/extension/src/providers/solana/types/sol-token.ts b/packages/extension/src/providers/solana/types/sol-token.ts index 296e08103..1d20938d5 100644 --- a/packages/extension/src/providers/solana/types/sol-token.ts +++ b/packages/extension/src/providers/solana/types/sol-token.ts @@ -14,6 +14,6 @@ export class SOLToken extends BaseToken { } public async send(): Promise { - throw new Error("EVM-send is not implemented here"); + throw new Error("sol-send is not implemented here"); } } diff --git a/packages/types/src/networks.ts b/packages/types/src/networks.ts index 6aa1b9b2a..602d672e7 100644 --- a/packages/types/src/networks.ts +++ b/packages/types/src/networks.ts @@ -124,4 +124,5 @@ export enum CoingeckoPlatform { Blast = "blast", Sanko = "sanko", Degen = "degen", + Solana = "solana", } diff --git a/yarn.lock b/yarn.lock index c7f161b77..debac56a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1674,7 +1674,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.25.0": +"@babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.0": version: 7.25.0 resolution: "@babel/runtime@npm:7.25.0" dependencies: @@ -2792,6 +2792,8 @@ __metadata: "@rollup/plugin-node-resolve": ^15.2.3 "@rollup/plugin-replace": ^5.0.7 "@rollup/plugin-typescript": ^11.1.6 + "@solana/spl-token": ^0.4.8 + "@solana/web3.js": ^1.95.2 "@types/bs58": ^4.0.4 "@types/chrome": ^0.0.269 "@types/ethereumjs-abi": ^0.6.5 @@ -6142,7 +6144,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.4.2": +"@noble/curves@npm:1.4.2, @noble/curves@npm:^1.4.2": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" dependencies: @@ -7591,6 +7593,202 @@ __metadata: languageName: node linkType: hard +"@solana/codecs-core@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/codecs-core@npm:2.0.0-preview.2" + dependencies: + "@solana/errors": 2.0.0-preview.2 + checksum: 7b618645d61cc7ef7ed0da00deb852f282ef8eaa8de5070a86523fdb1c3dec8e0a93c3989038056e088ea00e07ed5fd93f0f325d86f1961767310ad227e17871 + languageName: node + linkType: hard + +"@solana/codecs-core@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/codecs-core@npm:2.0.0-preview.4" + dependencies: + "@solana/errors": 2.0.0-preview.4 + peerDependencies: + typescript: ">=5" + checksum: 5dca4b46b0380153494620353208eebdbaefab8702c6fd8aa82738c5ba6832ec54d02035a339c4972e1996bd7796a4c08c39d678d82d96a558991989467d7a6d + languageName: node + linkType: hard + +"@solana/codecs-data-structures@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/codecs-data-structures@npm:2.0.0-preview.2" + dependencies: + "@solana/codecs-core": 2.0.0-preview.2 + "@solana/codecs-numbers": 2.0.0-preview.2 + "@solana/errors": 2.0.0-preview.2 + checksum: 6cad16fa2fd17f22f1cee191a96c2ed85d809587c87e9191ef12c5e928512e69a065bb573264d93f6c11565b0e1a6541f961216502a4336636445d1c6d949559 + languageName: node + linkType: hard + +"@solana/codecs-data-structures@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/codecs-data-structures@npm:2.0.0-preview.4" + dependencies: + "@solana/codecs-core": 2.0.0-preview.4 + "@solana/codecs-numbers": 2.0.0-preview.4 + "@solana/errors": 2.0.0-preview.4 + peerDependencies: + typescript: ">=5" + checksum: 1af74509e91bfc1492e44ebb0e21cf8a5e59f77b4a78fdabd1b76beef01c243d1f987c7f6773ada1063c21b97b598107a0c8d7191eee23a58304ba5b57f05453 + languageName: node + linkType: hard + +"@solana/codecs-numbers@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/codecs-numbers@npm:2.0.0-preview.2" + dependencies: + "@solana/codecs-core": 2.0.0-preview.2 + "@solana/errors": 2.0.0-preview.2 + checksum: 9f132a8ed54d31548437c0e337e3f3e2a076b3081cd7652c0e5fca16416cf0b3265744f206495bd3761c53bad7542c4f08e6ab02688660f355705b63df981f97 + languageName: node + linkType: hard + +"@solana/codecs-numbers@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/codecs-numbers@npm:2.0.0-preview.4" + dependencies: + "@solana/codecs-core": 2.0.0-preview.4 + "@solana/errors": 2.0.0-preview.4 + peerDependencies: + typescript: ">=5" + checksum: f7eb51b70a277ed096c9259b943c7ad50fb82dd8ce81b3262a22e8501774e3237fa0b6367221834dad2c16a75ec47bfd7a30d9f99d0c84325e7f2220f11bade4 + languageName: node + linkType: hard + +"@solana/codecs-strings@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/codecs-strings@npm:2.0.0-preview.2" + dependencies: + "@solana/codecs-core": 2.0.0-preview.2 + "@solana/codecs-numbers": 2.0.0-preview.2 + "@solana/errors": 2.0.0-preview.2 + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + checksum: 0df48a64ac2baa92c4c671380a4c506dd6ceec0c326c2bc59485298813e2fa659ceeb6fb26ef95be5a15b5b18262d33fde4520f09fe73b5d90da6abbd9e0a3a0 + languageName: node + linkType: hard + +"@solana/codecs-strings@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/codecs-strings@npm:2.0.0-preview.4" + dependencies: + "@solana/codecs-core": 2.0.0-preview.4 + "@solana/codecs-numbers": 2.0.0-preview.4 + "@solana/errors": 2.0.0-preview.4 + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: ">=5" + checksum: 979503d4964acb6d6a28b2465949b16e308f173960f0ead45ab87107c900c3aa54d6b81fe27aa6076392b9fa8d5d1e74211fd132648ebe3dfe96c9877726a3e8 + languageName: node + linkType: hard + +"@solana/codecs@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/codecs@npm:2.0.0-preview.2" + dependencies: + "@solana/codecs-core": 2.0.0-preview.2 + "@solana/codecs-data-structures": 2.0.0-preview.2 + "@solana/codecs-numbers": 2.0.0-preview.2 + "@solana/codecs-strings": 2.0.0-preview.2 + "@solana/options": 2.0.0-preview.2 + checksum: 33e12e2d4d56b6f2623443043093122775f19f5ddc266a89d447c19ed2f2e3f9cffe4385f05d889a496eb06205cebc33db646058fb63ea3f85a8e561c4441708 + languageName: node + linkType: hard + +"@solana/codecs@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/codecs@npm:2.0.0-preview.4" + dependencies: + "@solana/codecs-core": 2.0.0-preview.4 + "@solana/codecs-data-structures": 2.0.0-preview.4 + "@solana/codecs-numbers": 2.0.0-preview.4 + "@solana/codecs-strings": 2.0.0-preview.4 + "@solana/options": 2.0.0-preview.4 + peerDependencies: + typescript: ">=5" + checksum: fea7d819c374dc5726faabd648726c0024479d4f6a9802fca85702118087353c0a21deff1da3ad0be6f047f1a1c60be42330f373a6c2f4232973826672f4b40e + languageName: node + linkType: hard + +"@solana/errors@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/errors@npm:2.0.0-preview.2" + dependencies: + chalk: ^5.3.0 + commander: ^12.0.0 + bin: + errors: bin/cli.js + checksum: b93bdc42f039dfcd88d091cdf3d7f11bffcbb673797cd9ac54b89081a62f59ea04b4d7c0c9e80e532f6209c05d0a0b715e61f5ed3748f3d47a421f3d6a70e31c + languageName: node + linkType: hard + +"@solana/errors@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/errors@npm:2.0.0-preview.4" + dependencies: + chalk: ^5.3.0 + commander: ^12.1.0 + peerDependencies: + typescript: ">=5" + bin: + errors: bin/cli.mjs + checksum: e7e4b3a3957b675f0e2471f4fc48220d90094b24eef16196668372d2a223dc36b42434d87a36dc46583ef96e8ebe9d6030b4beca2fe03663d41d6d7e696e8aa9 + languageName: node + linkType: hard + +"@solana/options@npm:2.0.0-preview.2": + version: 2.0.0-preview.2 + resolution: "@solana/options@npm:2.0.0-preview.2" + dependencies: + "@solana/codecs-core": 2.0.0-preview.2 + "@solana/codecs-numbers": 2.0.0-preview.2 + checksum: 63875f73174e38e40d0f6d5349ad590b38dd3821455886f5ba7b9ff6e6359114bb99e0b72ae0eb7362fec083dfb383817feebefd357e4f88d40cdded2b1a3676 + languageName: node + linkType: hard + +"@solana/options@npm:2.0.0-preview.4": + version: 2.0.0-preview.4 + resolution: "@solana/options@npm:2.0.0-preview.4" + dependencies: + "@solana/codecs-core": 2.0.0-preview.4 + "@solana/codecs-data-structures": 2.0.0-preview.4 + "@solana/codecs-numbers": 2.0.0-preview.4 + "@solana/codecs-strings": 2.0.0-preview.4 + "@solana/errors": 2.0.0-preview.4 + peerDependencies: + typescript: ">=5" + checksum: d6da97a14bedb4a7a560f743f5715dc23268beb096de534bfe32a31535b3767383e9f4a8c5728b6160c614370f3cbc2cdc144853bf99a50f4db6a9b394a89d06 + languageName: node + linkType: hard + +"@solana/spl-token-group@npm:^0.0.5": + version: 0.0.5 + resolution: "@solana/spl-token-group@npm:0.0.5" + dependencies: + "@solana/codecs": 2.0.0-preview.4 + "@solana/spl-type-length-value": 0.1.0 + peerDependencies: + "@solana/web3.js": ^1.94.0 + checksum: 0bfe93e4674d3dcbdc547bebf49b1ff8f9a10d62d067d8a360f8618f4226eed7c26691cfa51fee8cff1ef3828bd929e2152b178144d8b2185102108fcd5c2646 + languageName: node + linkType: hard + +"@solana/spl-token-metadata@npm:^0.1.3": + version: 0.1.4 + resolution: "@solana/spl-token-metadata@npm:0.1.4" + dependencies: + "@solana/codecs": 2.0.0-preview.2 + "@solana/spl-type-length-value": 0.1.0 + peerDependencies: + "@solana/web3.js": ^1.91.6 + checksum: 974969ce59a184f17e8d2f6aa98a118154ec042e9efa41a7b8ad33aecff8234d0a1161573807a59ab9e9d3557d8438b0b78f785cfc481f038900337f73523b02 + languageName: node + linkType: hard + "@solana/spl-token@npm:^0.3.7": version: 0.3.8 resolution: "@solana/spl-token@npm:0.3.8" @@ -7604,6 +7802,30 @@ __metadata: languageName: node linkType: hard +"@solana/spl-token@npm:^0.4.8": + version: 0.4.8 + resolution: "@solana/spl-token@npm:0.4.8" + dependencies: + "@solana/buffer-layout": ^4.0.0 + "@solana/buffer-layout-utils": ^0.2.0 + "@solana/spl-token-group": ^0.0.5 + "@solana/spl-token-metadata": ^0.1.3 + buffer: ^6.0.3 + peerDependencies: + "@solana/web3.js": ^1.94.0 + checksum: 85c48b2baee93ec70f688e5c3460de2974845bc513953f26be851a1a9d074926095661b5247f7de756cbaaeb42b10e6bb2ae73743fc2a5137e50ae25f471ab7e + languageName: node + linkType: hard + +"@solana/spl-type-length-value@npm:0.1.0": + version: 0.1.0 + resolution: "@solana/spl-type-length-value@npm:0.1.0" + dependencies: + buffer: ^6.0.3 + checksum: 9bea6d9638fd2093db4542b12ac657407950737bd00ed919448308df59bfeb9d97a96e471d7824ba4077daeb01bbb0cf9e410d412767555e49a5bfc6bfb9bc91 + languageName: node + linkType: hard + "@solana/web3.js@npm:1.77.3": version: 1.77.3 resolution: "@solana/web3.js@npm:1.77.3" @@ -7673,6 +7895,29 @@ __metadata: languageName: node linkType: hard +"@solana/web3.js@npm:^1.95.2": + version: 1.95.2 + resolution: "@solana/web3.js@npm:1.95.2" + dependencies: + "@babel/runtime": ^7.24.8 + "@noble/curves": ^1.4.2 + "@noble/hashes": ^1.4.0 + "@solana/buffer-layout": ^4.0.1 + agentkeepalive: ^4.5.0 + bigint-buffer: ^1.1.5 + bn.js: ^5.2.1 + borsh: ^0.7.0 + bs58: ^4.0.1 + buffer: 6.0.3 + fast-stable-stringify: ^1.0.0 + jayson: ^4.1.1 + node-fetch: ^2.7.0 + rpc-websockets: ^9.0.2 + superstruct: ^2.0.2 + checksum: 1d06e929a2d4c7bcb9d5155122d49f01f6baff0cd1f0fe8f21d75d077e44354fb9a87c1eba93dce63993acbbba4c8f4c03219f77ff6fa91b3c7c49d6b32eda33 + languageName: node + linkType: hard + "@stablelib/aead@npm:^1.0.1": version: 1.0.1 resolution: "@stablelib/aead@npm:1.0.1" @@ -13366,6 +13611,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^12.0.0, commander@npm:^12.1.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514 + languageName: node + linkType: hard + "commander@npm:^2.15.0, commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.20.3": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -18817,6 +19069,28 @@ __metadata: languageName: node linkType: hard +"jayson@npm:^4.1.1": + version: 4.1.1 + resolution: "jayson@npm:4.1.1" + dependencies: + "@types/connect": ^3.4.33 + "@types/node": ^12.12.54 + "@types/ws": ^7.4.4 + JSONStream: ^1.3.5 + commander: ^2.20.3 + delay: ^5.0.0 + es6-promisify: ^5.0.0 + eyes: ^0.1.8 + isomorphic-ws: ^4.0.1 + json-stringify-safe: ^5.0.1 + uuid: ^8.3.2 + ws: ^7.5.10 + bin: + jayson: bin/jayson.js + checksum: 3a0f94f6548d5f6c366a9e6791129f2e19697ea09c2c37e9112b1a19902802aca8375b024b6b7a1e0465521f9fe6f06723258a6aac685290ce3761d92ea61e0a + languageName: node + linkType: hard + "jest-diff@npm:^27.5.1": version: 27.5.1 resolution: "jest-diff@npm:27.5.1" @@ -24311,7 +24585,7 @@ __metadata: languageName: node linkType: hard -"rpc-websockets@npm:^9.0.0": +"rpc-websockets@npm:^9.0.0, rpc-websockets@npm:^9.0.2": version: 9.0.2 resolution: "rpc-websockets@npm:9.0.2" dependencies: @@ -25737,6 +26011,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^2.0.2": + version: 2.0.2 + resolution: "superstruct@npm:2.0.2" + checksum: a5f75b72cb8b14b86f4f7f750dae8c5ab0e4e1d92414b55e7625bae07bbcafad81c92486e7e32ccacd6ae1f553caff2b92a50ff42ad5093fd35b9cb7f4e5ec86 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -29190,6 +29471,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^7.5.10": + version: 7.5.10 + resolution: "ws@npm:7.5.10" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: f9bb062abf54cc8f02d94ca86dcd349c3945d63851f5d07a3a61c2fcb755b15a88e943a63cf580cbdb5b74436d67ef6b67f745b8f7c0814e411379138e1863cb + languageName: node + linkType: hard + "ws@npm:^8.16.0, ws@npm:^8.4.2, ws@npm:^8.5.0, ws@npm:^8.8.1": version: 8.16.0 resolution: "ws@npm:8.16.0" From 7e8d6ecc3743c7029134ed5dc8910c2455ee04bc Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:40:08 -0700 Subject: [PATCH 06/20] devop: solana send screen --- .../extension/src/libs/cache-fetch/index.ts | 7 +- .../extension/src/libs/cache-fetch/types.ts | 1 + .../nft-select-list/index.vue | 3 +- .../libs/assets-handlers/assetinfo-mew.ts | 3 - .../extension/src/providers/solana/index.ts | 5 +- .../extension/src/providers/solana/inject.ts | 6 +- .../solana/libs/accounts-state/index.ts | 2 +- .../solana/libs/accounts-state/types.ts | 2 +- .../src/providers/solana/libs/api.ts | 38 +- .../providers/solana/libs/message-router.ts | 20 - .../src/providers/solana/libs/utils.ts | 105 +-- .../solana/methods/btc_getNetwork.ts | 34 - .../solana/methods/btc_getPublicKey.ts | 33 - .../solana/methods/btc_requestAccounts.ts | 80 --- .../solana/methods/btc_signMessage.ts | 52 -- .../providers/solana/methods/btc_signPsbt.ts | 55 -- .../solana/methods/btc_switchNetwork.ts | 60 -- .../src/providers/solana/methods/index.ts | 17 +- .../src/providers/solana/types/sol-network.ts | 10 +- .../src/providers/solana/types/sol-token.ts | 8 +- .../src/providers/solana/ui/index.ts | 4 +- .../src/providers/solana/ui/libs/signer.ts | 127 +--- .../src/providers/solana/ui/libs/tx-size.ts | 260 ------- .../components/send-address-input.vue | 20 +- .../components/send-alert.vue | 72 -- .../components/send-token-select.vue | 19 +- .../solana/ui/send-transaction/index.vue | 648 +++++++++++------- .../verify-transaction/index.vue | 156 +++-- .../src/providers/solana/ui/types.ts | 6 +- packages/extension/src/types/provider.ts | 5 +- .../action/views/send-transaction/index.vue | 2 + 31 files changed, 609 insertions(+), 1251 deletions(-) delete mode 100644 packages/extension/src/providers/solana/methods/btc_getNetwork.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_getPublicKey.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_requestAccounts.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_signMessage.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_signPsbt.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_switchNetwork.ts delete mode 100644 packages/extension/src/providers/solana/ui/libs/tx-size.ts delete mode 100644 packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue diff --git a/packages/extension/src/libs/cache-fetch/index.ts b/packages/extension/src/libs/cache-fetch/index.ts index cfdb9cb21..a553beba2 100644 --- a/packages/extension/src/libs/cache-fetch/index.ts +++ b/packages/extension/src/libs/cache-fetch/index.ts @@ -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; }); } }; diff --git a/packages/extension/src/libs/cache-fetch/types.ts b/packages/extension/src/libs/cache-fetch/types.ts index 582ddf46b..574a4eb84 100644 --- a/packages/extension/src/libs/cache-fetch/types.ts +++ b/packages/extension/src/libs/cache-fetch/types.ts @@ -2,6 +2,7 @@ export interface RequestOptions { url: string; post?: Record; headers?: Record; + postProcess?: (data: any) => any; } export interface StoredData { timestamp: number; diff --git a/packages/extension/src/providers/common/ui/send-transaction/nft-select-list/index.vue b/packages/extension/src/providers/common/ui/send-transaction/nft-select-list/index.vue index 09ff241d9..67689d189 100644 --- a/packages/extension/src/providers/common/ui/send-transaction/nft-select-list/index.vue +++ b/packages/extension/src/providers/common/ui/send-transaction/nft-select-list/index.vue @@ -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, + type: Object as PropType, default: () => ({}), }, address: { diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts index 7e9a738cf..5d33b8ef7 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts @@ -205,7 +205,6 @@ export default ( (obj, cur) => ({ ...obj, [cur.contract]: null }), {} as Record ); - console.log(marketInfo); if (network.coingeckoID) { const nativeMarket = await marketData.getMarketData([ network.coingeckoID, @@ -233,7 +232,6 @@ export default ( const tokenInfo: Record = await getKnownNetworkTokens( network.name ); - console.log(tokenInfo); tokenInfo[NATIVE_TOKEN_ADDRESS] = { chainId: (network as EvmNetwork).chainID, name: network.name_long, @@ -246,7 +244,6 @@ export default ( const unknownTokens: string[] = []; let nativeAsset: AssetsType | null = null; for (const [address, market] of Object.entries(marketInfo)) { - console.log(address, market, tokenInfo[address]); if (market && tokenInfo[address]) { const userBalance = fromBase( balances[address].balance, diff --git a/packages/extension/src/providers/solana/index.ts b/packages/extension/src/providers/solana/index.ts index b937a2828..918228e47 100644 --- a/packages/extension/src/providers/solana/index.ts +++ b/packages/extension/src/providers/solana/index.ts @@ -33,7 +33,10 @@ class SolanaProvider this.network = network; this.toWindow = toWindow; this.setMiddleWares(); - this.requestProvider = getRequestProvider("", this.middlewares); + this.requestProvider = getRequestProvider( + this.network.node, + this.middlewares + ); this.requestProvider.on("notification", (notif: any) => { this.sendNotification(JSON.stringify(notif)); }); diff --git a/packages/extension/src/providers/solana/inject.ts b/packages/extension/src/providers/solana/inject.ts index 805268e7e..0bbcf7969 100644 --- a/packages/extension/src/providers/solana/inject.ts +++ b/packages/extension/src/providers/solana/inject.ts @@ -9,7 +9,7 @@ import { SendMessageHandler, } from "@/types/provider"; import { EnkryptWindow } from "@/types/globals"; -import { BitcoinNetworks } from "./types"; +import { SolanaNetwork } from "./types/sol-network"; import { InternalMethods } from "@/types/messenger"; import { SettingsType } from "@/libs/settings-state/types"; @@ -19,14 +19,14 @@ export class Provider extends EventEmitter implements ProviderInterface { type: ProviderType; version: string = __VERSION__; autoRefreshOnNetworkChange = false; - networks: typeof BitcoinNetworks; + networks: typeof SolanaNetwork; sendMessageHandler: SendMessageHandler; constructor(options: ProviderOptions) { super(); this.connected = true; this.name = options.name; this.type = options.type; - this.networks = BitcoinNetworks; + this.networks = SolanaNetwork; this.sendMessageHandler = options.sendMessageHandler; } diff --git a/packages/extension/src/providers/solana/libs/accounts-state/index.ts b/packages/extension/src/providers/solana/libs/accounts-state/index.ts index 54a360903..c757c8fd6 100644 --- a/packages/extension/src/providers/solana/libs/accounts-state/index.ts +++ b/packages/extension/src/providers/solana/libs/accounts-state/index.ts @@ -5,7 +5,7 @@ class AccountState { #storage: BrowserStorage; constructor() { this.#storage = new BrowserStorage( - InternalStorageNamespace.bitcoinAccountsState + InternalStorageNamespace.solanaAccountsState ); } async addApprovedAddress(address: string, domain: string): Promise { diff --git a/packages/extension/src/providers/solana/libs/accounts-state/types.ts b/packages/extension/src/providers/solana/libs/accounts-state/types.ts index 768317475..69b77adda 100644 --- a/packages/extension/src/providers/solana/libs/accounts-state/types.ts +++ b/packages/extension/src/providers/solana/libs/accounts-state/types.ts @@ -1,5 +1,5 @@ export enum StorageKeys { - accountsState = "bitcoin-accounts-state", + accountsState = "solana-accounts-state", } export interface IState { approvedAccounts: string[]; diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts index 62b7a2916..c1a9d6d44 100644 --- a/packages/extension/src/providers/solana/libs/api.ts +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -38,27 +38,33 @@ class API implements ProviderAPIInterface { return true; } getTokenInfo = async (contractAddress: string): Promise => { + interface TokenDetails { + address: string; + decimals: number; + name: string; + symbol: string; + } const allTokensResponse = await cacheFetch( { url: "https://utl.solcast.dev/solana-tokenlist.json", + postProcess: (data: any) => { + const allTokens = data.tokens as TokenDetails[]; + const tObj: Record = {}; + allTokens.forEach((t) => { + tObj[t.address] = t; + }); + return tObj; + }, }, - 10 * 60 * 1000 + 60 * 60 * 1000 ); - const allTokens = allTokensResponse.tokens as { - address: string; - decimals: number; - name: string; - symbol: string; - }[]; - for (const t of allTokens) { - if (t.address === contractAddress) { - console.log(t, contractAddress); - return { - name: t.name, - symbol: t.symbol, - decimals: t.decimals, - }; - } + const allTokens = allTokensResponse as Record; + if (allTokens[contractAddress]) { + return { + name: allTokens[contractAddress].name, + symbol: allTokens[contractAddress].symbol, + decimals: allTokens[contractAddress].decimals, + }; } return { name: "Unknown", diff --git a/packages/extension/src/providers/solana/libs/message-router.ts b/packages/extension/src/providers/solana/libs/message-router.ts index e715f2807..a87a8b1a7 100644 --- a/packages/extension/src/providers/solana/libs/message-router.ts +++ b/packages/extension/src/providers/solana/libs/message-router.ts @@ -27,26 +27,6 @@ const handleIncomingMessage: handleIncomingMessageType = ( } else if (jsonMsg.method === MessageMethod.changeAddress) { const address = jsonMsg.params[0] as string; _provider.emit(EmitEvent.accountsChanged, [address]); - } else if ( - (jsonMsg.method as EnkryptProviderEventMethods) === - EnkryptProviderEventMethods.chainChanged - ) { - if ( - jsonMsg.params[0] === NetworkNames.Bitcoin || - jsonMsg.params[0] === NetworkNames.BitcoinTest - ) { - _provider - .switchNetwork( - jsonMsg.params[0] === NetworkNames.Bitcoin ? "livenet" : "testnet" - ) - .then(() => { - _provider.emit(EmitEvent.networkChanged, [ - jsonMsg.params[0] === NetworkNames.Bitcoin - ? "livenet" - : "testnet", - ]); - }); - } } } catch (e) { console.error(e); diff --git a/packages/extension/src/providers/solana/libs/utils.ts b/packages/extension/src/providers/solana/libs/utils.ts index f34045285..edb1aeda6 100644 --- a/packages/extension/src/providers/solana/libs/utils.ts +++ b/packages/extension/src/providers/solana/libs/utils.ts @@ -1,104 +1,11 @@ -import { BitcoinNetworkInfo, HaskoinUnspentType } from "../types"; -import { address as BTCAddress } from "bitcoinjs-lib"; -import { GasPriceTypes } from "@/providers/common/types"; -import { fromBase } from "@enkryptcom/utils"; -import BigNumber from "bignumber.js"; -import { BitcoinNetwork } from "../types/bitcoin-network"; -import { BTCTxInfo } from "../ui/types"; - -const isAddress = (address: string, network: BitcoinNetworkInfo): boolean => { +import { PublicKey } from "@solana/web3.js"; +const isAddress = (address: string): boolean => { try { - BTCAddress.toOutputScript(address, network); - return true; - } catch { + const addPub = new PublicKey(address); + return PublicKey.isOnCurve(addPub.toBytes()); + } catch (e) { return false; } }; -const getTxInfo = ( - utxos: HaskoinUnspentType[], - ordinalUTXO?: HaskoinUnspentType -): BTCTxInfo => { - const txInfo: BTCTxInfo = { - inputs: [], - outputs: [], - }; - utxos.forEach((u) => { - txInfo.inputs.push({ - hash: u.txid, - index: u.index, - raw: u.raw, - witnessUtxo: { - script: u.pkscript, - value: u.value, - }, - }); - }); - if (ordinalUTXO) { - txInfo.inputs.unshift({ - hash: ordinalUTXO.txid, - index: ordinalUTXO.index, - raw: ordinalUTXO.raw, - witnessUtxo: { - script: ordinalUTXO.pkscript, - value: ordinalUTXO.value, - }, - }); - } - return txInfo; -}; - -const getGasCostValues = async ( - network: BitcoinNetwork, - byteSize: number, - nativeVal = "0", - decimals: number, - currencyName: string -) => { - const fees = await network.feeHandler(); - const gasVals = { - [GasPriceTypes.FASTEST]: (byteSize * fees.FASTEST).toString(), - [GasPriceTypes.FAST]: (byteSize * fees.FAST).toString(), - [GasPriceTypes.REGULAR]: (byteSize * fees.REGULAR).toString(), - [GasPriceTypes.ECONOMY]: (byteSize * fees.ECONOMY).toString(), - }; - const getConvertedVal = (type: GasPriceTypes) => - fromBase(gasVals[type], decimals); - - const gasCostValues = { - [GasPriceTypes.ECONOMY]: { - nativeValue: getConvertedVal(GasPriceTypes.ECONOMY), - fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.ECONOMY)) - .times(nativeVal!) - .toString(), - nativeSymbol: currencyName, - fiatSymbol: "USD", - }, - [GasPriceTypes.REGULAR]: { - nativeValue: getConvertedVal(GasPriceTypes.REGULAR), - fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.REGULAR)) - .times(nativeVal!) - .toString(), - nativeSymbol: currencyName, - fiatSymbol: "USD", - }, - [GasPriceTypes.FAST]: { - nativeValue: getConvertedVal(GasPriceTypes.FAST), - fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FAST)) - .times(nativeVal!) - .toString(), - nativeSymbol: currencyName, - fiatSymbol: "USD", - }, - [GasPriceTypes.FASTEST]: { - nativeValue: getConvertedVal(GasPriceTypes.FASTEST), - fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FASTEST)) - .times(nativeVal!) - .toString(), - nativeSymbol: currencyName, - fiatSymbol: "USD", - }, - }; - return gasCostValues; -}; -export { isAddress, getGasCostValues, getTxInfo }; +export { isAddress }; diff --git a/packages/extension/src/providers/solana/methods/btc_getNetwork.ts b/packages/extension/src/providers/solana/methods/btc_getNetwork.ts deleted file mode 100644 index 9bb201b7b..000000000 --- a/packages/extension/src/providers/solana/methods/btc_getNetwork.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { getCustomError } from "@/libs/error"; -import { MiddlewareFunction, NetworkNames } from "@enkryptcom/types"; -import BitcoinProvider from ".."; -import AccountState from "../libs/accounts-state"; -import { ProviderRPCRequest } from "@/types/provider"; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_getNetwork") return next(); - else { - if (!payload.options || !payload.options.domain) { - return res(getCustomError("btc_getNetwork: invalid domain")); - } - - const accountsState = new AccountState(); - - accountsState - .getApprovedAddresses(payload.options!.domain) - .then((accounts) => { - if (!accounts.length) { - return res(null, ""); - } - if (this.network.name === NetworkNames.Bitcoin) - return res(null, "livenet"); - if (this.network.name === NetworkNames.BitcoinTest) - return res(null, "testnet"); - res(null, ""); - }); - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts b/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts deleted file mode 100644 index f47542501..000000000 --- a/packages/extension/src/providers/solana/methods/btc_getPublicKey.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { MiddlewareFunction } from "@enkryptcom/types"; -import { ProviderRPCRequest } from "@/types/provider"; -import AccountState from "../libs/accounts-state"; -import { getCustomError } from "@/libs/error"; -import BitcoinProvider from ".."; - -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_getPublicKey") return next(); - else { - if (payload.options && payload.options.domain) { - const accountsState = new AccountState(); - accountsState - .getApprovedAddresses(payload.options.domain) - .then((accounts) => { - if (accounts.length) { - this.KeyRing.getAccount(accounts[0]).then((pubAccounts) => { - res(null, pubAccounts.address.replace("0x", "")); - }); - } else { - res(null, ""); - } - }); - } else { - res(getCustomError("No domain set!")); - } - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts b/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts deleted file mode 100644 index dcba6d17f..000000000 --- a/packages/extension/src/providers/solana/methods/btc_requestAccounts.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { CallbackFunction, MiddlewareFunction } from "@enkryptcom/types"; -import type BitcoinProvider from ".."; -import { ProviderRPCRequest } from "@/types/provider"; -import { WindowPromise } from "@/libs/window-promise"; -import AccountState from "../libs/accounts-state"; -import { getCustomError } from "@/libs/error"; -let isAccountAccessPending = false; -const pendingPromises: { - payload: ProviderRPCRequest; - res: CallbackFunction; -}[] = []; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_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 accountsState = new AccountState(); - accountsState - .getApprovedAddresses(_payload.options.domain) - .then((accounts) => { - if (accounts.length) { - _res(null, [ - accounts.map((acc) => this.network.displayAddress(acc))[0], - ]); - handleRemainingPromises(); - } else { - const windowPromise = new WindowPromise(); - windowPromise - .getResponse( - this.getUIPath(this.UIRoutes.btcConnectDApp.path), - JSON.stringify({ - ..._payload, - params: [this.network.name], - }) - ) - .then(({ error, result }) => { - if (error) _res(error as any); - const accounts = JSON.parse(result || "[]"); - _res( - null, - accounts.map((acc: string) => - this.network.displayAddress(acc) - ) - ); - }) - .finally(handleRemainingPromises); - } - }); - } else { - _res(getCustomError("No domain set!")); - } - }; - handleAccountAccess(payload, res); - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_signMessage.ts b/packages/extension/src/providers/solana/methods/btc_signMessage.ts deleted file mode 100644 index aa555b97a..000000000 --- a/packages/extension/src/providers/solana/methods/btc_signMessage.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getCustomError } from "@/libs/error"; -import { MiddlewareFunction } from "@enkryptcom/types"; -import BitcoinProvider from ".."; -import { WindowPromise } from "@/libs/window-promise"; -import { ProviderRPCRequest } from "@/types/provider"; -import AccountState from "../libs/accounts-state"; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_signMessage") return next(); - else { - if (!payload.params || payload.params.length < 2) { - return res(getCustomError("btc_signMessage: invalid params")); - } - if (!payload.options || !payload.options.domain) { - return res(getCustomError("btc_signMessage: invalid domain")); - } - const msg = payload.params[0] as string; - const type = payload.params[1] as string; - const accountsState = new AccountState(); - - accountsState - .getApprovedAddresses(payload.options!.domain) - .then((accounts) => { - if (!accounts.length) { - return res(null, ""); - } - this.KeyRing.getAccount(accounts[0]).then((acc) => { - if (!acc) - return res(getCustomError("btc_signMessage: account not found")); - const windowPromise = new WindowPromise(); - windowPromise - .getResponse( - this.getUIPath(this.UIRoutes.btcSign.path), - JSON.stringify({ - ...payload, - params: [msg, type, acc, this.network.name], - }), - true - ) - .then(({ error, result }) => { - if (error) return res(error); - res(null, JSON.parse(result as string)); - }); - }); - }); - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_signPsbt.ts b/packages/extension/src/providers/solana/methods/btc_signPsbt.ts deleted file mode 100644 index c63e52815..000000000 --- a/packages/extension/src/providers/solana/methods/btc_signPsbt.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getCustomError } from "@/libs/error"; -import { MiddlewareFunction } from "@enkryptcom/types"; -import BitcoinProvider from ".."; -import { WindowPromise } from "@/libs/window-promise"; -import { SignPSBTOptions } from "../types"; -import AccountState from "../libs/accounts-state"; -import { ProviderRPCRequest } from "@/types/provider"; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_signPsbt") return next(); - else { - if (!payload.params || payload.params.length < 2) { - return res( - getCustomError("btc_signPsbt: invalid request not enough params") - ); - } - if (!payload.options || !payload.options.domain) { - return res(getCustomError("btc_signPsbt: invalid domain")); - } - const psbt = payload.params[0] as string; - const options = payload.params[1] as SignPSBTOptions; - const accountsState = new AccountState(); - - accountsState - .getApprovedAddresses(payload.options!.domain) - .then((accounts) => { - if (!accounts.length) { - return res(null, ""); - } - this.KeyRing.getAccount(accounts[0]).then((acc) => { - if (!acc) - return res(getCustomError("btc_signPsbt: account not found")); - const windowPromise = new WindowPromise(); - windowPromise - .getResponse( - this.getUIPath(this.UIRoutes.btcSendTransaction.path), - JSON.stringify({ - ...payload, - params: [psbt, options, acc, this.network.name], - }), - true - ) - .then(({ error, result }) => { - if (error) return res(error); - res(null, JSON.parse(result as string)); - }); - }); - }); - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts b/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts deleted file mode 100644 index a272e19d0..000000000 --- a/packages/extension/src/providers/solana/methods/btc_switchNetwork.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getCustomError } from "@/libs/error"; -import { sendToBackgroundFromBackground } from "@/libs/messenger/extension"; -import { InternalMethods } from "@/types/messenger"; -import { ProviderRPCRequest } from "@/types/provider"; -import { MiddlewareFunction } from "@enkryptcom/types"; -import BTCNetworks from "../networks"; -import DomainState from "@/libs/domain-state"; -import BitcoinProvider from ".."; -import { BitcoinNetworks } from "../types"; -import { trackNetworkSelected } from "@/libs/metrics"; -import { NetworkChangeEvents } from "@/libs/metrics/types"; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_switchNetwork") return next(); - else { - if ( - !payload.params || - payload.params.length < 1 || - !Object.keys(BitcoinNetworks).includes(payload.params[0]) - ) { - return res(getCustomError("btc_switchNetwork: invalid params")); - } - const internalName = - BitcoinNetworks[payload.params![0] as keyof typeof BitcoinNetworks]; - const allNetworks = Object.values(BTCNetworks); - const validNetwork = allNetworks.find((net) => net.name === internalName); - if (validNetwork) { - trackNetworkSelected(NetworkChangeEvents.NetworkChangeAPI, { - provider: validNetwork.provider, - network: validNetwork.name, - }); - sendToBackgroundFromBackground({ - message: JSON.stringify({ - method: InternalMethods.changeNetwork, - params: [validNetwork.name], - }), - provider: validNetwork.provider, - tabId: payload.options?.tabId, - }).then(() => { - const domainState = new DomainState(); - domainState - .setSelectedNetwork(validNetwork.name) - .then(() => res(null, true)); - }); - } else { - return res( - getCustomError( - `btc_switchNetwork: porvided network ${ - payload.params![0] - } not supported` - ) - ); - } - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/index.ts b/packages/extension/src/providers/solana/methods/index.ts index f2818c463..07e8bab18 100644 --- a/packages/extension/src/providers/solana/methods/index.ts +++ b/packages/extension/src/providers/solana/methods/index.ts @@ -1,16 +1,3 @@ -import btcRequestAccounts from "./btc_requestAccounts"; -import btcSignMessage from "./btc_signMessage"; import btcGetBalance from "./btc_getBalance"; -import btcSwitchNetwork from "./btc_switchNetwork"; -import btcGetPublicKey from "./btc_getPublicKey"; -import btcSignPsbt from "./btc_signPsbt"; -import btcGetNetwork from "./btc_getNetwork"; -export default [ - btcRequestAccounts, - btcSignMessage, - btcGetBalance, - btcSwitchNetwork, - btcGetPublicKey, - btcSignPsbt, - btcGetNetwork, -]; + +export default [btcGetBalance]; diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index 1161c5955..fb335c7dc 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -14,11 +14,12 @@ import MarketData from "@/libs/market-data"; import BigNumber from "bignumber.js"; import { CoinGeckoTokenMarket } from "@/libs/market-data/types"; import Sparkline from "@/libs/sparkline"; -import { SOLToken } from "./sol-token"; +import { SOLToken, SolTokenOptions } from "./sol-token"; import { NFTCollection } from "@/types/nft"; import { fromBase, hexToBuffer } from "@enkryptcom/utils"; import bs58 from "bs58"; -import getBalances from "@/providers/ethereum/libs/assets-handlers/solanachain"; +import { NATIVE_TOKEN_ADDRESS } from "@/providers/ethereum/libs/common"; +import { isAddress as isSolanaAddress } from "../libs/utils"; export interface SolanaNetworkOptions { name: NetworkNames; @@ -91,7 +92,7 @@ export class SolanaNetwork extends BaseNetwork { public async getAllTokens(pubkey: string): Promise { const assets = await this.getAllTokenInfo(pubkey); return assets.map((token) => { - const bTokenOptions: BaseTokenOptions = { + const bTokenOptions: SolTokenOptions = { decimals: token.decimals, icon: token.icon, name: token.name, @@ -99,6 +100,7 @@ export class SolanaNetwork extends BaseNetwork { balance: token.balance, price: token.value, coingeckoID: this.coingeckoID, + contract: NATIVE_TOKEN_ADDRESS, }; return new SOLToken(bTokenOptions); }); @@ -147,4 +149,6 @@ export class SolanaNetwork extends BaseNetwork { public getAllActivity(address: string): Promise { return this.activityHandler(this, address); } + + public isAddress = isSolanaAddress; } diff --git a/packages/extension/src/providers/solana/types/sol-token.ts b/packages/extension/src/providers/solana/types/sol-token.ts index 1d20938d5..636b7befa 100644 --- a/packages/extension/src/providers/solana/types/sol-token.ts +++ b/packages/extension/src/providers/solana/types/sol-token.ts @@ -1,9 +1,15 @@ import { BaseToken, BaseTokenOptions } from "@/types/base-token"; import SolanaAPI from "@/providers/bitcoin/libs/api"; +export interface SolTokenOptions extends BaseTokenOptions { + contract: string; +} + export class SOLToken extends BaseToken { - constructor(options: BaseTokenOptions) { + contract: string; + constructor(options: SolTokenOptions) { super(options); + this.contract = options.contract; } public async getLatestUserBalance( diff --git a/packages/extension/src/providers/solana/ui/index.ts b/packages/extension/src/providers/solana/ui/index.ts index 865942ef0..5b39a0ec8 100644 --- a/packages/extension/src/providers/solana/ui/index.ts +++ b/packages/extension/src/providers/solana/ui/index.ts @@ -1,7 +1,7 @@ import { ProviderName, UIExportOptions } from "@/types/provider"; import getRoutes from "./routes"; const uiExport: UIExportOptions = { - providerName: ProviderName.bitcoin, - routes: getRoutes(ProviderName.bitcoin), + providerName: ProviderName.solana, + routes: getRoutes(ProviderName.solana), }; export default uiExport; diff --git a/packages/extension/src/providers/solana/ui/libs/signer.ts b/packages/extension/src/providers/solana/ui/libs/signer.ts index 161fdc71d..20ed12451 100644 --- a/packages/extension/src/providers/solana/ui/libs/signer.ts +++ b/packages/extension/src/providers/solana/ui/libs/signer.ts @@ -1,134 +1,17 @@ -import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger"; +import { InternalOnMessageResponse } from "@/types/messenger"; import { SignerTransactionOptions, SignerMessageOptions } from "../types"; -import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; -import { hexToBuffer, bufferToHex } from "@enkryptcom/utils"; -import { Psbt } from "bitcoinjs-lib"; -import { BitcoinNetwork, PaymentType } from "../../types/bitcoin-network"; -import { EnkryptAccount } from "@enkryptcom/types"; -import { signMessageOfBIP322Simple } from "../../libs/bip322-message-sign"; -import { magicHash, toCompact } from "../../libs/sign-message-utils"; - -const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => { - return { - publicKey: hexToBuffer(account.address), - network: network.networkInfo, - sign: (hash: Buffer): Promise => { - return sendUsingInternalMessengers({ - method: InternalMethods.sign, - params: [bufferToHex(hash), account], - }).then((res) => { - if (res.error) { - return Promise.reject({ - error: res.error, - }); - } else { - return hexToBuffer(JSON.parse(res.result!)).subarray(0, 64); - } - }); - }, - }; -}; +import { Psbt } from "bitcoinjs-lib"; const TransactionSigner = ( options: SignerTransactionOptions ): Promise => { - const { account, network, payload } = options; - if (account.isHardware) { - throw new Error("btc-hardware not implemented"); - } else { - const tx = new Psbt({ - network: network.networkInfo, - maximumFeeRate: network.networkInfo.maxFeeRate, - }); - payload.inputs - .map((u) => { - const res: { - hash: string; - index: number; - witnessUtxo?: { script: Buffer; value: number }; - nonWitnessUtxo?: Buffer; - } = { - hash: u.hash, - index: u.index, - }; - if (network.networkInfo.paymentType === PaymentType.P2WPKH) { - res.witnessUtxo = { - script: Buffer.from(u.witnessUtxo.script, "hex"), - value: u.witnessUtxo.value, - }; - } else if (network.networkInfo.paymentType === PaymentType.P2PKH) { - res.nonWitnessUtxo = Buffer.from(u.raw, "hex"); - } - return res; - }) - .forEach((input) => tx.addInput(input)); - payload.outputs.forEach((output) => tx.addOutput(output)); - const signer = PSBTSigner(account, network); - return tx.signAllInputsAsync(signer).then(() => { - tx.finalizeAllInputs(); - return tx; - }); - } + throw new Error("btc-tx-signer not implemented"); }; const MessageSigner = ( options: SignerMessageOptions ): Promise => { - const { account, payload, network } = options; - if (account.isHardware) { - throw new Error("btc-hardware not implemented"); - } else { - if (options.type === "bip322-simple") { - const signer = PSBTSigner(account, network); - return signMessageOfBIP322Simple({ - address: account.address, - message: payload.toString(), - network: network, - Signer: signer, - }) - .then((sig) => { - return { - result: JSON.stringify(sig), - }; - }) - .catch((e) => { - return { - error: e.message, - }; - }); - } else { - const signer = { - sign: ( - hash: Buffer - ): Promise<{ signature: Buffer; recovery: number }> => { - return sendUsingInternalMessengers({ - method: InternalMethods.sign, - params: [bufferToHex(hash), account], - }).then((res) => { - if (res.error) { - return Promise.reject({ - error: res.error, - }); - } else { - const sigBuffer = hexToBuffer(JSON.parse(res.result!)); - return { - signature: sigBuffer.subarray(0, 64), - recovery: sigBuffer[64], - }; - } - }); - }, - }; - const mHash = magicHash(payload); - return signer.sign(mHash).then((sig) => { - return { - result: JSON.stringify( - toCompact(sig.recovery, sig.signature, true).toString("base64") - ), - }; - }); - } - } + throw new Error("btc-msg-signer not implemented"); }; -export { TransactionSigner, MessageSigner, PSBTSigner }; +export { TransactionSigner, MessageSigner }; diff --git a/packages/extension/src/providers/solana/ui/libs/tx-size.ts b/packages/extension/src/providers/solana/ui/libs/tx-size.ts deleted file mode 100644 index 4dcb707eb..000000000 --- a/packages/extension/src/providers/solana/ui/libs/tx-size.ts +++ /dev/null @@ -1,260 +0,0 @@ -// https://github.com/jlopp/bitcoin-transaction-size-calculator/blob/master/index.html - -import { toBN } from "web3-utils"; -import { PaymentType } from "../../types/bitcoin-network"; - -enum InputScriptType { - P2PKH = "P2PKH", - P2SH = "P2SH", - "P2SH-P2WPKH" = "P2SH-P2WPKH", - "P2SH-P2WSH" = "P2SH-P2WSH", - P2WPKH = "P2WPKH", - P2WSH = "P2WSH", - P2TR = "P2TR", -} -const P2PKH_IN_SIZE = 148; -const P2PKH_OUT_SIZE = 34; - -const P2SH_OUT_SIZE = 32; -const P2SH_P2WPKH_OUT_SIZE = 32; -const P2SH_P2WSH_OUT_SIZE = 32; - -// All segwit input sizes are reduced by 1 WU to account for the witness item counts being added for every input per the transaction header -const P2SH_P2WPKH_IN_SIZE = 90.75; - -const P2WPKH_IN_SIZE = 67.75; -const P2WPKH_OUT_SIZE = 31; - -const P2WSH_OUT_SIZE = 43; -const P2TR_OUT_SIZE = 43; - -const P2TR_IN_SIZE = 57.25; - -const PUBKEY_SIZE = 33; -const SIGNATURE_SIZE = 72; - -const getSizeOfVarInt = (length: number) => { - if (length < 253) { - return 1; - } else if (length < 65535) { - return 3; - } else if (length < 4294967295) { - return 5; - } else if (length < toBN("18446744073709551615").toNumber()) { - return 9; - } else { - alert("Invalid var int"); - } -}; - -const getSizeOfScriptLengthElement = (length: number) => { - if (length < 75) { - return 1; - } else if (length <= 255) { - return 2; - } else if (length <= 65535) { - return 3; - } else if (length <= 4294967295) { - return 5; - } else { - alert("Size of redeem script is too large"); - } -}; - -const getTxOverheadExtraRawBytes = ( - input_script: InputScriptType, - input_count: number -) => { - let witness_bytes = 0; - // Returns the remaining 3/4 bytes per witness bytes - if ( - input_script !== InputScriptType.P2PKH && - input_script !== InputScriptType.P2SH - ) { - // Transactions with segwit inputs have extra overhead - witness_bytes = - 0.25 + // segwit marker - 0.25 + // segwit flag - input_count / 4; // witness element count per input - } - return witness_bytes * 3; -}; - -const getTxOverheadVBytes = ( - input_script: InputScriptType, - input_count: number, - output_count: number -) => { - let witness_vbytes = 0; - if ( - input_script != InputScriptType.P2PKH && - input_script != InputScriptType.P2SH - ) { - // Transactions with segwit inputs have extra overhead - witness_vbytes = - 0.25 + // segwit marker - 0.25 + // segwit flag - input_count / 4; // witness element count per input - } - return ( - 4 + // nVersion - getSizeOfVarInt(input_count)! + // number of inputs - getSizeOfVarInt(output_count)! + // number of outputs - 4 + // nLockTime - witness_vbytes - ); -}; - -interface calcInputType { - input_script?: InputScriptType; - input_n?: number; - input_m?: number; - input_count: number; -} -interface calcOutputType { - p2pkh_output_count?: number; - p2sh_output_count?: number; - p2sh_p2wpkh_output_count?: number; - p2sh_p2wsh_output_count?: number; - p2wpkh_output_count?: number; - p2wsh_output_count?: number; - p2tr_output_count?: number; -} -const calculateSize = ( - inputOptions: calcInputType, - outputOptions: calcOutputType -) => { - const defaultInputOptions = { - input_script: InputScriptType.P2WPKH, - input_m: 1, - input_n: 1, - }; - const defaultOutputOptions = { - p2pkh_output_count: 0, - p2sh_output_count: 0, - p2sh_p2wpkh_output_count: 0, - p2sh_p2wsh_output_count: 0, - p2wpkh_output_count: 0, - p2wsh_output_count: 0, - p2tr_output_count: 0, - }; - const _inputOptions = { ...defaultInputOptions, ...inputOptions }; - const _outputOptions = { ...defaultOutputOptions, ...outputOptions }; - const { input_script, input_n, input_m, input_count } = _inputOptions; - const { - p2pkh_output_count, - p2sh_output_count, - p2sh_p2wpkh_output_count, - p2sh_p2wsh_output_count, - p2wpkh_output_count, - p2wsh_output_count, - p2tr_output_count, - } = _outputOptions; - - const output_count = - p2pkh_output_count + - p2sh_output_count + - p2sh_p2wpkh_output_count + - p2sh_p2wsh_output_count + - p2wpkh_output_count + - p2wsh_output_count + - p2tr_output_count; - // In most cases the input size is predictable. For multisig inputs we need to perform a detailed calculation - let inputSize = 0; // in virtual bytes - let inputWitnessSize = 0; - let redeemScriptSize = 0; - let scriptSigSize = 0; - switch (input_script) { - case "P2PKH": - inputSize = P2PKH_IN_SIZE; - break; - case "P2SH-P2WPKH": - inputSize = P2SH_P2WPKH_IN_SIZE; - inputWitnessSize = 107; // size(signature) + signature + size(pubkey) + pubkey - break; - case "P2WPKH": - inputSize = P2WPKH_IN_SIZE; - inputWitnessSize = 107; // size(signature) + signature + size(pubkey) + pubkey - break; - case "P2TR": // Only consider the cooperative taproot signing path; assume multisig is done via aggregate signatures - inputSize = P2TR_IN_SIZE; - inputWitnessSize = 65; // getSizeOfVarInt(schnorrSignature) + schnorrSignature; - break; - case "P2SH": - redeemScriptSize = - 1 + // OP_M - input_n * (1 + PUBKEY_SIZE) + // OP_PUSH33 - 1 + // OP_N - 1; // OP_CHECKMULTISIG - scriptSigSize = - 1 + // size(0) - input_m * (1 + SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature - getSizeOfScriptLengthElement(redeemScriptSize)! + - redeemScriptSize; - inputSize = 32 + 4 + getSizeOfVarInt(scriptSigSize)! + scriptSigSize + 4; - break; - case "P2SH-P2WSH": - case "P2WSH": - redeemScriptSize = - 1 + // OP_M - input_n * (1 + PUBKEY_SIZE) + // OP_PUSH33 - 1 + // OP_N - 1; // OP_CHECKMULTISIG - inputWitnessSize = - 1 + // size(0) - input_m * (1 + SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature - getSizeOfScriptLengthElement(redeemScriptSize)! + - redeemScriptSize; - inputSize = - 36 + // outpoint (spent UTXO ID) - inputWitnessSize / 4 + // witness program - 4; // nSequence - if (input_script == "P2SH-P2WSH") { - inputSize += 32 + 3; // P2SH wrapper (redeemscript hash) + overhead? - } - } - const txVBytes = - getTxOverheadVBytes(input_script, input_count, output_count) + - inputSize * input_count + - P2PKH_OUT_SIZE * p2pkh_output_count + - P2SH_OUT_SIZE * p2sh_output_count + - P2SH_P2WPKH_OUT_SIZE * p2sh_p2wpkh_output_count + - P2SH_P2WSH_OUT_SIZE * p2sh_p2wsh_output_count + - P2WPKH_OUT_SIZE * p2wpkh_output_count + - P2WSH_OUT_SIZE * p2wsh_output_count + - P2TR_OUT_SIZE * p2tr_output_count; - const txBytes = - getTxOverheadExtraRawBytes(input_script, input_count)! + - txVBytes + - (inputWitnessSize * input_count * 3) / 4; - const txWeight = txVBytes * 4; - return { - txVBytes, - txBytes, - txWeight, - }; -}; -const calculateSizeBasedOnType = ( - numInputs: number, - numOutputs: number, - type: PaymentType -): number => { - const output: calcOutputType = {}; - if (type === PaymentType.P2PKH) { - output.p2pkh_output_count = numOutputs; - } else { - output.p2wpkh_output_count = numOutputs; - } - const size = calculateSize( - { - input_script: - type === PaymentType.P2PKH - ? InputScriptType.P2PKH - : InputScriptType.P2WPKH, - input_count: numInputs, - }, - output - ); - return type === PaymentType.P2PKH ? size.txBytes : size.txVBytes; -}; -export { InputScriptType, calculateSize, calculateSizeBasedOnType }; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue index 9e39d9328..c7ee00680 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue @@ -2,8 +2,8 @@
@@ -17,7 +17,7 @@ :disabled="disableDirectInput" placeholder="address" :style="{ - color: !isAddress(btcAddress, network.networkInfo) ? 'red' : 'black', + color: !network.isAddress(solAddress) ? 'red' : 'black', }" @focus="changeFocus" @blur="changeFocus" @@ -30,8 +30,7 @@ import { replaceWithEllipsis } from "@/ui/action/utils/filters"; import { computed } from "@vue/reactivity"; import { PropType, ref } from "vue"; -import { isAddress } from "@/providers/bitcoin/libs/utils"; -import { BitcoinNetwork } from "@/providers/bitcoin/types/bitcoin-network"; +import { SolanaNetwork } from "@/providers/solana/types/sol-network"; const isFocus = ref(false); const addressInput = ref(); @@ -50,7 +49,7 @@ const props = defineProps({ }, }, network: { - type: Object as PropType, + type: Object as PropType, default: () => ({}), }, from: { @@ -63,16 +62,15 @@ const emit = defineEmits<{ (e: "update:inputAddress", address: string): void; (e: "toggle:showContacts", show: boolean): void; }>(); -const btcAddress = computed(() => { - if (props.value && props.value.length > 66) - return props.network.displayAddress(props.value); +const solAddress = computed(() => { + if (props.value) return props.network.displayAddress(props.value); else return props.value; }); const address = computed({ get: () => isFocus.value - ? btcAddress.value - : replaceWithEllipsis(btcAddress.value, 6, 6), + ? solAddress.value + : replaceWithEllipsis(solAddress.value, 6, 6), set: (value) => emit("update:inputAddress", value), }); diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue deleted file mode 100644 index 9f33862f8..000000000 --- a/packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - - - diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue index b86d1f202..ac2d5a4f1 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue @@ -1,7 +1,7 @@ diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue index 081c6d6fb..255476b03 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -12,6 +12,13 @@
+

+ {{ errorMsg }} +

Double check the information and confirm transaction

@@ -27,13 +34,12 @@ :network="network" /> - {{ errorMsg }} @@ -86,15 +92,20 @@ import HardwareWalletMsg from "@/providers/common/ui/verify-transaction/hardware import SendProcess from "@action/views/send-process/index.vue"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import { VerifyTransactionParams } from "../../types"; +import Transaction from "@/providers/ethereum/libs/transaction"; +import Web3Eth from "web3-eth"; import { getCurrentContext } from "@/libs/messenger/extension"; -import { DEFAULT_BTC_NETWORK, getNetworkByName } from "@/libs/utils/networks"; +import { DEFAULT_EVM_NETWORK, getNetworkByName } from "@/libs/utils/networks"; import { TransactionSigner } from "../../libs/signer"; import { ActivityStatus, Activity, ActivityType } from "@/types/activity"; import ActivityState from "@/libs/activity-state"; import { EnkryptAccount } from "@enkryptcom/types"; import CustomScrollbar from "@action/components/custom-scrollbar/index.vue"; -import { BitcoinNetwork } from "@/providers/bitcoin/types/bitcoin-network"; -import BitcoinAPI from "@/providers/bitcoin/libs/api"; +import broadcastTx from "@/providers/ethereum/libs/tx-broadcaster"; +import { BaseNetwork } from "@/types/base-network"; +import { bigIntToHex } from "@ethereumjs/util"; +import { toBN } from "web3-utils"; +import { bufferToHex, toBase } from "@enkryptcom/utils"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; @@ -107,7 +118,7 @@ const txData: VerifyTransactionParams = JSON.parse( ); const isNft = ref(txData.isNFT); const isProcessing = ref(false); -const network = ref(DEFAULT_BTC_NETWORK); +const network = ref(DEFAULT_EVM_NETWORK); const isSendDone = ref(false); const account = ref(); const isPopup: boolean = getCurrentContext() === "new-window"; @@ -116,7 +127,7 @@ const isWindowPopup = ref(false); const errorMsg = ref(""); defineExpose({ verifyScrollRef }); onBeforeMount(async () => { - network.value = (await getNetworkByName(selectedNetwork)!) as BitcoinNetwork; + network.value = (await getNetworkByName(selectedNetwork))!; trackSendEvents(SendEventType.SendVerify, { network: network.value.name }); account.value = await KeyRing.getAccount(txData.fromAddress); isWindowPopup.value = account.value.isHardware; @@ -134,8 +145,11 @@ const sendAction = async () => { trackSendEvents(SendEventType.SendApprove, { network: network.value.name, }); + const web3 = new Web3Eth(network.value.node); + const tx = new Transaction(txData.TransactionData, web3); + const txActivity: Activity = { - from: network.value.displayAddress(txData.fromAddress), + from: txData.fromAddress, to: txData.toAddress, isIncoming: txData.fromAddress === txData.toAddress, network: network.value.name, @@ -155,61 +169,80 @@ const sendAction = async () => { transactionHash: "", }; const activityState = new ActivityState(); - const api = (await network.value.api()) as BitcoinAPI; - TransactionSigner({ - account: account.value!, - network: network.value as BitcoinNetwork, - payload: JSON.parse(txData.TxInfo), - }) - .then((signedTx) => { - api - .broadcastTx(signedTx.extractTransaction().toHex()) - .then(() => { - trackSendEvents(SendEventType.SendComplete, { - network: network.value.name, - }); - activityState.addActivities( - [ - { - ...txActivity, - ...{ transactionHash: signedTx.extractTransaction().getId() }, - }, - ], + await tx + .getFinalizedTransaction({ + gasPriceType: txData.gasPriceType, + totalGasPrice: toBN( + toBase(txData.gasFee.nativeValue, network.value.decimals) + ), + }) + .then(async (finalizedTx) => { + const onHash = (hash: string) => { + trackSendEvents(SendEventType.SendComplete, { + network: network.value.name, + }); + activityState.addActivities( + [ { - address: network.value.displayAddress(txData.fromAddress), - network: network.value.name, - } - ); - isSendDone.value = true; - if (getCurrentContext() === "popup") { - setTimeout(() => { - isProcessing.value = false; - router.go(-2); - }, 4500); - } else { - setTimeout(() => { - isProcessing.value = false; - window.close(); - }, 1500); - } + ...txActivity, + ...{ + transactionHash: hash, + nonce: bigIntToHex(finalizedTx.nonce), + }, + }, + ], + { address: txData.fromAddress, network: network.value.name } + ); + isSendDone.value = true; + if (getCurrentContext() === "popup") { + setTimeout(() => { + isProcessing.value = false; + router.go(-2); + }, 4500); + } else { + setTimeout(() => { + isProcessing.value = false; + window.close(); + }, 1500); + } + }; + TransactionSigner({ + account: account.value!, + network: network.value, + payload: finalizedTx, + }) + .then((signedTx) => { + broadcastTx(bufferToHex(signedTx.serialize()), network.value.name) + .then(onHash) + .catch(() => { + web3 + .sendSignedTransaction(bufferToHex(signedTx.serialize())) + .on("transactionHash", onHash) + .on("error", (error: any) => { + txActivity.status = ActivityStatus.failed; + activityState.addActivities([txActivity], { + address: txData.fromAddress, + network: network.value.name, + }); + isProcessing.value = false; + errorMsg.value = error.message; + trackSendEvents(SendEventType.SendFailed, { + network: network.value.name, + error: errorMsg.value, + }); + console.error("ERROR", error); + }); + }); }) - .catch((error) => { + .catch((e) => { + isProcessing.value = false; + errorMsg.value = e.error ? e.error.message : e.message; trackSendEvents(SendEventType.SendFailed, { network: network.value.name, - error: error.message, + error: errorMsg.value, }); - txActivity.status = ActivityStatus.failed; - activityState.addActivities([txActivity], { - address: txData.fromAddress, - network: network.value.name, - }); - console.error("ERROR", error); + console.error(e); }); - }) - .catch((error) => { - isProcessing.value = false; - console.error("error", error); - errorMsg.value = JSON.stringify(error); }); }; const isHasScroll = () => { @@ -236,6 +269,7 @@ const isHasScroll = () => { &.popup { box-shadow: none; + padding: 0 23px; } } @@ -282,7 +316,7 @@ const isHasScroll = () => { font-size: 16px; line-height: 24px; color: @secondaryLabel; - padding: 4px 141px 16px 32px; + padding: 4px 141px 13px 32px; margin: 0; &.popup { @@ -306,7 +340,7 @@ const isHasScroll = () => { position: absolute; left: 0; bottom: 0; - padding: 0 32px 32px 32px; + padding: 10px 32px 14px 32px; display: flex; justify-content: space-between; align-items: center; @@ -315,8 +349,10 @@ const isHasScroll = () => { box-sizing: border-box; &.popup { - padding: 24px; + padding: 24px 0; background: @white; + box-shadow: none !important; + border-top: 1px solid rgba(0, 0, 0, 0.05); } &.border { diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index 2020c148c..fd4758b8a 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -1,7 +1,7 @@ import { ToTokenData } from "@/ui/action/types/token"; import { EnkryptAccount } from "@enkryptcom/types"; import { GasPriceTypes } from "@/providers/common/types"; -import { BitcoinNetwork } from "../types/bitcoin-network"; +import { SolanaNetwork } from "../types/sol-network"; import { NFTItemWithCollectionName } from "@/types/nft"; export interface GasFeeInfo { @@ -35,13 +35,13 @@ export interface VerifyTransactionParams { export interface SignerTransactionOptions { payload: BTCTxInfo; - network: BitcoinNetwork; + network: SolanaNetwork; account: EnkryptAccount; } export interface SignerMessageOptions { payload: Buffer; - network: BitcoinNetwork; + network: SolanaNetwork; account: EnkryptAccount; type: string; } diff --git a/packages/extension/src/types/provider.ts b/packages/extension/src/types/provider.ts index 97ac31b34..fc5a55d19 100644 --- a/packages/extension/src/types/provider.ts +++ b/packages/extension/src/types/provider.ts @@ -2,6 +2,7 @@ import type { InjectedProvider as EthereumProvider } from "../providers/ethereum import type { InjectedProvider as PolkadotProvider } from "@/providers/polkadot/types"; 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 EventEmitter from "eventemitter3"; import { MiddlewareFunction, @@ -40,6 +41,7 @@ export enum InternalStorageNamespace { substrateAccountsState = "SubstrateAccountsState", bitcoinAccountsState = "BitcoinAccountsState", kadenaAccountsState = "KadenaAccountsState", + solanaAccountsState = "SolanaAccountsState", activityState = "ActivityState", marketData = "MarketData", cacheFetch = "CacheFetch", @@ -148,7 +150,8 @@ export type Provider = | EthereumProvider | PolkadotProvider | BitcoinProvider - | KadenaProvider; + | KadenaProvider + | SolanaProvider; export interface ProviderRequestOptions { url: string; 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 f3175ac8e..104eb202d 100644 --- a/packages/extension/src/ui/action/views/send-transaction/index.vue +++ b/packages/extension/src/ui/action/views/send-transaction/index.vue @@ -11,6 +11,7 @@ import SendTransactionSubstrate from "@/providers/polkadot/ui/send-transaction/i import SendTransactionEVM from "@/providers/ethereum/ui/send-transaction/index.vue"; 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 { useRoute } from "vue-router"; import { ProviderName } from "@/types/provider"; import { getNetworkByName } from "@/libs/utils/networks"; @@ -23,6 +24,7 @@ const sendLayouts: Record = { [ProviderName.polkadot]: SendTransactionSubstrate, [ProviderName.bitcoin]: SendTransactionBTC, [ProviderName.kadena]: SendTransactionKadena, + [ProviderName.solana]: SendTransactionSolana, [ProviderName.enkrypt]: null, }; From 65927c85f2a5f047709987415fef71565f3d8519 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:41:43 -0700 Subject: [PATCH 07/20] devop: more solana additions --- .../src/providers/solana/types/sol-network.ts | 2 +- .../src/providers/solana/ui/libs/signer.ts | 17 - .../components/send-fee-select.vue | 133 +++++ .../solana/ui/send-transaction/index.vue | 528 ++++++++---------- .../verify-transaction/index.vue | 159 +++--- .../src/providers/solana/ui/types.ts | 36 +- .../action/views/verify-transaction/index.vue | 2 + 7 files changed, 442 insertions(+), 435 deletions(-) delete mode 100644 packages/extension/src/providers/solana/ui/libs/signer.ts create mode 100644 packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index fb335c7dc..b6d0dc264 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -1,7 +1,7 @@ import { BaseNetwork, BaseNetworkOptions } from "@/types/base-network"; import SolAPI from "@/providers/solana/libs/api"; import { AssetsType } from "@/types/provider"; -import { BaseToken, BaseTokenOptions } from "@/types/base-token"; +import { BaseToken } from "@/types/base-token"; import { ProviderName } from "@/types/provider"; import { NetworkNames, SignerType } from "@enkryptcom/types"; import createIcon from "../libs/blockies"; diff --git a/packages/extension/src/providers/solana/ui/libs/signer.ts b/packages/extension/src/providers/solana/ui/libs/signer.ts deleted file mode 100644 index 20ed12451..000000000 --- a/packages/extension/src/providers/solana/ui/libs/signer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { InternalOnMessageResponse } from "@/types/messenger"; -import { SignerTransactionOptions, SignerMessageOptions } from "../types"; - -import { Psbt } from "bitcoinjs-lib"; -const TransactionSigner = ( - options: SignerTransactionOptions -): Promise => { - throw new Error("btc-tx-signer not implemented"); -}; - -const MessageSigner = ( - options: SignerMessageOptions -): Promise => { - throw new Error("btc-msg-signer not implemented"); -}; - -export { TransactionSigner, MessageSigner }; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue new file mode 100644 index 000000000..31fac7429 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 2fa1b8867..ca9026fbe 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -80,7 +80,7 @@ - - - + />
@@ -153,21 +135,18 @@ import SendTokenSelect from "./components/send-token-select.vue"; import SendAlert from "@/providers/common/ui/send-transaction/send-alert.vue"; import SendNftSelect from "@/providers/common/ui/send-transaction/send-nft-select.vue"; import SendInputAmount from "@/providers/common/ui/send-transaction/send-input-amount.vue"; -import SendFeeSelect from "@/providers/common/ui/send-transaction/send-fee-select.vue"; -import TransactionFeeView from "@action/views/transaction-fee/index.vue"; +import SendFeeSelect from "./components/send-fee-select.vue"; import BaseButton from "@action/components/base-button/index.vue"; import { NFTItemWithCollectionName, NFTItem, NFTType } from "@/types/nft"; import { AccountsHeaderData } from "@action/types/account"; import { numberToHex, toBN } from "web3-utils"; import { GasPriceTypes, GasFeeType } from "@/providers/common/types"; -import { SolanaNetwork } from "../../types/sol-network"; +import { SolanaNetwork, getAddress } from "../../types/sol-network"; import { SOLToken } from "../../types/sol-token"; import BigNumber from "bignumber.js"; import { defaultGasCostVals } from "@/providers/common/libs/default-vals"; -import Transaction from "@/providers/ethereum/libs/transaction"; -import Web3Eth from "web3-eth"; import { fromBase, toBase, isValidDecimals } from "@enkryptcom/utils"; -import { VerifyTransactionParams } from "../types"; +import { VerifyTransactionParams, SendTransactionDataType } from "../types"; import { formatFloatingPointValue } from "@/libs/utils/number-formatter"; import { routes as RouterNames } from "@/ui/action/router"; import getUiPath from "@/libs/utils/get-ui-path"; @@ -175,10 +154,15 @@ import Browser from "webextension-polyfill"; import { ProviderName } from "@/types/provider"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import { GenericNameResolver, CoinType } from "@/libs/name-resolver"; -import { NetworkNames } from "@enkryptcom/types"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; import { NATIVE_TOKEN_ADDRESS } from "@/providers/ethereum/libs/common"; +import { + Transaction as SolTransaction, + SystemProgram, + PublicKey, +} from "@solana/web3.js"; +import SolanaAPI from "@/providers/solana/libs/api"; const props = defineProps({ network: { @@ -198,11 +182,12 @@ const loadingAsset = new SOLToken({ price: "0", name: "loading", contract: "0x0", - decimals: 9, + decimals: props.network.decimals, }); const route = useRoute(); const router = useRouter(); +const solConnection = ref(); const nameResolver = new GenericNameResolver(); const addressInputTo = ref(); @@ -229,11 +214,7 @@ const sendAmount = computed(() => { return "0"; }); const isMaxSelected = ref(false); -const selectedFee = ref( - props.network.name === NetworkNames.Ethereum || NetworkNames.Binance - ? GasPriceTypes.REGULAR - : GasPriceTypes.ECONOMY -); +const selectedFee = ref(GasPriceTypes.ECONOMY); const gasCostValues = ref(defaultGasCostVals); const addressFrom = ref( props.accountInfo.selectedAccount?.address ?? "" @@ -251,198 +232,121 @@ const selectedNft = ref({ type: NFTType.ERC721, }); -const showMax = computed(() => { - if (selectedAsset.value.contract !== NATIVE_TOKEN_ADDRESS) return true; - return true; +const nativeBalance = computed(() => { + const accountIndex = props.accountInfo.activeAccounts.findIndex( + (acc) => acc.address === addressFrom.value + ); + if (accountIndex !== -1) { + const balance = props.accountInfo.activeBalances[accountIndex]; + if (balance !== "~") { + return toBase(balance, props.network.decimals); + } + } + return "0"; }); -// const nativeBalance = computed(() => { -// const accountIndex = props.accountInfo.activeAccounts.findIndex( -// (acc) => acc.address === addressFrom.value -// ); +onMounted(async () => { + trackSendEvents(SendEventType.SendOpen, { network: props.network.name }); + solConnection.value = (await props.network.api()).api as SolanaAPI; + fetchAssets().then(setBaseCosts); +}); -// if (accountIndex !== -1) { -// const balance = props.accountInfo.activeBalances[accountIndex]; +const TxInfo = computed(() => { + const value = sendAmount.value + ? numberToHex(toBase(sendAmount.value, props.network.decimals)) + : "0x0"; + const toAddress = addressTo.value; + return { + from: addressFrom.value as `0x{string}`, + value: value as `0x${string}`, + to: toAddress as `0x${string}`, + }; +}); -// if (balance !== "~") { -// return toBase(balance, props.network.decimals); -// } -// } +const nativeBalanceAfterTransaction = computed(() => { + if ( + isSendToken.value && + nativeBalance.value && + selectedAsset.value && + selectedAsset.value.contract && + amount.value !== "" && + isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) + ) { + let endingAmount = toBN(nativeBalance.value); + if (selectedAsset.value.contract === NATIVE_TOKEN_ADDRESS) { + const rawAmount = toBN( + toBase(amount.value, selectedAsset.value.decimals!) + ); + endingAmount = endingAmount.sub(rawAmount); + } + endingAmount = endingAmount.sub( + toBN( + toBase( + gasCostValues.value[selectedFee.value].nativeValue, + props.network.decimals + ) + ) + ); + return endingAmount; + } else if ( + !isSendToken.value && + nativeBalance.value && + selectedNft.value.id + ) { + let endingAmount = toBN(nativeBalance.value); + endingAmount = endingAmount.sub( + toBN( + toBase( + gasCostValues.value[selectedFee.value].nativeValue, + props.network.decimals + ) + ) + ); + return endingAmount; + } + return toBN(0); +}); -// return "0"; -// }); +const setTransactionFees = (tx: SolTransaction) => { + return tx + .getEstimatedFee(solConnection.value!.web3) + .then(async (fee) => { + const getConvertedVal = () => + fromBase(fee!.toString(), props.network.decimals); + const nativeVal = accountAssets.value[0].price || "0"; + gasCostValues.value[GasPriceTypes.ECONOMY] = { + nativeValue: getConvertedVal(), + fiatValue: new BigNumber(getConvertedVal()) + .times(nativeVal!) + .toString(), + nativeSymbol: props.network.currencyName, + fiatSymbol: "USD", + }; + isEstimateValid.value = true; + }) + .catch(() => { + isEstimateValid.value = false; + }); +}; -onMounted(async () => { - trackSendEvents(SendEventType.SendOpen, { network: props.network.name }); - fetchAssets(); - //.then(setBaseCosts); +const Tx = computed(() => { + const from = new PublicKey(getAddress(TxInfo.value.from)); + const transaction = new SolTransaction().add( + SystemProgram.transfer({ + fromPubkey: from, + toPubkey: TxInfo.value.to + ? new PublicKey(getAddress(TxInfo.value.to)) + : from, + lamports: toBN(TxInfo.value.value).toNumber(), + }) + ); + transaction.feePayer = from; + return transaction; }); +const setBaseCosts = async () => { + updateTransactionFees(Tx.value); +}; -// const TxInfo = computed(() => { -// const web3 = new Web3Eth(); -// const value = -// isSendToken.value && selectedAsset.value.contract === NATIVE_TOKEN_ADDRESS -// ? numberToHex(toBase(sendAmount.value, props.network.decimals)) -// : "0x0"; -// const toAddress = -// isSendToken.value && selectedAsset.value.contract === NATIVE_TOKEN_ADDRESS -// ? addressTo.value -// : isSendToken.value -// ? selectedAsset.value.contract -// : selectedNft.value.contract; -// const erc20Contract = new web3.Contract(erc20 as any); -// const erc721Contract = new web3.Contract(erc721 as any); -// const erc1155Contract = new web3.Contract(erc1155 as any); -// const data = -// isSendToken.value && selectedAsset.value.contract === NATIVE_TOKEN_ADDRESS -// ? "0x" -// : isSendToken.value -// ? erc20Contract.methods -// .transfer( -// addressTo.value, -// toBase(sendAmount.value, selectedAsset.value.decimals!) -// ) -// .encodeABI() -// : selectedNft.value.type === NFTType.ERC721 -// ? erc721Contract.methods -// .transferFrom( -// addressFrom.value, -// addressTo.value, -// selectedNft.value.id -// ) -// .encodeABI() -// : erc1155Contract.methods -// .safeTransferFrom( -// addressFrom.value, -// addressTo.value, -// selectedNft.value.id, -// 1, -// [] -// ) -// .encodeABI(); -// return { -// chainId: props.network.chainID, -// from: addressFrom.value as `0x{string}`, -// value: value as `0x${string}`, -// to: toAddress as `0x${string}`, -// data: data as `0x${string}`, -// }; -// }); -// const Tx = computed(() => { -// const web3 = new Web3Eth(props.network.node); -// const tx = new Transaction(TxInfo.value, web3); -// return tx; -// }); - -// const nativeBalanceAfterTransaction = computed(() => { -// if ( -// isSendToken.value && -// nativeBalance.value && -// selectedAsset.value && -// selectedAsset.value.contract && -// amount.value !== "" && -// isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) -// ) { -// let endingAmount = toBN(nativeBalance.value); - -// if (selectedAsset.value.contract === NATIVE_TOKEN_ADDRESS) { -// const rawAmount = toBN( -// toBase(amount.value, selectedAsset.value.decimals!) -// ); -// endingAmount = endingAmount.sub(rawAmount); -// } - -// endingAmount = endingAmount.sub( -// toBN( -// toBase( -// gasCostValues.value[selectedFee.value].nativeValue, -// props.network.decimals -// ) -// ) -// ); - -// return endingAmount; -// } else if ( -// !isSendToken.value && -// nativeBalance.value && -// selectedNft.value.id -// ) { -// let endingAmount = toBN(nativeBalance.value); -// endingAmount = endingAmount.sub( -// toBN( -// toBase( -// gasCostValues.value[selectedFee.value].nativeValue, -// props.network.decimals -// ) -// ) -// ); -// return endingAmount; -// } - -// return toBN(0); -// }); - -// const setTransactionFees = (tx: Transaction) => { -// return tx -// .getGasCosts() -// .then(async (gasvals) => { -// const getConvertedVal = (type: GasPriceTypes) => -// fromBase(gasvals[type], props.network.decimals); -// const nativeVal = accountAssets.value[0].price || "0"; -// gasCostValues.value = { -// [GasPriceTypes.ECONOMY]: { -// nativeValue: getConvertedVal(GasPriceTypes.ECONOMY), -// fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.ECONOMY)) -// .times(nativeVal!) -// .toString(), -// nativeSymbol: props.network.currencyName, -// fiatSymbol: "USD", -// }, -// [GasPriceTypes.REGULAR]: { -// nativeValue: getConvertedVal(GasPriceTypes.REGULAR), -// fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.REGULAR)) -// .times(nativeVal!) -// .toString(), -// nativeSymbol: props.network.currencyName, -// fiatSymbol: "USD", -// }, -// [GasPriceTypes.FAST]: { -// nativeValue: getConvertedVal(GasPriceTypes.FAST), -// fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FAST)) -// .times(nativeVal!) -// .toString(), -// nativeSymbol: props.network.currencyName, -// fiatSymbol: "USD", -// }, -// [GasPriceTypes.FASTEST]: { -// nativeValue: getConvertedVal(GasPriceTypes.FASTEST), -// fiatValue: new BigNumber(getConvertedVal(GasPriceTypes.FASTEST)) -// .times(nativeVal!) -// .toString(), -// nativeSymbol: props.network.currencyName, -// fiatSymbol: "USD", -// }, -// }; -// isEstimateValid.value = true; -// }) -// .catch(() => { -// isEstimateValid.value = false; -// }); -// }; - -// const setBaseCosts = () => { -// const web3 = new Web3Eth(props.network.node); -// const tx = new Transaction( -// { -// chainId: props.network.chainID, -// from: props.accountInfo.selectedAccount!.address as `0x{string}`, -// value: "0x0", -// to: NATIVE_TOKEN_ADDRESS, -// }, -// web3 -// ); -// updateTransactionFees(tx); -// }; const fetchAssets = () => { accountAssets.value = []; selectedAsset.value = loadingAsset; @@ -470,14 +374,14 @@ const sendButtonTitle = computed(() => { }); const isValidSend = computed(() => { - // if (!isInputsValid.value) return false; - // if (nativeBalanceAfterTransaction.value.isNeg()) return false; - // if (!isEstimateValid.value) return false; + if (!isInputsValid.value) return false; + if (nativeBalanceAfterTransaction.value.isNeg()) return false; + if (!isEstimateValid.value) return false; return true; }); const isInputsValid = computed(() => { - if (!props.network.isAddress(addressTo.value)) return false; + if (!props.network.isAddress(getAddress(addressTo.value))) return false; if ( isSendToken.value && !isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) @@ -488,38 +392,38 @@ const isInputsValid = computed(() => { return false; } if (new BigNumber(sendAmount.value).gt(assetMaxValue.value)) return false; - if (gasCostValues.value.REGULAR.nativeValue === "0") return false; + if (gasCostValues.value.ECONOMY.nativeValue === "0") return false; return true; }); -const updateTransactionFees = (tx: Transaction) => { +const updateTransactionFees = (tx: SolTransaction) => { if (isMaxSelected.value) { amount.value = ""; } - // setTransactionFees(tx).then(() => { - // if (isMaxSelected.value) { - // amount.value = - // parseFloat(assetMaxValue.value) < 0 ? "0" : assetMaxValue.value; - // } - // }); + solConnection.value!.web3.getLatestBlockhash().then((bhash) => { + tx.recentBlockhash = bhash.blockhash; + setTransactionFees(tx).then(() => { + if (isMaxSelected.value) { + amount.value = + parseFloat(assetMaxValue.value) < 0 ? "0" : assetMaxValue.value; + } + }); + }); }; const isOpenSelectContactFrom = ref(false); const isOpenSelectContactTo = ref(false); const isOpenSelectToken = ref(false); - -const isOpenSelectFee = ref(false); - const isOpenSelectNft = ref(false); -// watch( -// [isInputsValid, addressTo, selectedAsset, selectedNft, isSendToken], -// () => { -// if (isInputsValid.value) { -// updateTransactionFees(Tx.value); -// } -// } -// ); +watch( + [isInputsValid, addressTo, selectedAsset, selectedNft, isSendToken], + () => { + if (isInputsValid.value) { + updateTransactionFees(Tx.value); + } + } +); watch([isSendToken], () => { inputAmount("0"); @@ -558,9 +462,9 @@ const assetMaxValue = computed(() => { }); const setMaxValue = () => { isMaxSelected.value = true; - // if (isInputsValid.value) { - // updateTransactionFees(Tx.value); - // } + if (isInputsValid.value) { + updateTransactionFees(Tx.value); + } }; const inputAddressFrom = (text: string) => { addressFrom.value = text; @@ -616,74 +520,84 @@ const inputAmount = (inputAmount: string) => { const inputAmountBn = new BigNumber(inputAmount); isMaxSelected.value = false; amount.value = inputAmountBn.lt(0) ? "0" : inputAmount; - // if (isInputsValid.value) { - // updateTransactionFees(Tx.value); - // } -}; - -const toggleSelectFee = () => { - isOpenSelectFee.value = !isOpenSelectFee.value; -}; - -const selectFee = (type: GasPriceTypes) => { - selectedFee.value = type; - isOpenSelectFee.value = false; - // if (isMaxSelected.value && isInputsValid.value) - // updateTransactionFees(Tx.value); + if (isInputsValid.value) { + updateTransactionFees(Tx.value); + } }; const sendAction = async () => { - // const keyring = new PublicKeyRing(); - // const fromAccountInfo = await keyring.getAccount( - // addressFrom.value.toLowerCase() - // ); - // const txVerifyInfo: VerifyTransactionParams = { - // TransactionData: TxInfo.value, - // isNFT: !isSendToken.value, - // NFTData: !isSendToken.value ? selectedNft.value : undefined, - // toToken: { - // amount: toBase(sendAmount.value, selectedAsset.value.decimals!), - // decimals: selectedAsset.value.decimals!, - // icon: selectedAsset.value.icon as string, - // symbol: selectedAsset.value.symbol || "unknown", - // valueUSD: new BigNumber(selectedAsset.value.price || "0") - // .times(sendAmount.value) - // .toString(), - // name: selectedAsset.value.name || "", - // price: selectedAsset.value.price || "0", - // }, - // fromAddress: fromAccountInfo.address, - // fromAddressName: fromAccountInfo.name, - // gasFee: gasCostValues.value[selectedFee.value], - // gasPriceType: selectedFee.value, - // toAddress: addressTo.value, - // }; - // const routedRoute = router.resolve({ - // name: RouterNames.verify.name, - // query: { - // id: selected, - // txData: Buffer.from(JSON.stringify(txVerifyInfo), "utf8").toString( - // "base64" - // ), - // }, + const keyring = new PublicKeyRing(); + const fromAccountInfo = await keyring.getAccount( + addressFrom.value.toLowerCase() + ); + // const transaction = Tx.value; + // transaction.recentBlockhash = ( + // await solConnection.value!.web3.getLatestBlockhash() + // ).blockhash; + // const msgToSign = transaction.serializeMessage(); + // sendUsingInternalMessengers({ + // method: InternalMethods.sign, + // params: [bufferToHex(msgToSign), fromAccountInfo], + // }).then((res) => { + // if (res.error) return res; + // transaction.addSignature( + // transaction.feePayer!, + // hexToBuffer(JSON.parse(res.result!)) + // ); + // console.log(transaction.verifySignatures(true)); + // solConnection + // .value!.web3.sendRawTransaction(transaction.serialize()) + // .then((hash) => { + // console.log(`https://solscan.io/tx/${hash}`); + // }); // }); - // if (fromAccountInfo.isHardware) { - // await Browser.windows.create({ - // url: Browser.runtime.getURL( - // getUiPath( - // `eth-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`, - // ProviderName.ethereum - // ) - // ), - // type: "popup", - // focused: true, - // height: 600, - // width: 460, - // }); - // window.close(); - // } else { - // router.push(routedRoute); - // } + const txVerifyInfo: VerifyTransactionParams = { + TransactionData: TxInfo.value, + isNFT: !isSendToken.value, + NFTData: !isSendToken.value ? selectedNft.value : undefined, + toToken: { + amount: toBase(sendAmount.value, selectedAsset.value.decimals!), + decimals: selectedAsset.value.decimals!, + icon: selectedAsset.value.icon as string, + symbol: selectedAsset.value.symbol || "unknown", + valueUSD: new BigNumber(selectedAsset.value.price || "0") + .times(sendAmount.value) + .toString(), + name: selectedAsset.value.name || "", + price: selectedAsset.value.price || "0", + }, + fromAddress: fromAccountInfo.address, + fromAddressName: fromAccountInfo.name, + gasFee: gasCostValues.value[selectedFee.value], + gasPriceType: selectedFee.value, + toAddress: addressTo.value, + }; + const routedRoute = router.resolve({ + name: RouterNames.verify.name, + query: { + id: selected, + txData: Buffer.from(JSON.stringify(txVerifyInfo), "utf8").toString( + "base64" + ), + }, + }); + if (fromAccountInfo.isHardware) { + await Browser.windows.create({ + url: Browser.runtime.getURL( + getUiPath( + `eth-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`, + ProviderName.ethereum + ) + ), + type: "popup", + focused: true, + height: 600, + width: 460, + }); + window.close(); + } else { + router.push(routedRoute); + } }; const toggleSelector = (isTokenSend: boolean) => { diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue index 255476b03..59fff281f 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -92,22 +92,26 @@ import HardwareWalletMsg from "@/providers/common/ui/verify-transaction/hardware import SendProcess from "@action/views/send-process/index.vue"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import { VerifyTransactionParams } from "../../types"; -import Transaction from "@/providers/ethereum/libs/transaction"; -import Web3Eth from "web3-eth"; import { getCurrentContext } from "@/libs/messenger/extension"; import { DEFAULT_EVM_NETWORK, getNetworkByName } from "@/libs/utils/networks"; -import { TransactionSigner } from "../../libs/signer"; import { ActivityStatus, Activity, ActivityType } from "@/types/activity"; import ActivityState from "@/libs/activity-state"; import { EnkryptAccount } from "@enkryptcom/types"; import CustomScrollbar from "@action/components/custom-scrollbar/index.vue"; -import broadcastTx from "@/providers/ethereum/libs/tx-broadcaster"; import { BaseNetwork } from "@/types/base-network"; -import { bigIntToHex } from "@ethereumjs/util"; import { toBN } from "web3-utils"; -import { bufferToHex, toBase } from "@enkryptcom/utils"; +import { bufferToHex, hexToBuffer } from "@enkryptcom/utils"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; +import { + Transaction as SolTransaction, + SystemProgram, + PublicKey, +} from "@solana/web3.js"; +import { getAddress } from "@/providers/solana/types/sol-network"; +import SolanaAPI from "@/providers/solana/libs/api"; +import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; +import { InternalMethods } from "@/types/messenger"; const KeyRing = new PublicKeyRing(); const route = useRoute(); @@ -145,8 +149,15 @@ const sendAction = async () => { trackSendEvents(SendEventType.SendApprove, { network: network.value.name, }); - const web3 = new Web3Eth(network.value.node); - const tx = new Transaction(txData.TransactionData, web3); + const from = new PublicKey(getAddress(txData.TransactionData.from)); + const transaction = new SolTransaction().add( + SystemProgram.transfer({ + fromPubkey: from, + toPubkey: new PublicKey(getAddress(txData.TransactionData.to)), + lamports: toBN(txData.TransactionData.value).toNumber(), + }) + ); + transaction.feePayer = from; const txActivity: Activity = { from: txData.fromAddress, @@ -169,81 +180,69 @@ const sendAction = async () => { transactionHash: "", }; const activityState = new ActivityState(); - await tx - .getFinalizedTransaction({ - gasPriceType: txData.gasPriceType, - totalGasPrice: toBN( - toBase(txData.gasFee.nativeValue, network.value.decimals) - ), - }) - .then(async (finalizedTx) => { - const onHash = (hash: string) => { - trackSendEvents(SendEventType.SendComplete, { - network: network.value.name, - }); - activityState.addActivities( - [ - { - ...txActivity, - ...{ - transactionHash: hash, - nonce: bigIntToHex(finalizedTx.nonce), - }, + const solAPI = (await network.value.api()).api as SolanaAPI; + transaction.recentBlockhash = ( + await solAPI.web3.getLatestBlockhash() + ).blockhash; + const msgToSign = transaction.serializeMessage(); + sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [bufferToHex(msgToSign), account.value!], + }).then((res) => { + if (res.error) return res; + transaction.addSignature( + transaction.feePayer!, + hexToBuffer(JSON.parse(res.result!)) + ); + const onHash = (hash: string) => { + trackSendEvents(SendEventType.SendComplete, { + network: network.value.name, + }); + activityState.addActivities( + [ + { + ...txActivity, + ...{ + transactionHash: hash, }, - ], - { address: txData.fromAddress, network: network.value.name } - ); - isSendDone.value = true; - if (getCurrentContext() === "popup") { - setTimeout(() => { - isProcessing.value = false; - router.go(-2); - }, 4500); - } else { - setTimeout(() => { - isProcessing.value = false; - window.close(); - }, 1500); - } - }; - TransactionSigner({ - account: account.value!, - network: network.value, - payload: finalizedTx, - }) - .then((signedTx) => { - broadcastTx(bufferToHex(signedTx.serialize()), network.value.name) - .then(onHash) - .catch(() => { - web3 - .sendSignedTransaction(bufferToHex(signedTx.serialize())) - .on("transactionHash", onHash) - .on("error", (error: any) => { - txActivity.status = ActivityStatus.failed; - activityState.addActivities([txActivity], { - address: txData.fromAddress, - network: network.value.name, - }); - isProcessing.value = false; - errorMsg.value = error.message; - trackSendEvents(SendEventType.SendFailed, { - network: network.value.name, - error: errorMsg.value, - }); - console.error("ERROR", error); - }); - }); - }) - .catch((e) => { + }, + ], + { address: txData.fromAddress, network: network.value.name } + ); + isSendDone.value = true; + if (getCurrentContext() === "popup") { + setTimeout(() => { + isProcessing.value = false; + router.go(-2); + }, 4500); + } else { + setTimeout(() => { isProcessing.value = false; - errorMsg.value = e.error ? e.error.message : e.message; - trackSendEvents(SendEventType.SendFailed, { - network: network.value.name, - error: errorMsg.value, - }); - console.error(e); + window.close(); + }, 1500); + } + }; + solAPI.web3 + .sendRawTransaction(transaction.serialize()) + .then((hash) => { + onHash(hash); + console.log(`https://solscan.io/tx/${hash}`); + }) + .catch((e) => { + txActivity.status = ActivityStatus.failed; + activityState.addActivities([txActivity], { + address: txData.fromAddress, + network: network.value.name, }); - }); + isProcessing.value = false; + errorMsg.value = e.message; + trackSendEvents(SendEventType.SendFailed, { + network: network.value.name, + error: errorMsg.value, + }); + console.error("ERROR", e); + }); + }); }; const isHasScroll = () => { if (verifyScrollRef.value) { diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index fd4758b8a..d30908268 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -1,27 +1,17 @@ import { ToTokenData } from "@/ui/action/types/token"; import { EnkryptAccount } from "@enkryptcom/types"; -import { GasPriceTypes } from "@/providers/common/types"; +import { GasFeeInfo, GasPriceTypes } from "@/providers/common/types"; import { SolanaNetwork } from "../types/sol-network"; import { NFTItemWithCollectionName } from "@/types/nft"; -export interface GasFeeInfo { - nativeValue: string; - fiatValue: string; - nativeSymbol: string; - fiatSymbol: string; -} -export interface BTCTxInfo { - inputs: any[]; - outputs: { address: string; value: number }[]; -} -export interface GasFeeType { - [GasPriceTypes.ECONOMY]: GasFeeInfo; - [GasPriceTypes.REGULAR]: GasFeeInfo; - [GasPriceTypes.FAST]: GasFeeInfo; - [GasPriceTypes.FASTEST]: GasFeeInfo; +export interface SendTransactionDataType { + from: `0x${string}`; + value: `0x${string}`; + to: `0x${string}`; } export interface VerifyTransactionParams { + TransactionData: SendTransactionDataType; isNFT: boolean; NFTData?: NFTItemWithCollectionName; fromAddress: string; @@ -30,18 +20,4 @@ export interface VerifyTransactionParams { toToken: ToTokenData; gasFee: GasFeeInfo; gasPriceType: GasPriceTypes; - TxInfo: string; -} - -export interface SignerTransactionOptions { - payload: BTCTxInfo; - network: SolanaNetwork; - account: EnkryptAccount; -} - -export interface SignerMessageOptions { - payload: Buffer; - network: SolanaNetwork; - account: EnkryptAccount; - type: string; } 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 4744c7040..46d26e7a5 100644 --- a/packages/extension/src/ui/action/views/verify-transaction/index.vue +++ b/packages/extension/src/ui/action/views/verify-transaction/index.vue @@ -7,6 +7,7 @@ import VerifyTransactionSubstrate from "@/providers/polkadot/ui/send-transaction import VerifyTransactionEVM from "@/providers/ethereum/ui/send-transaction/verify-transaction/index.vue"; 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 { useRoute } from "vue-router"; import { ProviderName } from "@/types/provider"; import { getNetworkByName } from "@/libs/utils/networks"; @@ -17,6 +18,7 @@ const sendLayouts: Record = { [ProviderName.polkadot]: VerifyTransactionSubstrate, [ProviderName.bitcoin]: VerifyTransactionBTC, [ProviderName.kadena]: VerifyTransactionKadena, + [ProviderName.solana]: VerifyTransactionSolana, [ProviderName.enkrypt]: null, }; const layout = shallowRef(); From 51a6559c129f989a4a63f3ce8a69b3aa4a88857b Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:28:00 -0700 Subject: [PATCH 08/20] devop: minor changes --- packages/extension/src/providers/solana/ui/types.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index d30908268..00681a2e4 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -1,7 +1,5 @@ import { ToTokenData } from "@/ui/action/types/token"; -import { EnkryptAccount } from "@enkryptcom/types"; import { GasFeeInfo, GasPriceTypes } from "@/providers/common/types"; -import { SolanaNetwork } from "../types/sol-network"; import { NFTItemWithCollectionName } from "@/types/nft"; export interface SendTransactionDataType { @@ -20,4 +18,5 @@ export interface VerifyTransactionParams { toToken: ToTokenData; gasFee: GasFeeInfo; gasPriceType: GasPriceTypes; + TxInfo: string; } From 2da005eaef41f4d85b69d4044c4aa9d87dc85b27 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:12:10 -0700 Subject: [PATCH 09/20] devop: fix priority fees --- .../src/providers/solana/types/sol-network.ts | 3 +- .../solana/ui/libs/get-priority-fees.ts | 74 +++++++++ .../src/providers/solana/ui/routes/index.ts | 16 +- .../src/providers/solana/ui/routes/names.ts | 24 +-- .../solana/ui/send-transaction/index.vue | 146 ++++++++++++------ .../verify-transaction/index.vue | 15 +- ...-connect-dapp.vue => sol-connect-dapp.vue} | 0 ...-sign-message.vue => sol-sign-message.vue} | 0 ...saction.vue => sol-verify-transaction.vue} | 0 .../src/providers/solana/ui/types.ts | 9 +- .../action/views/assets-select-list/index.vue | 1 + 11 files changed, 211 insertions(+), 77 deletions(-) create mode 100644 packages/extension/src/providers/solana/ui/libs/get-priority-fees.ts rename packages/extension/src/providers/solana/ui/{btc-connect-dapp.vue => sol-connect-dapp.vue} (100%) rename packages/extension/src/providers/solana/ui/{btc-sign-message.vue => sol-sign-message.vue} (100%) rename packages/extension/src/providers/solana/ui/{btc-verify-transaction.vue => sol-verify-transaction.vue} (100%) diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index b6d0dc264..722f7573e 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -18,7 +18,6 @@ import { SOLToken, SolTokenOptions } from "./sol-token"; import { NFTCollection } from "@/types/nft"; import { fromBase, hexToBuffer } from "@enkryptcom/utils"; import bs58 from "bs58"; -import { NATIVE_TOKEN_ADDRESS } from "@/providers/ethereum/libs/common"; import { isAddress as isSolanaAddress } from "../libs/utils"; export interface SolanaNetworkOptions { @@ -100,7 +99,7 @@ export class SolanaNetwork extends BaseNetwork { balance: token.balance, price: token.value, coingeckoID: this.coingeckoID, - contract: NATIVE_TOKEN_ADDRESS, + contract: token.contract!, }; return new SOLToken(bTokenOptions); }); diff --git a/packages/extension/src/providers/solana/ui/libs/get-priority-fees.ts b/packages/extension/src/providers/solana/ui/libs/get-priority-fees.ts new file mode 100644 index 000000000..4705b1c9a --- /dev/null +++ b/packages/extension/src/providers/solana/ui/libs/get-priority-fees.ts @@ -0,0 +1,74 @@ +// https://docs.chainstack.com/docs/solana-estimate-priority-fees-getrecentprioritizationfees +import { Connection, PublicKey } from "@solana/web3.js"; + +interface PrioritizationFeeObject { + slot: number; + prioritizationFee: number; +} + +interface Config { + lockedWritableAccounts: PublicKey[]; +} + +const getPrioritizationFees = async ( + payer: PublicKey, + connection: Connection +): Promise<{ low: number; medium: number; high: number }> => { + try { + const config: Config = { + lockedWritableAccounts: [payer], + }; + const prioritizationFeeObjects = + (await connection.getRecentPrioritizationFees( + config + )) as PrioritizationFeeObject[]; + + if (prioritizationFeeObjects.length === 0) { + return { + low: 1000, + medium: 1500, + high: 2000, + }; + } + const averageFeeIncludingZeros = + prioritizationFeeObjects.length > 0 + ? Math.floor( + prioritizationFeeObjects.reduce( + (acc, feeObject) => acc + feeObject.prioritizationFee, + 0 + ) / prioritizationFeeObjects.length + ) + : 0; + const nonZeroFees = prioritizationFeeObjects + .map((feeObject) => feeObject.prioritizationFee) + .filter((fee) => fee !== 0); + const averageFeeExcludingZeros = + nonZeroFees.length > 0 + ? Math.floor( + nonZeroFees.reduce((acc, fee) => acc + fee, 0) / nonZeroFees.length + ) + : 0; + const sortedFees = nonZeroFees.sort((a, b) => a - b); + let medianFee = 0; + if (sortedFees.length > 0) { + const midIndex = Math.floor(sortedFees.length / 2); + medianFee = + sortedFees.length % 2 !== 0 + ? sortedFees[midIndex] + : Math.floor((sortedFees[midIndex - 1] + sortedFees[midIndex]) / 2); + } + return { + low: averageFeeIncludingZeros, + medium: medianFee, + high: averageFeeExcludingZeros, + }; + } catch (error) { + return { + low: 1000, + medium: 1500, + high: 2000, + }; + } +}; + +export default getPrioritizationFees; diff --git a/packages/extension/src/providers/solana/ui/routes/index.ts b/packages/extension/src/providers/solana/ui/routes/index.ts index fc3707ea1..8fc0d4a40 100644 --- a/packages/extension/src/providers/solana/ui/routes/index.ts +++ b/packages/extension/src/providers/solana/ui/routes/index.ts @@ -1,14 +1,14 @@ -import btcSign from "../btc-sign-message.vue"; -import btcSendTransaction from "../btc-verify-transaction.vue"; -import btcConnectDApp from "../btc-connect-dapp.vue"; -import btcHWVerify from "../send-transaction/verify-transaction/index.vue"; +import solSign from "../sol-sign-message.vue"; +import solSendTransaction from "../sol-verify-transaction.vue"; +import solConnectDApp from "../sol-connect-dapp.vue"; +import solHWVerify from "../send-transaction/verify-transaction/index.vue"; import { RouteRecordRaw } from "vue-router"; import RouteNames from "./names"; const routes = Object.assign({}, RouteNames); -routes.btcSign.component = btcSign; -routes.btcSendTransaction.component = btcSendTransaction; -routes.btcConnectDApp.component = btcConnectDApp; -routes.btcHWVerify.component = btcHWVerify; +routes.solSign.component = solSign; +routes.solSendTransaction.component = solSendTransaction; +routes.solConnectDApp.component = solConnectDApp; +routes.solHWVerify.component = solHWVerify; export default (namespace: string): RouteRecordRaw[] => { return Object.values(routes).map((route) => { route.path = `/${namespace}/${route.path}`; diff --git a/packages/extension/src/providers/solana/ui/routes/names.ts b/packages/extension/src/providers/solana/ui/routes/names.ts index 021efe31d..7541bb69f 100644 --- a/packages/extension/src/providers/solana/ui/routes/names.ts +++ b/packages/extension/src/providers/solana/ui/routes/names.ts @@ -1,22 +1,22 @@ export default { - btcSign: { - path: "btc-sign", - name: "btcSign", + solSign: { + path: "sol-sign", + name: "solSign", component: {}, }, - btcSendTransaction: { - path: "btc-send-transaction", - name: "btcSendTransaction", + solSendTransaction: { + path: "sol-send-transaction", + name: "solSendTransaction", component: {}, }, - btcConnectDApp: { - path: "btc-connect-dapp", - name: "btcConnectDApp", + solConnectDApp: { + path: "sol-connect-dapp", + name: "solConnectDApp", component: {}, }, - btcHWVerify: { - path: "btc-hw-verify", - name: "btcHWVerify", + solHWVerify: { + path: "sol-hw-verify", + name: "solHWVerify", component: {}, }, }; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index ca9026fbe..87644410a 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -161,7 +161,17 @@ import { Transaction as SolTransaction, SystemProgram, PublicKey, + ComputeBudgetProgram, } from "@solana/web3.js"; +import { + createTransferInstruction, + getAssociatedTokenAddressSync, + getAccount, + createAssociatedTokenAccountInstruction, + ACCOUNT_SIZE, +} from "@solana/spl-token"; +import getPriorityFees from "../libs/get-priority-fees"; + import SolanaAPI from "@/providers/solana/libs/api"; const props = defineProps({ @@ -200,6 +210,8 @@ const accountAssets = ref([]); const selectedAsset = ref(loadingAsset); const amount = ref(""); const isEstimateValid = ref(true); +const priorityFee = ref(0); +const storageFee = ref(0); const hasEnoughBalance = computed(() => { if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { return false; @@ -255,11 +267,12 @@ const TxInfo = computed(() => { const value = sendAmount.value ? numberToHex(toBase(sendAmount.value, props.network.decimals)) : "0x0"; - const toAddress = addressTo.value; + const contract = selectedAsset.value.contract; return { - from: addressFrom.value as `0x{string}`, - value: value as `0x${string}`, - to: toAddress as `0x${string}`, + from: addressFrom.value, + value, + to: addressTo.value, + contract, }; }); @@ -311,8 +324,9 @@ const setTransactionFees = (tx: SolTransaction) => { return tx .getEstimatedFee(solConnection.value!.web3) .then(async (fee) => { + const totalFee = fee! + storageFee.value; const getConvertedVal = () => - fromBase(fee!.toString(), props.network.decimals); + fromBase(totalFee.toString(), props.network.decimals); const nativeVal = accountAssets.value[0].price || "0"; gasCostValues.value[GasPriceTypes.ECONOMY] = { nativeValue: getConvertedVal(), @@ -329,22 +343,8 @@ const setTransactionFees = (tx: SolTransaction) => { }); }; -const Tx = computed(() => { - const from = new PublicKey(getAddress(TxInfo.value.from)); - const transaction = new SolTransaction().add( - SystemProgram.transfer({ - fromPubkey: from, - toPubkey: TxInfo.value.to - ? new PublicKey(getAddress(TxInfo.value.to)) - : from, - lamports: toBN(TxInfo.value.value).toNumber(), - }) - ); - transaction.feePayer = from; - return transaction; -}); const setBaseCosts = async () => { - updateTransactionFees(Tx.value); + updateTransactionFees(); }; const fetchAssets = () => { @@ -396,13 +396,83 @@ const isInputsValid = computed(() => { return true; }); -const updateTransactionFees = (tx: SolTransaction) => { +const updateTransactionFees = async () => { + storageFee.value = 0; + const from = new PublicKey(getAddress(TxInfo.value.from)); + const to = TxInfo.value.to + ? new PublicKey(getAddress(TxInfo.value.to)) + : from; + let transaction = new SolTransaction().add( + SystemProgram.transfer({ + fromPubkey: from, + toPubkey: to, + lamports: toBN(TxInfo.value.value).toNumber(), + }) + ); + if (selectedAsset.value.contract !== NATIVE_TOKEN_ADDRESS) { + console.log("herer", 1); + const contract = new PublicKey(selectedAsset.value.contract); + const associatedTokenTo = getAssociatedTokenAddressSync(contract, to); + const associatedTokenFrom = getAssociatedTokenAddressSync(contract, to); + const validATA = await getAccount( + solConnection.value!.web3, + associatedTokenTo + ) + .then(() => true) + .catch(() => false); + if (validATA) { + console.log("herer", 2); + transaction = new SolTransaction().add( + createTransferInstruction( + associatedTokenFrom, + associatedTokenTo, + from, + toBN(TxInfo.value.value).toNumber() + ) + ); + } else { + console.log("herer", 3); + transaction = new SolTransaction().add( + createAssociatedTokenAccountInstruction( + from, + associatedTokenTo, + to, + contract + ) + ); + transaction.add( + createTransferInstruction( + associatedTokenFrom, + associatedTokenTo, + from, + toBN(TxInfo.value.value).toNumber() + ) + ); + storageFee.value = + await solConnection.value!.web3.getMinimumBalanceForRentExemption( + ACCOUNT_SIZE + ); + } + } + transaction.feePayer = from; + if (isMaxSelected.value) { amount.value = ""; } + priorityFee.value = ( + await getPriorityFees( + new PublicKey(getAddress(TxInfo.value.from)), + solConnection.value!.web3 + ) + ).high; solConnection.value!.web3.getLatestBlockhash().then((bhash) => { - tx.recentBlockhash = bhash.blockhash; - setTransactionFees(tx).then(() => { + transaction.add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: priorityFee.value, + }) + ); + transaction.recentBlockhash = bhash.blockhash; + setTransactionFees(transaction).then(() => { if (isMaxSelected.value) { amount.value = parseFloat(assetMaxValue.value) < 0 ? "0" : assetMaxValue.value; @@ -420,7 +490,7 @@ watch( [isInputsValid, addressTo, selectedAsset, selectedNft, isSendToken], () => { if (isInputsValid.value) { - updateTransactionFees(Tx.value); + updateTransactionFees(); } } ); @@ -463,7 +533,7 @@ const assetMaxValue = computed(() => { const setMaxValue = () => { isMaxSelected.value = true; if (isInputsValid.value) { - updateTransactionFees(Tx.value); + updateTransactionFees(); } }; const inputAddressFrom = (text: string) => { @@ -521,7 +591,7 @@ const inputAmount = (inputAmount: string) => { isMaxSelected.value = false; amount.value = inputAmountBn.lt(0) ? "0" : inputAmount; if (isInputsValid.value) { - updateTransactionFees(Tx.value); + updateTransactionFees(); } }; @@ -530,27 +600,6 @@ const sendAction = async () => { const fromAccountInfo = await keyring.getAccount( addressFrom.value.toLowerCase() ); - // const transaction = Tx.value; - // transaction.recentBlockhash = ( - // await solConnection.value!.web3.getLatestBlockhash() - // ).blockhash; - // const msgToSign = transaction.serializeMessage(); - // sendUsingInternalMessengers({ - // method: InternalMethods.sign, - // params: [bufferToHex(msgToSign), fromAccountInfo], - // }).then((res) => { - // if (res.error) return res; - // transaction.addSignature( - // transaction.feePayer!, - // hexToBuffer(JSON.parse(res.result!)) - // ); - // console.log(transaction.verifySignatures(true)); - // solConnection - // .value!.web3.sendRawTransaction(transaction.serialize()) - // .then((hash) => { - // console.log(`https://solscan.io/tx/${hash}`); - // }); - // }); const txVerifyInfo: VerifyTransactionParams = { TransactionData: TxInfo.value, isNFT: !isSendToken.value, @@ -569,6 +618,7 @@ const sendAction = async () => { fromAddress: fromAccountInfo.address, fromAddressName: fromAccountInfo.name, gasFee: gasCostValues.value[selectedFee.value], + priorityFee: priorityFee.value, gasPriceType: selectedFee.value, toAddress: addressTo.value, }; @@ -585,7 +635,7 @@ const sendAction = async () => { await Browser.windows.create({ url: Browser.runtime.getURL( getUiPath( - `eth-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`, + `sol-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`, ProviderName.ethereum ) ), diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue index 59fff281f..238dc2525 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -93,20 +93,24 @@ import SendProcess from "@action/views/send-process/index.vue"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import { VerifyTransactionParams } from "../../types"; import { getCurrentContext } from "@/libs/messenger/extension"; -import { DEFAULT_EVM_NETWORK, getNetworkByName } from "@/libs/utils/networks"; +import { + DEFAULT_SOLANA_NETWORK, + getNetworkByName, +} from "@/libs/utils/networks"; import { ActivityStatus, Activity, ActivityType } from "@/types/activity"; import ActivityState from "@/libs/activity-state"; import { EnkryptAccount } from "@enkryptcom/types"; import CustomScrollbar from "@action/components/custom-scrollbar/index.vue"; import { BaseNetwork } from "@/types/base-network"; import { toBN } from "web3-utils"; -import { bufferToHex, hexToBuffer } from "@enkryptcom/utils"; +import { bufferToHex, hexToBuffer, toBase } from "@enkryptcom/utils"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; import { Transaction as SolTransaction, SystemProgram, PublicKey, + ComputeBudgetProgram, } from "@solana/web3.js"; import { getAddress } from "@/providers/solana/types/sol-network"; import SolanaAPI from "@/providers/solana/libs/api"; @@ -122,7 +126,7 @@ const txData: VerifyTransactionParams = JSON.parse( ); const isNft = ref(txData.isNFT); const isProcessing = ref(false); -const network = ref(DEFAULT_EVM_NETWORK); +const network = ref(DEFAULT_SOLANA_NETWORK); const isSendDone = ref(false); const account = ref(); const isPopup: boolean = getCurrentContext() === "new-window"; @@ -157,6 +161,11 @@ const sendAction = async () => { lamports: toBN(txData.TransactionData.value).toNumber(), }) ); + transaction.add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: txData.priorityFee, + }) + ); transaction.feePayer = from; const txActivity: Activity = { diff --git a/packages/extension/src/providers/solana/ui/btc-connect-dapp.vue b/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue similarity index 100% rename from packages/extension/src/providers/solana/ui/btc-connect-dapp.vue rename to packages/extension/src/providers/solana/ui/sol-connect-dapp.vue diff --git a/packages/extension/src/providers/solana/ui/btc-sign-message.vue b/packages/extension/src/providers/solana/ui/sol-sign-message.vue similarity index 100% rename from packages/extension/src/providers/solana/ui/btc-sign-message.vue rename to packages/extension/src/providers/solana/ui/sol-sign-message.vue diff --git a/packages/extension/src/providers/solana/ui/btc-verify-transaction.vue b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue similarity index 100% rename from packages/extension/src/providers/solana/ui/btc-verify-transaction.vue rename to packages/extension/src/providers/solana/ui/sol-verify-transaction.vue diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index 00681a2e4..e0453d229 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -3,9 +3,10 @@ import { GasFeeInfo, GasPriceTypes } from "@/providers/common/types"; import { NFTItemWithCollectionName } from "@/types/nft"; export interface SendTransactionDataType { - from: `0x${string}`; - value: `0x${string}`; - to: `0x${string}`; + from: string; + value: string; + to: string; + contract: string; } export interface VerifyTransactionParams { @@ -17,6 +18,6 @@ export interface VerifyTransactionParams { toAddress: string; toToken: ToTokenData; gasFee: GasFeeInfo; + priorityFee: number; gasPriceType: GasPriceTypes; - TxInfo: string; } diff --git a/packages/extension/src/ui/action/views/assets-select-list/index.vue b/packages/extension/src/ui/action/views/assets-select-list/index.vue index 63206b02f..f7bc831d3 100644 --- a/packages/extension/src/ui/action/views/assets-select-list/index.vue +++ b/packages/extension/src/ui/action/views/assets-select-list/index.vue @@ -90,6 +90,7 @@ const yEnd = throttle((event) => { const searchQuery = ref(); const listedAssets = computed(() => { + console.log(props.assets); if (searchQuery.value) { return props.assets .filter((token) => { From 28713b61fd87b320d7cc63e214a4fe10f41f3fd8 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:38:13 -0700 Subject: [PATCH 10/20] fix: minor issue --- .../src/providers/solana/ui/send-transaction/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 87644410a..d2f626201 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -413,7 +413,7 @@ const updateTransactionFees = async () => { console.log("herer", 1); const contract = new PublicKey(selectedAsset.value.contract); const associatedTokenTo = getAssociatedTokenAddressSync(contract, to); - const associatedTokenFrom = getAssociatedTokenAddressSync(contract, to); + const associatedTokenFrom = getAssociatedTokenAddressSync(contract, from); const validATA = await getAccount( solConnection.value!.web3, associatedTokenTo From e418920f1f7b292e49ad477faa5620cefcb3c02d Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:55:10 -0700 Subject: [PATCH 11/20] feat: nft send --- packages/extension/package.json | 4 + .../src/libs/keyring/public-keyring.ts | 10 - .../libs/nft-handlers/simplehash-solana.ts | 95 +++ .../src/libs/nft-handlers/types/simplehash.ts | 13 + .../src/providers/solana/libs/api.ts | 17 +- .../src/providers/solana/networks/solana.ts | 2 + .../src/providers/solana/types/sol-network.ts | 2 +- .../solana/ui/send-transaction/index.vue | 102 ++- .../verify-transaction/index.vue | 67 +- .../src/providers/solana/ui/types.ts | 2 +- packages/extension/src/types/activity.ts | 7 +- packages/extension/src/types/nft.ts | 2 + .../action/views/assets-select-list/index.vue | 1 - .../action/views/network-activity/index.vue | 14 +- yarn.lock | 702 +++++++++++++++++- 15 files changed, 952 insertions(+), 88 deletions(-) create mode 100644 packages/extension/src/libs/nft-handlers/simplehash-solana.ts diff --git a/packages/extension/package.json b/packages/extension/package.json index 506c44793..a123abd39 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -37,7 +37,11 @@ "@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/web3.js": "^1.95.2", "@types/chrome": "^0.0.269", diff --git a/packages/extension/src/libs/keyring/public-keyring.ts b/packages/extension/src/libs/keyring/public-keyring.ts index a815015e8..c9a2048e8 100644 --- a/packages/extension/src/libs/keyring/public-keyring.ts +++ b/packages/extension/src/libs/keyring/public-keyring.ts @@ -77,16 +77,6 @@ class PublicKeyRing { walletType: WalletType.mnemonic, isHardware: false, }; - allKeys["7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE"] = { - address: "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE", - basePath: "m/501'/2'/0'/1", - name: "fake sol account #1", - pathIndex: 0, - publicKey: "0x0", - signerType: SignerType.ed25519sol, - walletType: WalletType.mnemonic, - isHardware: false, - }; } return allKeys; } diff --git a/packages/extension/src/libs/nft-handlers/simplehash-solana.ts b/packages/extension/src/libs/nft-handlers/simplehash-solana.ts new file mode 100644 index 000000000..9cfd617ef --- /dev/null +++ b/packages/extension/src/libs/nft-handlers/simplehash-solana.ts @@ -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 => { + 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 => { + 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 = {}; + 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); +}; diff --git a/packages/extension/src/libs/nft-handlers/types/simplehash.ts b/packages/extension/src/libs/nft-handlers/types/simplehash.ts index 08efa9ebf..ce6032c58 100644 --- a/packages/extension/src/libs/nft-handlers/types/simplehash.ts +++ b/packages/extension/src/libs/nft-handlers/types/simplehash.ts @@ -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; @@ -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; diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts index c1a9d6d44..b350069f4 100644 --- a/packages/extension/src/providers/solana/libs/api.ts +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -24,8 +24,21 @@ class API implements ProviderAPIInterface { // eslint-disable-next-line @typescript-eslint/no-empty-function async init(): Promise {} async getTransactionStatus(hash: string): Promise { - console.log(hash, "gettxstatus"); - return null; + return this.web3 + .getTransaction(hash, { + maxSupportedTransactionVersion: 0, + commitment: "confirmed", + }) + .then((tx) => { + if (!tx) return null; + const retVal: SOLRawInfo = { + blockNumber: tx.slot, + timestamp: tx.blockTime, + transactionHash: hash, + status: tx.meta?.err ? false : true, + }; + return retVal; + }); } async getBalance(pubkey: string): Promise { const balance = await this.web3.getBalance( diff --git a/packages/extension/src/providers/solana/networks/solana.ts b/packages/extension/src/providers/solana/networks/solana.ts index 9e9098465..b9b6128b7 100644 --- a/packages/extension/src/providers/solana/networks/solana.ts +++ b/packages/extension/src/providers/solana/networks/solana.ts @@ -2,6 +2,7 @@ import { NetworkNames } from "@enkryptcom/types"; import { SolanaNetwork, SolanaNetworkOptions } from "../types/sol-network"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; +import shNFTHandler from "@/libs/nft-handlers/simplehash-solana"; const solanaOptions: SolanaNetworkOptions = { name: NetworkNames.Solana, @@ -19,6 +20,7 @@ const solanaOptions: SolanaNetworkOptions = { activityHandler: wrapActivityHandler(() => Promise.resolve([])), basePath: "m/44'/501'", assetsInfoHandler, + NFTHandler: shNFTHandler, }; const bitcoin = new SolanaNetwork(solanaOptions); diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index 722f7573e..46a4784bd 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -49,7 +49,7 @@ export interface SolanaNetworkOptions { } export const getAddress = (pubkey: string) => { - if (pubkey.length === 44) return pubkey; + if (pubkey.length <= 44) return pubkey; return bs58.encode(hexToBuffer(pubkey)); }; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index d2f626201..0747ff2d8 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -157,11 +157,18 @@ import { GenericNameResolver, CoinType } from "@/libs/name-resolver"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; import { NATIVE_TOKEN_ADDRESS } from "@/providers/ethereum/libs/common"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { + getAssetWithProof, + transfer, + mplBubblegum, +} from "@metaplex-foundation/mpl-bubblegum"; import { Transaction as SolTransaction, SystemProgram, PublicKey, ComputeBudgetProgram, + TransactionInstruction, } from "@solana/web3.js"; import { createTransferInstruction, @@ -171,8 +178,10 @@ import { ACCOUNT_SIZE, } from "@solana/spl-token"; import getPriorityFees from "../libs/get-priority-fees"; +import bs58 from "bs58"; import SolanaAPI from "@/providers/solana/libs/api"; +import { getSimulationComputeUnits } from "@solana-developers/helpers"; const props = defineProps({ network: { @@ -210,8 +219,8 @@ const accountAssets = ref([]); const selectedAsset = ref(loadingAsset); const amount = ref(""); const isEstimateValid = ref(true); -const priorityFee = ref(0); const storageFee = ref(0); +const SolTx = ref(); const hasEnoughBalance = computed(() => { if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { return false; @@ -241,7 +250,7 @@ const selectedNft = ref({ name: "Loading", url: "", collectionName: "", - type: NFTType.ERC721, + type: NFTType.SolanaToken, }); const nativeBalance = computed(() => { @@ -265,12 +274,14 @@ onMounted(async () => { const TxInfo = computed(() => { const value = sendAmount.value - ? numberToHex(toBase(sendAmount.value, props.network.decimals)) + ? numberToHex(toBase(sendAmount.value, selectedAsset.value.decimals)) : "0x0"; - const contract = selectedAsset.value.contract; + const contract = isSendToken.value + ? selectedAsset.value.contract + : selectedNft.value.contract; return { from: addressFrom.value, - value, + value: isSendToken.value ? value : "0x1", to: addressTo.value, contract, }; @@ -354,7 +365,6 @@ const fetchAssets = () => { return props.network.getAllTokens(addressFrom.value).then((allAssets) => { accountAssets.value = allAssets as SOLToken[]; selectedAsset.value = allAssets[0] as SOLToken; - isLoadingAssets.value = false; }); }; @@ -402,16 +412,30 @@ const updateTransactionFees = async () => { const to = TxInfo.value.to ? new PublicKey(getAddress(TxInfo.value.to)) : from; - let transaction = new SolTransaction().add( - SystemProgram.transfer({ - fromPubkey: from, - toPubkey: to, - lamports: toBN(TxInfo.value.value).toNumber(), + const priorityFee = ( + await getPriorityFees( + new PublicKey(getAddress(TxInfo.value.from)), + solConnection.value!.web3 + ) + ).high; + const transaction = new SolTransaction().add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: priorityFee * 100, }) ); - if (selectedAsset.value.contract !== NATIVE_TOKEN_ADDRESS) { - console.log("herer", 1); - const contract = new PublicKey(selectedAsset.value.contract); + if (isSendToken.value && TxInfo.value.contract === NATIVE_TOKEN_ADDRESS) { + transaction.add( + SystemProgram.transfer({ + fromPubkey: from, + toPubkey: to, + lamports: toBN(TxInfo.value.value).toNumber(), + }) + ); + } else if ( + isSendToken.value || + (!isSendToken.value && selectedNft.value.type === NFTType.SolanaToken) + ) { + const contract = new PublicKey(TxInfo.value.contract); const associatedTokenTo = getAssociatedTokenAddressSync(contract, to); const associatedTokenFrom = getAssociatedTokenAddressSync(contract, from); const validATA = await getAccount( @@ -421,8 +445,7 @@ const updateTransactionFees = async () => { .then(() => true) .catch(() => false); if (validATA) { - console.log("herer", 2); - transaction = new SolTransaction().add( + transaction.add( createTransferInstruction( associatedTokenFrom, associatedTokenTo, @@ -431,8 +454,7 @@ const updateTransactionFees = async () => { ) ); } else { - console.log("herer", 3); - transaction = new SolTransaction().add( + transaction.add( createAssociatedTokenAccountInstruction( from, associatedTokenTo, @@ -453,25 +475,36 @@ const updateTransactionFees = async () => { ACCOUNT_SIZE ); } + } else if ( + !isSendToken.value && + selectedNft.value.type === NFTType.SolanaBGUM + ) { + const umi = createUmi(solConnection.value!.web3).use(mplBubblegum()); + const assetWithProof = await getAssetWithProof( + umi, + new PublicKey(selectedNft.value.contract) as any + ); + const txData = transfer(umi, { + ...assetWithProof, + leafOwner: from as any, + newLeafOwner: to as any, + }); + txData.getInstructions().forEach((i) => { + i.keys = i.keys.map((k) => { + k.pubkey = new PublicKey(k.pubkey) as any; + return k; + }); + i.programId = new PublicKey(i.programId) as any; + transaction.add(i as any); + }); } transaction.feePayer = from; - if (isMaxSelected.value) { amount.value = ""; } - priorityFee.value = ( - await getPriorityFees( - new PublicKey(getAddress(TxInfo.value.from)), - solConnection.value!.web3 - ) - ).high; - solConnection.value!.web3.getLatestBlockhash().then((bhash) => { - transaction.add( - ComputeBudgetProgram.setComputeUnitPrice({ - microLamports: priorityFee.value, - }) - ); + solConnection.value!.web3.getLatestBlockhash().then(async (bhash) => { transaction.recentBlockhash = bhash.blockhash; + SolTx.value = transaction; setTransactionFees(transaction).then(() => { if (isMaxSelected.value) { amount.value = @@ -618,9 +651,14 @@ const sendAction = async () => { fromAddress: fromAccountInfo.address, fromAddressName: fromAccountInfo.name, gasFee: gasCostValues.value[selectedFee.value], - priorityFee: priorityFee.value, gasPriceType: selectedFee.value, toAddress: addressTo.value, + encodedTx: bs58.encode( + SolTx.value!.serialize({ + requireAllSignatures: false, + verifySignatures: false, + }) + ), }; const routedRoute = router.resolve({ name: RouterNames.verify.name, diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue index 238dc2525..22824a093 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -102,20 +102,20 @@ import ActivityState from "@/libs/activity-state"; import { EnkryptAccount } from "@enkryptcom/types"; import CustomScrollbar from "@action/components/custom-scrollbar/index.vue"; import { BaseNetwork } from "@/types/base-network"; -import { toBN } from "web3-utils"; -import { bufferToHex, hexToBuffer, toBase } from "@enkryptcom/utils"; +import { bufferToHex, hexToBuffer } from "@enkryptcom/utils"; import { trackSendEvents } from "@/libs/metrics"; import { SendEventType } from "@/libs/metrics/types"; import { Transaction as SolTransaction, - SystemProgram, - PublicKey, ComputeBudgetProgram, + VersionedTransaction, + TransactionMessage, } from "@solana/web3.js"; -import { getAddress } from "@/providers/solana/types/sol-network"; +import { getSimulationComputeUnits } from "@solana-developers/helpers"; import SolanaAPI from "@/providers/solana/libs/api"; import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; import { InternalMethods } from "@/types/messenger"; +import bs58 from "bs58"; const KeyRing = new PublicKeyRing(); const route = useRoute(); @@ -153,25 +153,33 @@ const sendAction = async () => { trackSendEvents(SendEventType.SendApprove, { network: network.value.name, }); - const from = new PublicKey(getAddress(txData.TransactionData.from)); - const transaction = new SolTransaction().add( - SystemProgram.transfer({ - fromPubkey: from, - toPubkey: new PublicKey(getAddress(txData.TransactionData.to)), - lamports: toBN(txData.TransactionData.value).toNumber(), - }) + const transactiontemp = SolTransaction.from(bs58.decode(txData.encodedTx)); + const solAPI = (await network.value.api()).api as SolanaAPI; + const computeUnits = await getSimulationComputeUnits( + solAPI.web3, + transactiontemp.instructions, + transactiontemp.feePayer!, + [] ); - transaction.add( - ComputeBudgetProgram.setComputeUnitPrice({ - microLamports: txData.priorityFee, - }) + if (computeUnits) { + transactiontemp.instructions.unshift( + ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits + 5000 }) // adding few extra CUs as a buffer + ); + } + const latestBlock = await solAPI.web3.getLatestBlockhash(); + transactiontemp.recentBlockhash = latestBlock.blockhash; + const transaction = new VersionedTransaction( + new TransactionMessage({ + instructions: transactiontemp.instructions, + recentBlockhash: latestBlock.blockhash, + payerKey: transactiontemp.feePayer!, + }).compileToV0Message([]) ); - transaction.feePayer = from; - const txActivity: Activity = { - from: txData.fromAddress, + from: network.value.displayAddress(txData.fromAddress), to: txData.toAddress, - isIncoming: txData.fromAddress === txData.toAddress, + isIncoming: + network.value.displayAddress(txData.fromAddress) === txData.toAddress, network: network.value.name, status: ActivityStatus.pending, timestamp: new Date().getTime(), @@ -189,18 +197,15 @@ const sendAction = async () => { transactionHash: "", }; const activityState = new ActivityState(); - const solAPI = (await network.value.api()).api as SolanaAPI; - transaction.recentBlockhash = ( - await solAPI.web3.getLatestBlockhash() - ).blockhash; - const msgToSign = transaction.serializeMessage(); + //transaction.message.serialize() + const msgToSign = transaction.message.serialize(); sendUsingInternalMessengers({ method: InternalMethods.sign, params: [bufferToHex(msgToSign), account.value!], }).then((res) => { if (res.error) return res; transaction.addSignature( - transaction.feePayer!, + transactiontemp.feePayer!, hexToBuffer(JSON.parse(res.result!)) ); const onHash = (hash: string) => { @@ -216,7 +221,10 @@ const sendAction = async () => { }, }, ], - { address: txData.fromAddress, network: network.value.name } + { + address: network.value.displayAddress(txData.fromAddress), + network: network.value.name, + } ); isSendDone.value = true; if (getCurrentContext() === "popup") { @@ -232,15 +240,14 @@ const sendAction = async () => { } }; solAPI.web3 - .sendRawTransaction(transaction.serialize()) + .sendRawTransaction(Buffer.from(transaction.serialize())) .then((hash) => { onHash(hash); - console.log(`https://solscan.io/tx/${hash}`); }) .catch((e) => { txActivity.status = ActivityStatus.failed; activityState.addActivities([txActivity], { - address: txData.fromAddress, + address: network.value.displayAddress(txData.fromAddress), network: network.value.name, }); isProcessing.value = false; diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index e0453d229..1085e6da4 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -18,6 +18,6 @@ export interface VerifyTransactionParams { toAddress: string; toToken: ToTokenData; gasFee: GasFeeInfo; - priorityFee: number; gasPriceType: GasPriceTypes; + encodedTx: string; } diff --git a/packages/extension/src/types/activity.ts b/packages/extension/src/types/activity.ts index cb0a716f0..295e67192 100644 --- a/packages/extension/src/types/activity.ts +++ b/packages/extension/src/types/activity.ts @@ -19,8 +19,8 @@ interface BTCOuts extends BTCIns { interface SOLRawInfo { blockNumber: number; transactionHash: string; - timestamp: number | undefined; - fee: number; + timestamp: number | null | undefined; + status: boolean; } interface BTCRawInfo { @@ -126,7 +126,8 @@ interface Activity { | SubscanExtrinsicInfo | BTCRawInfo | SwapRawInfo - | KadenaRawInfo; + | KadenaRawInfo + | SOLRawInfo; } export { diff --git a/packages/extension/src/types/nft.ts b/packages/extension/src/types/nft.ts index a7600f8ca..bd16b62d1 100644 --- a/packages/extension/src/types/nft.ts +++ b/packages/extension/src/types/nft.ts @@ -2,6 +2,8 @@ export enum NFTType { ERC721 = "ERC721", ERC1155 = "ERC1155", Ordinals = "ORDINALS", + SolanaBGUM = "SOLANABGUM", + SolanaToken = "SOLANATOKEN", } export interface NFTItem { diff --git a/packages/extension/src/ui/action/views/assets-select-list/index.vue b/packages/extension/src/ui/action/views/assets-select-list/index.vue index f7bc831d3..63206b02f 100644 --- a/packages/extension/src/ui/action/views/assets-select-list/index.vue +++ b/packages/extension/src/ui/action/views/assets-select-list/index.vue @@ -90,7 +90,6 @@ const yEnd = throttle((event) => { const searchQuery = ref(); const listedAssets = computed(() => { - console.log(props.assets); if (searchQuery.value) { return props.assets .filter((token) => { 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 b2198966c..7193d8f86 100644 --- a/packages/extension/src/ui/action/views/network-activity/index.vue +++ b/packages/extension/src/ui/action/views/network-activity/index.vue @@ -67,6 +67,7 @@ import { SubscanExtrinsicInfo, SwapRawInfo, KadenaRawInfo, + SOLRawInfo, } from "@/types/activity"; import NetworkActivityLoading from "./components/network-activity-loading.vue"; import { ProviderName } from "@/types/provider"; @@ -179,13 +180,24 @@ const getInfo = (activity: Activity, info: any, timer: any) => { .then(() => updateVisibleActivity(activity)); } else if (props.network.provider === ProviderName.kadena) { const kadenaInfo = info as KadenaRawInfo; - activity.status = kadenaInfo.result.status == "success" ? ActivityStatus.success : ActivityStatus.failed; activity.rawInfo = kadenaInfo as KadenaRawInfo; + activityState + .updateActivity(activity, { + address: activityAddress.value, + network: props.network.name, + }) + .then(() => updateVisibleActivity(activity)); + } else if (props.network.provider === ProviderName.solana) { + const solInfo = info as SOLRawInfo; + activity.status = info.status + ? ActivityStatus.success + : ActivityStatus.failed; + activity.rawInfo = solInfo; activityState .updateActivity(activity, { address: activityAddress.value, diff --git a/yarn.lock b/yarn.lock index debac56a3..cd7126f0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2778,6 +2778,9 @@ __metadata: "@kadena/pactjs-cli": ^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 "@polkadot/api": ^12.2.3 "@polkadot/extension-inject": ^0.50.1 "@polkadot/keyring": ^13.0.2 @@ -2792,6 +2795,7 @@ __metadata: "@rollup/plugin-node-resolve": ^15.2.3 "@rollup/plugin-replace": ^5.0.7 "@rollup/plugin-typescript": ^11.1.6 + "@solana-developers/helpers": ^2.4.0 "@solana/spl-token": ^0.4.8 "@solana/web3.js": ^1.95.2 "@types/bs58": ^4.0.4 @@ -5971,6 +5975,232 @@ __metadata: languageName: node linkType: hard +"@metaplex-foundation/digital-asset-standard-api@npm:^1.0.4": + version: 1.0.4 + resolution: "@metaplex-foundation/digital-asset-standard-api@npm:1.0.4" + dependencies: + package.json: ^2.0.1 + peerDependencies: + "@metaplex-foundation/umi": ">= 0.8.2 < 1" + checksum: 075d3d6fa16d923dc74490a3deab9a2d17979f135550201ff9a4e0202de3734bbb3027475fc25c1952773f3258883e4f044e79273ca2afcc178fd0ef71e45a49 + languageName: node + linkType: hard + +"@metaplex-foundation/mpl-bubblegum@npm:^4.2.0": + version: 4.2.0 + resolution: "@metaplex-foundation/mpl-bubblegum@npm:4.2.0" + dependencies: + "@metaplex-foundation/digital-asset-standard-api": ^1.0.4 + "@metaplex-foundation/mpl-token-metadata": 3.0.0-alpha.27 + "@metaplex-foundation/mpl-toolbox": ^0.9.0 + "@noble/hashes": ^1.3.1 + merkletreejs: ^0.3.9 + peerDependencies: + "@metaplex-foundation/umi": ">= 0.8.9 < 1" + checksum: 66e9a6d5bd6a672fa359596ca32945de010eb51a9eb9106945b1538d88ac05993667642103684e9d660fc375d2b51b5b3ac6a02835058bb421a963b7e58a02a3 + languageName: node + linkType: hard + +"@metaplex-foundation/mpl-token-metadata@npm:3.0.0-alpha.27": + version: 3.0.0-alpha.27 + resolution: "@metaplex-foundation/mpl-token-metadata@npm:3.0.0-alpha.27" + dependencies: + "@metaplex-foundation/mpl-toolbox": ^0.9.0 + peerDependencies: + "@metaplex-foundation/umi": ^0.8.2 + checksum: ec79071615b418a4efcfac50616dc6a873f6a827589a5c83990e68e8aa3617813893f8574268cdd53d0cb0330997a979913494ee2e1a722b7716f5536dd579a4 + languageName: node + linkType: hard + +"@metaplex-foundation/mpl-toolbox@npm:^0.9.0": + version: 0.9.4 + resolution: "@metaplex-foundation/mpl-toolbox@npm:0.9.4" + peerDependencies: + "@metaplex-foundation/umi": ">= 0.8.2 < 1" + checksum: 712d1c2e243582cfe5a0589500d861ae65092ea740fa1f392dee88fca41153421a76c73943afa63a45b055d78065db95114c16fcb04668c6ef514f8784d18bb4 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-bundle-defaults@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-bundle-defaults@npm:0.9.2" + dependencies: + "@metaplex-foundation/umi-downloader-http": ^0.9.2 + "@metaplex-foundation/umi-eddsa-web3js": ^0.9.2 + "@metaplex-foundation/umi-http-fetch": ^0.9.2 + "@metaplex-foundation/umi-program-repository": ^0.9.2 + "@metaplex-foundation/umi-rpc-chunk-get-accounts": ^0.9.2 + "@metaplex-foundation/umi-rpc-web3js": ^0.9.2 + "@metaplex-foundation/umi-serializer-data-view": ^0.9.2 + "@metaplex-foundation/umi-transaction-factory-web3js": ^0.9.2 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + "@solana/web3.js": ^1.72.0 + checksum: bda48dd681ba70716292f36dbb230f754e83127336f62300164a9453cba238fcf4a06eb80da0b33a25ee052f7cab78956f3311b67d9e65a9b35f233ab572c35f + languageName: node + linkType: hard + +"@metaplex-foundation/umi-downloader-http@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-downloader-http@npm:0.9.2" + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + checksum: b0a95d51d22231356a616050405101523016849adedbbc7951cb5f14b2d9edb6ff991fc8c7578eae0897b6cb0efd44287ce74396cbade5bf7275c9cef16c3d4c + languageName: node + linkType: hard + +"@metaplex-foundation/umi-eddsa-web3js@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-eddsa-web3js@npm:0.9.2" + dependencies: + "@metaplex-foundation/umi-web3js-adapters": ^0.9.2 + "@noble/curves": ^1.0.0 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + "@solana/web3.js": ^1.72.0 + checksum: feb807bc03c4c5db0122c5b8c7864710227c570bb7447f7bc15fd4aa65db0dc4aa0f865b90c231c424357fe716722ab47b17509a11b4be6564b48a6f50c51d7e + languageName: node + linkType: hard + +"@metaplex-foundation/umi-http-fetch@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-http-fetch@npm:0.9.2" + dependencies: + node-fetch: ^2.6.7 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + checksum: b9ca0f1717f3c179188ce207f273b904719c174cd9c32bb602631c637aa1a11ef8feed940f44ab1b9149ecf35dcf3d77d8997ac136c2a7d19463ce3526d09d65 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-options@npm:^0.8.9": + version: 0.8.9 + resolution: "@metaplex-foundation/umi-options@npm:0.8.9" + checksum: 09ea3d2a9ab73a32248f1e7a6701a6f9c4756752239075ee626b2b1477ce83e3ba4af6e20f1bb1d30dd8510bf48bc4be178cf928bfeaeb0ad587b8ca5d48dab5 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-program-repository@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-program-repository@npm:0.9.2" + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + checksum: 99305260b8c3c5b3095c3c5a46169115f4d456dfaafc3db25e46a6bd41ad049f95e7a9b1f3299e257f00d2b64643ae1b9e1ac6097395b48c2fd6c6f71578d569 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-public-keys@npm:^0.8.9": + version: 0.8.9 + resolution: "@metaplex-foundation/umi-public-keys@npm:0.8.9" + dependencies: + "@metaplex-foundation/umi-serializers-encodings": ^0.8.9 + checksum: f5d1bf557ab9a7b8f47426d9d5186064780b906fdf2a540ec69f4c2ddfe514f2fb8aa7b708ac9a33d7c7e6155f83893a1650ca86ccb7549c754325b6228b2ecb + languageName: node + linkType: hard + +"@metaplex-foundation/umi-rpc-chunk-get-accounts@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-rpc-chunk-get-accounts@npm:0.9.2" + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + checksum: f8beab80aff209f04ef739e2ef583b6f91a8a06ac26720724012b32c23e151c81451784cdabf6d9d96bd334dabd3078e5959314845856a7be15c81cc492ff217 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-rpc-web3js@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-rpc-web3js@npm:0.9.2" + dependencies: + "@metaplex-foundation/umi-web3js-adapters": ^0.9.2 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + "@solana/web3.js": ^1.72.0 + checksum: bd69fc64598dd4782e5614accfda0f012c8a25b102310df8eb0e2a170f8c67de99055ccd0b76c745ab18c952c7a3d634df5d089d091d06a79c1ce6bd133d4e4d + languageName: node + linkType: hard + +"@metaplex-foundation/umi-serializer-data-view@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-serializer-data-view@npm:0.9.2" + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + checksum: 305eb67360b990a4ec80ca8a219b2c0aba89a58d3a83f3f15e641a8b11740f57abbe65954a92881fdd41dfe6934fb4883c95b0069ab0937198d94dff593e8d65 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-serializers-core@npm:^0.8.9": + version: 0.8.9 + resolution: "@metaplex-foundation/umi-serializers-core@npm:0.8.9" + checksum: 5305f893f3038e8493d0852864b80469492884e4052f1e03205ab276d9334858f92c9bd68dc4311ae0e62ebdf24862956d97bfc76a5d942a02ee89a21096bcbe + languageName: node + linkType: hard + +"@metaplex-foundation/umi-serializers-encodings@npm:^0.8.9": + version: 0.8.9 + resolution: "@metaplex-foundation/umi-serializers-encodings@npm:0.8.9" + dependencies: + "@metaplex-foundation/umi-serializers-core": ^0.8.9 + checksum: f32d1acfa020da9712fe9cd8c470f013d39546803f1eb0fa4a97305431ce235cf066f686638c3c6f63dcef2824d4f0b936ba203b86e94c914fd738d5b91f042a + languageName: node + linkType: hard + +"@metaplex-foundation/umi-serializers-numbers@npm:^0.8.9": + version: 0.8.9 + resolution: "@metaplex-foundation/umi-serializers-numbers@npm:0.8.9" + dependencies: + "@metaplex-foundation/umi-serializers-core": ^0.8.9 + checksum: 3b8144cbcbc41f0bd69832b8756983fbb05794cad4f1a759b054b332cd5ff13713415a7bc1713f729d7358b978361dfcd6a88c3b6d0036df5a5b772a1f37029d + languageName: node + linkType: hard + +"@metaplex-foundation/umi-serializers@npm:^0.9.0": + version: 0.9.0 + resolution: "@metaplex-foundation/umi-serializers@npm:0.9.0" + dependencies: + "@metaplex-foundation/umi-options": ^0.8.9 + "@metaplex-foundation/umi-public-keys": ^0.8.9 + "@metaplex-foundation/umi-serializers-core": ^0.8.9 + "@metaplex-foundation/umi-serializers-encodings": ^0.8.9 + "@metaplex-foundation/umi-serializers-numbers": ^0.8.9 + checksum: 3b12e1bb706a2ee78b76a030d77f6197f7a705517d249c001ca564091da2dac91073a74e573766bef20e09cad16f2e36b10839341fce2b7f4a697c67a4e05c93 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-transaction-factory-web3js@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-transaction-factory-web3js@npm:0.9.2" + dependencies: + "@metaplex-foundation/umi-web3js-adapters": ^0.9.2 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + "@solana/web3.js": ^1.72.0 + checksum: 5d8b6bd17e8eaa7b4db439062583d0e77a5e01e0e521634d89af08ae28abb3d737e20ab0ef4a05d73638dbccd468b4c8c62b2d56ea3a07e6f92177500fad8117 + languageName: node + linkType: hard + +"@metaplex-foundation/umi-web3js-adapters@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi-web3js-adapters@npm:0.9.2" + dependencies: + buffer: ^6.0.3 + peerDependencies: + "@metaplex-foundation/umi": ^0.9.2 + "@solana/web3.js": ^1.72.0 + checksum: 0fc9cdb24059d47e4eae13e4b0e2caac39f188bb5df8cde8ff34cd482e3b3d2b20319d7bcb5b043be171ca3ad63d0e6e5d668fc8392a7128f42b014971d63eb1 + languageName: node + linkType: hard + +"@metaplex-foundation/umi@npm:^0.9.2": + version: 0.9.2 + resolution: "@metaplex-foundation/umi@npm:0.9.2" + dependencies: + "@metaplex-foundation/umi-options": ^0.8.9 + "@metaplex-foundation/umi-public-keys": ^0.8.9 + "@metaplex-foundation/umi-serializers": ^0.9.0 + checksum: 1178cfef1a5ccf9364c32043dae873103b59f2ca2e36f02cee132007f1224ad7e5ea29f28624852392d73993532c3f3f3e7deab7716835cdc1eea2d3f96970bb + languageName: node + linkType: hard + "@mobily/ts-belt@npm:^3.13.1": version: 3.13.1 resolution: "@mobily/ts-belt@npm:3.13.1" @@ -7572,6 +7802,18 @@ __metadata: languageName: node linkType: hard +"@solana-developers/helpers@npm:^2.4.0": + version: 2.4.0 + resolution: "@solana-developers/helpers@npm:2.4.0" + dependencies: + "@solana/spl-token": ^0.4.8 + "@solana/web3.js": ^1.95.2 + bs58: ^6.0.0 + dotenv: ^16.4.5 + checksum: 847e1676ebab87b5f7420a9440b0a45d5ba36ac2813e0b0052e937de2b2c52e735e6f664b84364468f6908f573891c30dd70c86311b3f21bedd17c170a2db82e + languageName: node + linkType: hard + "@solana/buffer-layout-utils@npm:^0.2.0": version: 0.2.0 resolution: "@solana/buffer-layout-utils@npm:0.2.0" @@ -11052,6 +11294,15 @@ __metadata: languageName: node linkType: hard +"abs@npm:^1.2.1": + version: 1.3.14 + resolution: "abs@npm:1.3.14" + dependencies: + ul: ^5.0.0 + checksum: af5fc49949f0694f458b7849e8ab9f10d2f6a2e1a93b3ac5cb25d62ad331d9f4d37a72fcbc1a84591fdf90fa3fffba8bf6de60dd483dd1af2200166f3436b3d3 + languageName: node + linkType: hard + "accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -12651,6 +12902,13 @@ __metadata: languageName: node linkType: hard +"buffer-reverse@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-reverse@npm:1.0.1" + checksum: e350872a89b17af0a7e1bd7a73239a535164f3f010b0800add44f2e52bd0511548dc5b96c20309effba969868c385023d2d02a0add6155f6a76da7b3073b77bd + languageName: node + linkType: hard + "buffer-to-arraybuffer@npm:^0.0.5": version: 0.0.5 resolution: "buffer-to-arraybuffer@npm:0.0.5" @@ -13005,6 +13263,13 @@ __metadata: languageName: node linkType: hard +"capture-stack-trace@npm:^1.0.0": + version: 1.0.2 + resolution: "capture-stack-trace@npm:1.0.2" + checksum: 13295e8176e8de74bcbe0e4fd938bed9eb4204b4cc200210ff46df91cb20b69e86f6ef42f408a59454f8b62e567ef0ee6ee5b5e7e16e686668bc77f2741542b4 + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.3.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -14089,6 +14354,15 @@ __metadata: languageName: node linkType: hard +"create-error-class@npm:^3.0.1": + version: 3.0.2 + resolution: "create-error-class@npm:3.0.2" + dependencies: + capture-stack-trace: ^1.0.0 + checksum: 7254a6f96002d3226d3c1fec952473398761eb4fb12624c5dce6ed0017cdfad6de39b29aa7139680d7dcf416c25f2f308efda6eb6d9b7123f829b19ef8271511 + languageName: node + linkType: hard + "create-hash@npm:1.2.0, create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": version: 1.2.0 resolution: "create-hash@npm:1.2.0" @@ -14238,7 +14512,7 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:4.2.0, crypto-js@npm:^4.0.0, crypto-js@npm:^4.1.1": +"crypto-js@npm:4.2.0, crypto-js@npm:^4.0.0, crypto-js@npm:^4.1.1, crypto-js@npm:^4.2.0": version: 4.2.0 resolution: "crypto-js@npm:4.2.0" checksum: f051666dbc077c8324777f44fbd3aaea2986f198fe85092535130d17026c7c2ccf2d23ee5b29b36f7a4a07312db2fae23c9094b644cc35f7858b1b4fcaf27774 @@ -14616,6 +14890,13 @@ __metadata: languageName: node linkType: hard +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3, deep-is@npm:~0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -14669,6 +14950,15 @@ __metadata: languageName: node linkType: hard +"deffy@npm:^2.2.1, deffy@npm:^2.2.2": + version: 2.2.4 + resolution: "deffy@npm:2.2.4" + dependencies: + typpy: ^2.0.0 + checksum: a06f44306c676d5fb663120610060a3abc48c745200f306a11acc2936095f901b170018fa5ffedbc1ebddf43b83467dc11b90b9ec5fdd232f970d85d66515543 + languageName: node + linkType: hard + "define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.1": version: 1.1.1 resolution: "define-data-property@npm:1.1.1" @@ -15089,7 +15379,7 @@ __metadata: languageName: node linkType: hard -"duplexer2@npm:^0.1.2, duplexer2@npm:~0.1.0, duplexer2@npm:~0.1.2": +"duplexer2@npm:^0.1.2, duplexer2@npm:^0.1.4, duplexer2@npm:~0.1.0, duplexer2@npm:~0.1.2": version: 0.1.4 resolution: "duplexer2@npm:0.1.4" dependencies: @@ -15309,6 +15599,15 @@ __metadata: languageName: node linkType: hard +"err@npm:^1.1.1": + version: 1.1.1 + resolution: "err@npm:1.1.1" + dependencies: + typpy: ^2.2.0 + checksum: e9b8b5226724e32c7fdf2a14c1380093e130e1bfa4e15f6886ffd7239f0a51b8733da7b8790f01a6681f641a97838d0537e18d553528ab52245a05e6dcf75fe7 + languageName: node + linkType: hard + "errno@npm:^0.1.1": version: 0.1.8 resolution: "errno@npm:0.1.8" @@ -16395,6 +16694,16 @@ __metadata: languageName: node linkType: hard +"exec-limiter@npm:^3.0.0": + version: 3.2.13 + resolution: "exec-limiter@npm:3.2.13" + dependencies: + limit-it: ^3.0.0 + typpy: ^2.1.0 + checksum: 61c4d7e222bf5d062e9bf9a067cc9346c6d16cce935e5e4ddaa0c2fbcbda9b214545f04aeabc3bb902a17b0b340b6710f4e7ead4999ed255764c028074a1b3c7 + languageName: node + linkType: hard + "execa@npm:^0.8.0": version: 0.8.0 resolution: "execa@npm:0.8.0" @@ -17106,6 +17415,15 @@ __metadata: languageName: node linkType: hard +"function.name@npm:^1.0.3": + version: 1.0.13 + resolution: "function.name@npm:1.0.13" + dependencies: + noop6: ^1.0.1 + checksum: 376bd4247cacffb50ae425c64e99c42fd10a875197e09db7e65002a5bc0f539608b03f1ff7e27234516cd5b3bbfd9d9d8e912fa9aaeec52d3b422d501f290fff + languageName: node + linkType: hard + "function.prototype.name@npm:^1.1.6": version: 1.1.6 resolution: "function.prototype.name@npm:1.1.6" @@ -17293,6 +17611,23 @@ __metadata: languageName: node linkType: hard +"git-package-json@npm:^1.4.0": + version: 1.4.10 + resolution: "git-package-json@npm:1.4.10" + dependencies: + deffy: ^2.2.1 + err: ^1.1.1 + gry: ^5.0.0 + normalize-package-data: ^2.3.5 + oargv: ^3.4.1 + one-by-one: ^3.1.0 + r-json: ^1.2.1 + r-package-json: ^1.0.0 + tmp: 0.0.28 + checksum: 69c16f42e9dfbfd60daf5f7cf33642b2cf4ebf90ffac452b4eacf12c3d9f1728da1c0d771348f6adccd7f4c4a3a7f271e19fbfbb81e16c4096143dfe7ad7df4a + languageName: node + linkType: hard + "git-raw-commits@npm:^4.0.0": version: 4.0.0 resolution: "git-raw-commits@npm:4.0.0" @@ -17306,6 +17641,34 @@ __metadata: languageName: node linkType: hard +"git-source@npm:^1.1.0": + version: 1.1.10 + resolution: "git-source@npm:1.1.10" + dependencies: + git-url-parse: ^5.0.1 + checksum: d3efbe1b32994c82827b43fa5e5de2503bf5e7f44c048b27514d6617f6e0119f4f609c899ad261c63661749867938767bff0a5180c2a3e6193ba386e497e17be + languageName: node + linkType: hard + +"git-up@npm:^1.0.0": + version: 1.2.1 + resolution: "git-up@npm:1.2.1" + dependencies: + is-ssh: ^1.0.0 + parse-url: ^1.0.0 + checksum: 45a939df02e949a1608858b472b50d7165a17521ded288365d0c5b0eb8fa157ba14a6ceaeda316ebdcf4c1c455bbd037a86cc48432723d8c094404d881357b0d + languageName: node + linkType: hard + +"git-url-parse@npm:^5.0.1": + version: 5.0.1 + resolution: "git-url-parse@npm:5.0.1" + dependencies: + git-up: ^1.0.0 + checksum: 081c37feac708e93601ab8b0c6c5b465984dd62334e0f5121ca95f4d4d22fa668f97ade78ed5fb42f3b637ab3c60785eae2e60402e5c356fae97292791d425b1 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -17550,6 +17913,29 @@ __metadata: languageName: node linkType: hard +"got@npm:^5.0.0": + version: 5.7.1 + resolution: "got@npm:5.7.1" + dependencies: + create-error-class: ^3.0.1 + duplexer2: ^0.1.4 + is-redirect: ^1.0.0 + is-retry-allowed: ^1.0.0 + is-stream: ^1.0.0 + lowercase-keys: ^1.0.0 + node-status-codes: ^1.0.0 + object-assign: ^4.0.1 + parse-json: ^2.1.0 + pinkie-promise: ^2.0.0 + read-all-stream: ^3.0.0 + readable-stream: ^2.0.5 + timed-out: ^3.0.0 + unzip-response: ^1.0.2 + url-parse-lax: ^1.0.0 + checksum: bd8e0d0b28e27bc34b81ce98a7ff116b2dec77ac70dda9cf624000eca303ebff51a7c00c1113bede3038afa2e865a61e6a5a6d2c645c5adf68ac8ed5e5b8d064 + languageName: node + linkType: hard + "got@npm:^7.1.0": version: 7.1.0 resolution: "got@npm:7.1.0" @@ -17593,6 +17979,18 @@ __metadata: languageName: node linkType: hard +"gry@npm:^5.0.0": + version: 5.0.8 + resolution: "gry@npm:5.0.8" + dependencies: + abs: ^1.2.1 + exec-limiter: ^3.0.0 + one-by-one: ^3.0.0 + ul: ^5.0.0 + checksum: 969eb7a98f8e5f290e2fab9e689a2cef02099ef92abceafdc9d7074356e34e2ab275a56f148867ead9d7db79b6dc7458a8208b805c4a9dbd43b5a406d5402d7c + languageName: node + linkType: hard + "gzip-size@npm:^6.0.0": version: 6.0.0 resolution: "gzip-size@npm:6.0.0" @@ -18309,6 +18707,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 + languageName: node + linkType: hard + "injectpromise@npm:^1.0.0": version: 1.0.0 resolution: "injectpromise@npm:1.0.0" @@ -18725,6 +19130,13 @@ __metadata: languageName: node linkType: hard +"is-redirect@npm:^1.0.0": + version: 1.0.0 + resolution: "is-redirect@npm:1.0.0" + checksum: 25dd3d9943f57ef0f29d28e2d9deda8288e0c7098ddc65abec3364ced9a6491ea06cfaf5110c61fc40ec1fde706b73cee5d171f85278edbf4e409b85725bfea7 + languageName: node + linkType: hard + "is-reference@npm:1.2.1": version: 1.2.1 resolution: "is-reference@npm:1.2.1" @@ -18760,6 +19172,15 @@ __metadata: languageName: node linkType: hard +"is-ssh@npm:^1.0.0, is-ssh@npm:^1.3.0": + version: 1.4.0 + resolution: "is-ssh@npm:1.4.0" + dependencies: + protocols: ^2.0.1 + checksum: 75eaa17b538bee24b661fbeb0f140226ac77e904a6039f787bea418431e2162f1f9c4c4ccad3bd169e036cd701cc631406e8c505d9fa7e20164e74b47f86f40f + languageName: node + linkType: hard + "is-stream@npm:^1.0.0, is-stream@npm:^1.1.0": version: 1.1.0 resolution: "is-stream@npm:1.1.0" @@ -19001,6 +19422,13 @@ __metadata: languageName: node linkType: hard +"iterate-object@npm:^1.1.0": + version: 1.3.4 + resolution: "iterate-object@npm:1.3.4" + checksum: b63496c489177babccb4b487322279ea4377e08d02b93902c3ffba3032a788f014a74e03c615da2a24807fa3fca872f69c9570f7801c8e88181df7a49298904b + languageName: node + linkType: hard + "jackspeak@npm:^2.3.5": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" @@ -19905,6 +20333,15 @@ __metadata: languageName: node linkType: hard +"limit-it@npm:^3.0.0": + version: 3.2.10 + resolution: "limit-it@npm:3.2.10" + dependencies: + typpy: ^2.0.0 + checksum: 3a809aad23f191d2abd469d835829391c8e85074de109a25af44c2d939c41fc92643ab8190f362191bd9157119f32574cebbc7eef6e99695f354a01f7b2a7b76 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -20551,6 +20988,19 @@ __metadata: languageName: node linkType: hard +"merkletreejs@npm:^0.3.9": + version: 0.3.11 + resolution: "merkletreejs@npm:0.3.11" + dependencies: + bignumber.js: ^9.0.1 + buffer-reverse: ^1.0.1 + crypto-js: ^4.2.0 + treeify: ^1.1.0 + web3-utils: ^1.3.4 + checksum: 93edb8ec66aa6c9f59aa0902e675590e3791692893bb3e8c1018f758367ded1bf7d6b1057ccde3729b283ca946a316838a631a922960b76889b3be528c43ef4e + languageName: node + linkType: hard + "methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -21504,6 +21954,13 @@ __metadata: languageName: node linkType: hard +"node-status-codes@npm:^1.0.0": + version: 1.0.0 + resolution: "node-status-codes@npm:1.0.0" + checksum: 10fe52de31cc94536aa49a2a8a28e39a880d02832ac268e7edd2b082292232abcaa8e44fe4a318d072a08ce114851fab269ab8d7f9527bd5609aebaf2bb6df17 + languageName: node + linkType: hard + "nodemon@npm:^3.1.4": version: 3.1.4 resolution: "nodemon@npm:3.1.4" @@ -21538,6 +21995,13 @@ __metadata: languageName: node linkType: hard +"noop6@npm:^1.0.1": + version: 1.0.9 + resolution: "noop6@npm:1.0.9" + checksum: cc46d03eb22c5a990b3ce5e3ff71a82628efadcdeefdae566639657d2a947033ea919d1f4bfc7bfbcf674d43c21b3e580c2f07c8043e7ef487ac527237e2532f + languageName: node + linkType: hard + "nopt@npm:^5.0.0": version: 5.0.0 resolution: "nopt@npm:5.0.0" @@ -21560,7 +22024,7 @@ __metadata: languageName: node linkType: hard -"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.5.0": +"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.3.5, normalize-package-data@npm:^2.5.0": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" dependencies: @@ -21693,6 +22157,16 @@ __metadata: languageName: node linkType: hard +"oargv@npm:^3.4.1": + version: 3.4.10 + resolution: "oargv@npm:3.4.10" + dependencies: + iterate-object: ^1.1.0 + ul: ^5.0.0 + checksum: f713a1995da354236ec87e8f4dcc21326fe3289129a2c3731b868432b845043ffc4a85ce390c734e25c99e2a8650b745b25282c5b4dffc61496d7f7432dd924d + languageName: node + linkType: hard + "oauth-sign@npm:~0.9.0": version: 0.9.0 resolution: "oauth-sign@npm:0.9.0" @@ -21700,6 +22174,15 @@ __metadata: languageName: node linkType: hard +"obj-def@npm:^1.0.0": + version: 1.0.9 + resolution: "obj-def@npm:1.0.9" + dependencies: + deffy: ^2.2.2 + checksum: 93e28098d186a5609968bf0aed4d0816f0e5baf0354093a6f79687d9e419bd26c38ff6a5674b009ed22987634cb915936cf077f5180a5dbd9e824c80a74c0064 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -21866,6 +22349,16 @@ __metadata: languageName: node linkType: hard +"one-by-one@npm:^3.0.0, one-by-one@npm:^3.1.0": + version: 3.2.8 + resolution: "one-by-one@npm:3.2.8" + dependencies: + obj-def: ^1.0.0 + sliced: ^1.0.1 + checksum: 0af8cef27306172a67423001a1b8b242115ab733d18890275c9cd4adc8ec3414f0b453395915ca8eaddffbbbf71a68e74d16eded362dbd8fac22365af81277e1 + languageName: node + linkType: hard + "one-time@npm:^1.0.0": version: 1.0.0 resolution: "one-time@npm:1.0.0" @@ -21983,6 +22476,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.1": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "ow@npm:0.17.0": version: 0.17.0 resolution: "ow@npm:0.17.0" @@ -22127,6 +22627,38 @@ __metadata: languageName: node linkType: hard +"package-json-path@npm:^1.0.0": + version: 1.0.9 + resolution: "package-json-path@npm:1.0.9" + dependencies: + abs: ^1.2.1 + checksum: 4528ba905217628e5abf8bf843e7a5c6a9bb368068105ecca8c9d7d925922b5b9f982295710b0df63e1a51705b8077df449252a9fc17765e3b7e0358ae476860 + languageName: node + linkType: hard + +"package-json@npm:^2.3.1": + version: 2.4.0 + resolution: "package-json@npm:2.4.0" + dependencies: + got: ^5.0.0 + registry-auth-token: ^3.0.1 + registry-url: ^3.0.3 + semver: ^5.1.0 + checksum: 0120e4e8222e796f0b7c3e23c25d02a6cbb4818f4488f97e9dcd619383d56c8a7e7d9a0e46dee6a28b56ecabdbfa07ffca0049bff268ebf0ded0e7f1d906e1a0 + languageName: node + linkType: hard + +"package.json@npm:^2.0.1": + version: 2.0.1 + resolution: "package.json@npm:2.0.1" + dependencies: + git-package-json: ^1.4.0 + git-source: ^1.1.0 + package-json: ^2.3.1 + checksum: 579c4c3d6bdeb67e6e501bbaca82ee0a8c9590bf23c6da9eec8f7c2a4dcb3dc0b5dce9ce90ccfcde4c235c8269e1cebd84693d5123d16e5c1a0a22152579f6b6 + languageName: node + linkType: hard + "pact-lang-api@npm:^4.3.6": version: 4.3.6 resolution: "pact-lang-api@npm:4.3.6" @@ -22211,7 +22743,7 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^2.2.0": +"parse-json@npm:^2.1.0, parse-json@npm:^2.2.0": version: 2.2.0 resolution: "parse-json@npm:2.2.0" dependencies: @@ -22239,6 +22771,16 @@ __metadata: languageName: node linkType: hard +"parse-url@npm:^1.0.0": + version: 1.3.11 + resolution: "parse-url@npm:1.3.11" + dependencies: + is-ssh: ^1.3.0 + protocols: ^1.4.0 + checksum: 33e36566ed248cc8289a35c6c094f98cf68ee42735bd971f9696db4e75d4d85889e4cefecb4fbfab1da4990854e0a5b548434726aede7b9c72714d9dc45736c7 + languageName: node + linkType: hard + "parse5-htmlparser2-tree-adapter@npm:^6.0.0": version: 6.0.1 resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1" @@ -23473,6 +24015,20 @@ __metadata: languageName: node linkType: hard +"protocols@npm:^1.4.0": + version: 1.4.8 + resolution: "protocols@npm:1.4.8" + checksum: 2d555c013df0b05402970f67f7207c9955a92b1d13ffa503c814b5fe2f6dde7ac6a03320e0975c1f5832b0113327865e0b3b28bfcad023c25ddb54b53fab8684 + languageName: node + linkType: hard + +"protocols@npm:^2.0.1": + version: 2.0.1 + resolution: "protocols@npm:2.0.1" + checksum: 4a9bef6aa0449a0245ded319ac3cbfd032c3e76ebb562777037a3a832c99253d0e8bc2847f7be350236df620a11f7d4fe683ea7f59a2cc14c69f746b6259eda4 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -23717,6 +24273,25 @@ __metadata: languageName: node linkType: hard +"r-json@npm:^1.2.1": + version: 1.3.0 + resolution: "r-json@npm:1.3.0" + dependencies: + w-json: 1.3.10 + checksum: 9a2aa9b92a2f4b7932c7eb45175d9c7ff078e322eecaf1ca2c9cdda346ea68e73062004c1b3631a9127e84eedf982fc816110f0c7a1d07c6b2b3344f6d621791 + languageName: node + linkType: hard + +"r-package-json@npm:^1.0.0": + version: 1.0.9 + resolution: "r-package-json@npm:1.0.9" + dependencies: + package-json-path: ^1.0.0 + r-json: ^1.2.1 + checksum: e94b2b02e75dee37d42226656a66cacf69332324acf67743ddf4aa8300acc06bc35861c15379a4061232ca40d40d76f5a1beb82fe18b9e5d6225e2b722a3a138 + languageName: node + linkType: hard + "railroad-diagrams@npm:^1.0.0": version: 1.0.0 resolution: "railroad-diagrams@npm:1.0.0" @@ -23816,6 +24391,20 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.0.1, rc@npm:^1.1.6": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -23855,6 +24444,16 @@ __metadata: languageName: node linkType: hard +"read-all-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "read-all-stream@npm:3.1.0" + dependencies: + pinkie-promise: ^2.0.0 + readable-stream: ^2.0.0 + checksum: ff7bf7c5484dcb6e857d9eccc388d2d32674e538c88cad61703e3d8ae9386a7fa74e8b30b6eb82a3979ea60413e933b90238e99a59f782d7fabebb7833a5b9c4 + languageName: node + linkType: hard + "read-only-stream@npm:^2.0.0": version: 2.0.0 resolution: "read-only-stream@npm:2.0.0" @@ -23908,7 +24507,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -24059,6 +24658,25 @@ __metadata: languageName: node linkType: hard +"registry-auth-token@npm:^3.0.1": + version: 3.4.0 + resolution: "registry-auth-token@npm:3.4.0" + dependencies: + rc: ^1.1.6 + safe-buffer: ^5.0.1 + checksum: a15780726bae327a8fff4048cb6a5de03d58bc19ea9e2411322e32e4ebb59962efb669d270bdde384ed68ed7b948f5feb11469e3d0c7e50a33cc8866710f0bc2 + languageName: node + linkType: hard + +"registry-url@npm:^3.0.3": + version: 3.1.0 + resolution: "registry-url@npm:3.1.0" + dependencies: + rc: ^1.0.1 + checksum: 6d223da41b04e1824f5faa63905c6f2e43b216589d72794111573f017352b790aef42cd1f826463062f89d804abb2027e3d9665d2a9a0426a11eedd04d470af3 + languageName: node + linkType: hard + "regjsgen@npm:^0.6.0": version: 0.6.0 resolution: "regjsgen@npm:0.6.0" @@ -24847,7 +25465,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.1.0, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" bin: @@ -25305,6 +25923,13 @@ __metadata: languageName: node linkType: hard +"sliced@npm:^1.0.1": + version: 1.0.1 + resolution: "sliced@npm:1.0.1" + checksum: 84528d23279985ead75809eeec5d601b0fb6bc28348c6627f4feb40747533a1e36a75e8bc60f9079528079b21c434890b397e8fc5c24a649165cc0bbe90b4d70 + languageName: node + linkType: hard + "smart-buffer@npm:^4.1.0, smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -25932,6 +26557,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "stubborn-fs@npm:^1.2.5": version: 1.2.5 resolution: "stubborn-fs@npm:1.2.5" @@ -26351,6 +26983,13 @@ __metadata: languageName: node linkType: hard +"timed-out@npm:^3.0.0": + version: 3.1.3 + resolution: "timed-out@npm:3.1.3" + checksum: 7952bcc926fd43f3206f7cb9f27dc54912a48c08058da0208d33dabef35b6759d9f1d2d14976c529d2209bd92a109bb057d377211fd2d2d962f4ca0a8d3f15f0 + languageName: node + linkType: hard + "timed-out@npm:^4.0.0, timed-out@npm:^4.0.1": version: 4.0.1 resolution: "timed-out@npm:4.0.1" @@ -26398,6 +27037,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:0.0.28": + version: 0.0.28 + resolution: "tmp@npm:0.0.28" + dependencies: + os-tmpdir: ~1.0.1 + checksum: 8167d2471b650f88fde1515bbf6c62d2adef07972d06531569f789863a2436c32027b6d5fbe3102ed2c430b86043dffd337db12494f1f982534862d9487e4eff + languageName: node + linkType: hard + "tmp@npm:^0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" @@ -26527,6 +27175,13 @@ __metadata: languageName: node linkType: hard +"treeify@npm:^1.1.0": + version: 1.1.0 + resolution: "treeify@npm:1.1.0" + checksum: aa00dded220c1dd052573bd6fc2c52862f09870851a284f0d3650d72bf913ba9b4f6b824f4f1ab81899bae29375f4266b07fe47cbf82343a1efa13cc09ce87af + languageName: node + linkType: hard + "triple-beam@npm:^1.3.0": version: 1.4.1 resolution: "triple-beam@npm:1.4.1" @@ -27078,6 +27733,15 @@ __metadata: languageName: node linkType: hard +"typpy@npm:^2.0.0, typpy@npm:^2.1.0, typpy@npm:^2.2.0, typpy@npm:^2.3.4": + version: 2.3.13 + resolution: "typpy@npm:2.3.13" + dependencies: + function.name: ^1.0.3 + checksum: e415782245876bd2bd069e227feb93fb0e4d93ccc4c1f10c0bd2a2ba223a92dfca18a4ed95b8c87eb226337909f4fa1509c6e962d7917f6d648e7d24b497a13e + languageName: node + linkType: hard + "u2f-api@npm:0.2.7": version: 0.2.7 resolution: "u2f-api@npm:0.2.7" @@ -27147,6 +27811,16 @@ __metadata: languageName: node linkType: hard +"ul@npm:^5.0.0": + version: 5.2.15 + resolution: "ul@npm:5.2.15" + dependencies: + deffy: ^2.2.2 + typpy: ^2.3.4 + checksum: a47735d307da2e9b25568977d8b13d5ab2845b2f7a14ad75b22022945f3879e0ecfef8b6134cb661fc2e46a2e86e3f69c44659816c422d0be50181aeaf44413d + languageName: node + linkType: hard + "ultra-runner@npm:^3.10.5": version: 3.10.5 resolution: "ultra-runner@npm:3.10.5" @@ -27330,6 +28004,13 @@ __metadata: languageName: node linkType: hard +"unzip-response@npm:^1.0.2": + version: 1.0.2 + resolution: "unzip-response@npm:1.0.2" + checksum: 09efe5d1d23a40534f5f67f268c6f4d2533cba4f2d40ae3233e50f9d7d3ee1db5503c52014f3265174517fff9fdebedd9ac6ec0c57b23a4933791d754b722187 + languageName: node + linkType: hard + "upper-case-first@npm:^1.1.0, upper-case-first@npm:^1.1.2": version: 1.1.2 resolution: "upper-case-first@npm:1.1.2" @@ -27878,6 +28559,13 @@ __metadata: languageName: node linkType: hard +"w-json@npm:1.3.10": + version: 1.3.10 + resolution: "w-json@npm:1.3.10" + checksum: 8535a207e579e616797efc4d5140acc7c0aefd11f0c9f846e6739816a2db8637d235492d86fc5c47bb2dba5821413d72b2d62df9184ee9d6e22e67b3f90d205b + languageName: node + linkType: hard + "watchpack@npm:^2.3.1": version: 2.3.1 resolution: "watchpack@npm:2.3.1" @@ -28879,7 +29567,7 @@ __metadata: languageName: node linkType: hard -"web3-utils@npm:1.10.4, web3-utils@npm:^1.10.4": +"web3-utils@npm:1.10.4, web3-utils@npm:^1.10.4, web3-utils@npm:^1.3.4": version: 1.10.4 resolution: "web3-utils@npm:1.10.4" dependencies: From c1430511c9b50a3759ce7f167d55fd4a2dfa5559 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:43:06 -0700 Subject: [PATCH 12/20] devop: solana wallet-standard connection --- .../extension/configs/rollup.config.base.mjs | 6 +- packages/extension/package.json | 3 + .../extension/src/providers/solana/index.ts | 2 +- .../extension/src/providers/solana/inject.ts | 175 +++-- .../solana/libs/wallet-standard/account.ts | 67 ++ .../solana/libs/wallet-standard/icon.ts | 3 + .../solana/libs/wallet-standard/index.ts | 1 + .../solana/libs/wallet-standard/initialize.ts | 8 + .../solana/libs/wallet-standard/register.ts | 83 +++ .../solana/libs/wallet-standard/solana.ts | 36 ++ .../solana/libs/wallet-standard/util.ts | 23 + .../solana/libs/wallet-standard/wallet.ts | 368 +++++++++++ .../solana/libs/wallet-standard/window.ts | 57 ++ .../solana/methods/btc_getBalance.ts | 39 -- .../src/providers/solana/methods/index.ts | 6 +- .../providers/solana/methods/sol_connect.ts | 89 +++ .../solana/methods/sol_signInMessage.ts | 37 ++ .../solana/methods/sol_signTransaction.ts | 44 ++ .../src/providers/solana/networks/solana.ts | 3 +- .../src/providers/solana/types/sol-network.ts | 3 +- .../solana/ui/libs/signin-message.ts | 47 ++ .../solana/ui/send-transaction/index.vue | 3 - .../providers/solana/ui/sol-connect-dapp.vue | 12 +- .../providers/solana/ui/sol-sign-message.vue | 147 ++++- .../solana/ui/sol-verify-transaction.vue | 610 ++++++++++++------ .../src/providers/solana/ui/types.ts | 20 +- packages/extension/src/scripts/inject.ts | 6 + packages/extension/src/types/provider.ts | 1 + .../extension/src/ui/provider-pages/routes.ts | 10 +- yarn.lock | 50 ++ 30 files changed, 1598 insertions(+), 361 deletions(-) create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/account.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/icon.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/index.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/initialize.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/register.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/solana.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/util.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts create mode 100644 packages/extension/src/providers/solana/libs/wallet-standard/window.ts delete mode 100644 packages/extension/src/providers/solana/methods/btc_getBalance.ts create mode 100644 packages/extension/src/providers/solana/methods/sol_connect.ts create mode 100644 packages/extension/src/providers/solana/methods/sol_signInMessage.ts create mode 100644 packages/extension/src/providers/solana/methods/sol_signTransaction.ts create mode 100644 packages/extension/src/providers/solana/ui/libs/signin-message.ts diff --git a/packages/extension/configs/rollup.config.base.mjs b/packages/extension/configs/rollup.config.base.mjs index 6f83f2904..5f26a0aeb 100644 --- a/packages/extension/configs/rollup.config.base.mjs +++ b/packages/extension/configs/rollup.config.base.mjs @@ -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" }; const enableMinification = process.env.minify === "on"; @@ -20,8 +21,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"], }), diff --git a/packages/extension/package.json b/packages/extension/package.json index a123abd39..a147886d3 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -43,6 +43,7 @@ "@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", @@ -50,6 +51,7 @@ "@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", @@ -97,6 +99,7 @@ "@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", diff --git a/packages/extension/src/providers/solana/index.ts b/packages/extension/src/providers/solana/index.ts index 918228e47..db57526e1 100644 --- a/packages/extension/src/providers/solana/index.ts +++ b/packages/extension/src/providers/solana/index.ts @@ -40,7 +40,7 @@ class SolanaProvider this.requestProvider.on("notification", (notif: any) => { this.sendNotification(JSON.stringify(notif)); }); - this.namespace = ProviderName.bitcoin; + this.namespace = ProviderName.solana; this.KeyRing = new PublicKeyRing(); } private setMiddleWares(): void { diff --git a/packages/extension/src/providers/solana/inject.ts b/packages/extension/src/providers/solana/inject.ts index 0bbcf7969..9a9ddc259 100644 --- a/packages/extension/src/providers/solana/inject.ts +++ b/packages/extension/src/providers/solana/inject.ts @@ -9,113 +9,107 @@ import { SendMessageHandler, } from "@/types/provider"; import { EnkryptWindow } from "@/types/globals"; -import { SolanaNetwork } from "./types/sol-network"; import { InternalMethods } from "@/types/messenger"; import { SettingsType } from "@/libs/settings-state/types"; - -export class Provider extends EventEmitter implements ProviderInterface { +import { Enkrypt, EnkryptSolAccount } from "./libs/wallet-standard/window"; +import type { + PublicKey, + SendOptions, + Transaction, + VersionedTransaction, +} from "@solana/web3.js"; +import type { + SolanaSignInInput, + SolanaSignInOutput, +} from "@solana/wallet-standard-features"; +import { initialize } from "./libs/wallet-standard"; +import { EnkryptWalletAccount } from "./libs/wallet-standard/account"; +import { SolSignInResponse, SolSignTransactionRequest } from "./ui/types"; + +export class Provider + extends EventEmitter + implements ProviderInterface, Enkrypt +{ connected: boolean; name: ProviderName; type: ProviderType; version: string = __VERSION__; autoRefreshOnNetworkChange = false; - networks: typeof SolanaNetwork; sendMessageHandler: SendMessageHandler; + accounts: EnkryptSolAccount[]; constructor(options: ProviderOptions) { super(); this.connected = true; this.name = options.name; this.type = options.type; - this.networks = SolanaNetwork; this.sendMessageHandler = options.sendMessageHandler; + this.accounts = []; } - - async request(request: EthereumRequest): Promise { - const res = (await this.sendMessageHandler( - this.name, - JSON.stringify(request) - )) as EthereumResponse; - return res; - } - - requestAccounts = async () => { - return this.request({ - method: "btc_requestAccounts", - }); - }; - - getAccounts = async () => { - return this.request({ - method: "btc_requestAccounts", - }); - }; - - getPublicKey = async () => { + connect( + options?: { onlyIfTrusted?: boolean | undefined } | undefined + ): Promise { return this.request({ - method: "btc_getPublicKey", + method: "sol_connect", + params: [options], + }).then((res: { address: string; pubkey: string }[]) => { + this.accounts = res; + return res; }); - }; - - getNetwork = async () => { - return this.request({ - method: "btc_getNetwork", - }); - }; - - switchNetwork = async (network: string) => { - return this.request({ - method: "btc_switchNetwork", - params: [network], - }); - }; - - getBalance = async () => { + } + disconnect(): Promise { + console.log("disconnect"); + return Promise.reject("not implemented"); + } + signAndSendTransaction( + transaction: T, + options?: SendOptions | undefined + ): Promise<{ signature: string }> { + console.log("signAndSendTransaction"); + return Promise.reject("not implemented"); + } + signAllTransactions( + transactions: T[] + ): Promise { + console.log("signAllTransactions"); + return Promise.reject("not implemented"); + } + signIn(input?: SolanaSignInInput | undefined): Promise { return this.request({ - method: "btc_getBalance", + method: "sol_signInMessage", + params: [JSON.stringify(input)], + }).then((res: SolSignInResponse) => { + const accExists = this.accounts.find( + (acc) => acc.address === res.address + ); + if (!accExists) { + this.accounts.push({ address: res.address, pubkey: res.pubkey }); + } + return res; }); - }; - - signPsbt = async (psbtHex: string, options?: any) => { + } + signMessage(options: { + address: string; + message: string; + }): Promise { return this.request({ - method: "btc_signPsbt", - params: [psbtHex, options], - }); - }; - - signMessage = async (text: string, type: string) => { + method: "sol_signMessage", + params: [JSON.stringify(options)], + }).then((res: SolSignInResponse) => res); + } + signTransaction(transaction: SolSignTransactionRequest): Promise { + console.log("signTransaction"); return this.request({ - method: "btc_signMessage", - params: [text, type], - }); - }; - - getInscriptions = async () => { - return Promise.reject("not implemented"); - }; - - sendBitcoin = async () => { - return Promise.reject("not implemented"); - }; - - sendInscription = async () => { - return Promise.reject("not implemented"); - }; - - inscribeTransfer = async () => { - return Promise.reject("not implemented"); - }; - - pushTx = async () => { - return Promise.reject("not implemented"); - }; - - signPsbts = async () => { - return Promise.reject("not implemented"); - }; - - pushPsbt = async () => { - return Promise.reject("not implemented"); - }; + method: "sol_signTransaction", + params: [JSON.stringify(transaction)], + }).then((res: string) => res); + } + async request(request: EthereumRequest): Promise { + const res = (await this.sendMessageHandler( + this.name, + JSON.stringify(request) + )) as EthereumResponse; + return res; + } isConnected(): boolean { return this.connected; @@ -130,14 +124,7 @@ const injectDocument = ( options: ProviderOptions ): void => { const provider = new Provider(options); - options - .sendMessageHandler( - ProviderName.enkrypt, - JSON.stringify({ method: InternalMethods.getSettings, params: [] }) - ) - .then((settings: SettingsType) => { - if (settings.btc.injectUnisat) document["unisat"] = provider; - }); + initialize(provider); document["enkrypt"]["providers"][options.name] = provider; }; export default injectDocument; diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/account.ts b/packages/extension/src/providers/solana/libs/wallet-standard/account.ts new file mode 100644 index 000000000..a35583be8 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/account.ts @@ -0,0 +1,67 @@ +// This is copied with modification from @wallet-standard/wallet + +import { + SolanaSignAndSendTransaction, + SolanaSignMessage, + SolanaSignTransaction, +} from "@solana/wallet-standard-features"; +import type { WalletAccount } from "@wallet-standard/base"; +import { SOLANA_CHAINS } from "./solana"; + +const chains = SOLANA_CHAINS; +const features = [ + SolanaSignAndSendTransaction, + SolanaSignTransaction, + SolanaSignMessage, +] as const; + +export class EnkryptWalletAccount implements WalletAccount { + readonly #address: WalletAccount["address"]; + readonly #publicKey: WalletAccount["publicKey"]; + readonly #chains: WalletAccount["chains"]; + readonly #features: WalletAccount["features"]; + readonly #label: WalletAccount["label"]; + readonly #icon: WalletAccount["icon"]; + + get address() { + return this.#address; + } + + get publicKey() { + return this.#publicKey.slice(); + } + + get chains() { + return this.#chains.slice(); + } + + get features() { + return this.#features.slice(); + } + + get label() { + return this.#label; + } + + get icon() { + return this.#icon; + } + + constructor({ + address, + publicKey, + label, + icon, + }: Omit) { + if (new.target === EnkryptWalletAccount) { + Object.freeze(this); + } + + this.#address = address; + this.#publicKey = publicKey; + this.#chains = chains; + this.#features = features; + this.#label = label; + this.#icon = icon; + } +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/icon.ts b/packages/extension/src/providers/solana/libs/wallet-standard/icon.ts new file mode 100644 index 000000000..d5952dbc7 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/icon.ts @@ -0,0 +1,3 @@ +import type { WalletIcon } from "@wallet-standard/base"; +export const icon: WalletIcon = + "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODEiIGhlaWdodD0iODEiIHZpZXdCb3g9IjAgMCA4MSA4MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNy4wMDU3IDE3LjAwNjJDMTguOTMwMyAxNS4wODE2IDIxLjU0MDUgMTQuMDAwNCAyNC4yNjIyIDE0LjAwMDRMNjcuMzI5NiAxNFYyMS44NzQxQzY3LjMyOTYgMjMuODMwNSA2Ni41NTIzIDI1LjcwNjcgNjUuMTY5IDI3LjA5QzYzLjc4NTcgMjguNDczMyA2MS45MDk1IDI5LjI1MDQgNTkuOTUzMiAyOS4yNTA0SDM5LjcwNDVDMzYuOTgyOCAyOS4yNTA0IDM0LjM3MjYgMzAuMzMxNiAzMi40NDggMzIuMjU2MUMzMC41MjM1IDM0LjE4MDcgMjkuNDQyMyAzNi43OTA5IDI5LjQ0MjMgMzkuNTEyNlY0Mi4xMjQyQzI5LjQ0MjMgNDQuODQ1OSAzMC41MjM1IDQ3LjQ1NjEgMzIuNDQ4IDQ5LjM4MDZDMzQuMzcyNiA1MS4zMDUxIDM2Ljk4MjggNTIuMzg2MyAzOS43MDQ1IDUyLjM4NjNINTkuOTUzMkM2MS45MDk1IDUyLjM4NjMgNjMuNzg1NyA1My4xNjM1IDY1LjE2OSA1NC41NDY4QzY2LjU1MjMgNTUuOTMwMSA2Ny4zMjk2IDU3LjgwNjMgNjcuMzI5NiA1OS43NjI2VjY3LjMzSDI0LjI2MjJDMjEuNTQwNSA2Ny4zMyAxOC45MzAzIDY2LjI0ODggMTcuMDA1NyA2NC4zMjQzQzE1LjA4MTIgNjIuMzk5NyAxNCA1OS43ODk1IDE0IDU3LjA2NzhWMjQuMjYyNkMxNCAyMS41NDA5IDE1LjA4MTIgMTguOTMwNyAxNy4wMDU3IDE3LjAwNjJaTTQwLjE0NzkgMzMuNTQyM0g2MC45MTU3QzY0LjQ1OCAzMy41NDIzIDY3LjMyOTUgMzYuNDEzOCA2Ny4zMjk1IDM5Ljk1NjFWNDEuNjgxNkM2Ny4zMjk1IDQ1LjIyMzggNjQuNDU4IDQ4LjA5NTQgNjAuOTE1NyA0OC4wOTU0SDQwLjE0NzlDMzYuNjA1NyA0OC4wOTU0IDMzLjczNDEgNDUuMjIzOCAzMy43MzQxIDQxLjY4MTZWMzkuOTU2MUMzMy43MzQxIDM2LjQxMzggMzYuNjA1NyAzMy41NDIzIDQwLjE0NzkgMzMuNTQyM1oiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8yODdfMjM1OSkiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8yODdfMjM1OSIgeDE9IjE5LjM2MDIiIHkxPSIxNCIgeDI9IjU2Ljc2OTYiIHkyPSI2OS44MDA1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNDNTQ5RkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjU0QkZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/index.ts b/packages/extension/src/providers/solana/libs/wallet-standard/index.ts new file mode 100644 index 000000000..048cfe5bb --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/index.ts @@ -0,0 +1 @@ +export * from "./initialize"; diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/initialize.ts b/packages/extension/src/providers/solana/libs/wallet-standard/initialize.ts new file mode 100644 index 000000000..a361ca724 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/initialize.ts @@ -0,0 +1,8 @@ +import { registerWallet } from "./register"; +import { EnkryptWallet } from "./wallet"; +import type { Enkrypt } from "./window"; + +export function initialize(enkrypt: Enkrypt): void { + console.log(new EnkryptWallet(enkrypt)); + registerWallet(new EnkryptWallet(enkrypt)); +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/register.ts b/packages/extension/src/providers/solana/libs/wallet-standard/register.ts new file mode 100644 index 000000000..900ffadb9 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/register.ts @@ -0,0 +1,83 @@ +// This is copied from @wallet-standard/wallet + +import type { + DEPRECATED_WalletsWindow, + Wallet, + WalletEventsWindow, + WindowRegisterWalletEvent, + WindowRegisterWalletEventCallback, +} from "@wallet-standard/base"; + +export function registerWallet(wallet: Wallet): void { + const callback: WindowRegisterWalletEventCallback = ({ register }) => + register(wallet); + try { + (window as WalletEventsWindow).dispatchEvent( + new RegisterWalletEvent(callback) + ); + } catch (error) { + console.error( + "wallet-standard:register-wallet event could not be dispatched\n", + error + ); + } + try { + (window as WalletEventsWindow).addEventListener( + "wallet-standard:app-ready", + ({ detail: api }) => callback(api) + ); + } catch (error) { + console.error( + "wallet-standard:app-ready event listener could not be added\n", + error + ); + } +} + +class RegisterWalletEvent extends Event implements WindowRegisterWalletEvent { + readonly #detail: WindowRegisterWalletEventCallback; + + get detail() { + return this.#detail; + } + + get type() { + return "wallet-standard:register-wallet" as const; + } + + constructor(callback: WindowRegisterWalletEventCallback) { + super("wallet-standard:register-wallet", { + bubbles: false, + cancelable: false, + composed: false, + }); + this.#detail = callback; + } + + /** @deprecated */ + preventDefault(): never { + throw new Error("preventDefault cannot be called"); + } + + /** @deprecated */ + stopImmediatePropagation(): never { + throw new Error("stopImmediatePropagation cannot be called"); + } + + /** @deprecated */ + stopPropagation(): never { + throw new Error("stopPropagation cannot be called"); + } +} + +/** @deprecated */ +export function DEPRECATED_registerWallet(wallet: Wallet): void { + registerWallet(wallet); + try { + ((window as DEPRECATED_WalletsWindow).navigator.wallets ||= []).push( + ({ register }) => register(wallet) + ); + } catch (error) { + console.error("window.navigator.wallets could not be pushed\n", error); + } +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts b/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts new file mode 100644 index 000000000..0763451f6 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts @@ -0,0 +1,36 @@ +// This is copied from @solana/wallet-standard-chains + +import type { IdentifierString } from "@wallet-standard/base"; +import type { Transaction, VersionedTransaction } from "@solana/web3.js"; + +/** Solana Mainnet (beta) cluster, e.g. https://api.mainnet-beta.solana.com */ +export const SOLANA_MAINNET_CHAIN = "solana:mainnet"; + +/** Solana Devnet cluster, e.g. https://api.devnet.solana.com */ +export const SOLANA_DEVNET_CHAIN = "solana:devnet"; + +/** Solana Testnet cluster, e.g. https://api.testnet.solana.com */ +export const SOLANA_TESTNET_CHAIN = "solana:testnet"; + +/** Array of all Solana clusters */ +export const SOLANA_CHAINS = [ + SOLANA_MAINNET_CHAIN, + SOLANA_DEVNET_CHAIN, + SOLANA_TESTNET_CHAIN, +] as const; + +/** Type of all Solana clusters */ +export type SolanaChain = (typeof SOLANA_CHAINS)[number]; + +/** + * Check if a chain corresponds with one of the Solana clusters. + */ +export function isSolanaChain(chain: IdentifierString): chain is SolanaChain { + return SOLANA_CHAINS.includes(chain as SolanaChain); +} + +export function isVersionedTransaction( + transaction: Transaction | VersionedTransaction +): transaction is VersionedTransaction { + return "version" in transaction; +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/util.ts b/packages/extension/src/providers/solana/libs/wallet-standard/util.ts new file mode 100644 index 000000000..efa84cbab --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/util.ts @@ -0,0 +1,23 @@ +// This is copied from @wallet-standard/wallet + +export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { + return arraysEqual(a, b); +} + +interface Indexed { + length: number; + [index: number]: T; +} + +export function arraysEqual(a: Indexed, b: Indexed): boolean { + if (a === b) return true; + + const length = a.length; + if (length !== b.length) return false; + + for (let i = 0; i < length; i++) { + if (a[i] !== b[i]) return false; + } + + return true; +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts b/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts new file mode 100644 index 000000000..398becc9a --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts @@ -0,0 +1,368 @@ +import { + SolanaSignAndSendTransaction, + type SolanaSignAndSendTransactionFeature, + type SolanaSignAndSendTransactionMethod, + type SolanaSignAndSendTransactionOutput, + SolanaSignIn, + type SolanaSignInFeature, + type SolanaSignInMethod, + type SolanaSignInOutput, + SolanaSignMessage, + type SolanaSignMessageFeature, + type SolanaSignMessageMethod, + type SolanaSignMessageOutput, + SolanaSignTransaction, + type SolanaSignTransactionFeature, + type SolanaSignTransactionMethod, + type SolanaSignTransactionOutput, +} from "@solana/wallet-standard-features"; +import { Transaction, VersionedTransaction } from "@solana/web3.js"; +import type { Wallet } from "@wallet-standard/base"; +import { + StandardConnect, + type StandardConnectFeature, + type StandardConnectMethod, + StandardDisconnect, + type StandardDisconnectFeature, + type StandardDisconnectMethod, + StandardEvents, + type StandardEventsFeature, + type StandardEventsListeners, + type StandardEventsNames, + type StandardEventsOnMethod, +} from "@wallet-standard/features"; +import { EnkryptWalletAccount } from "./account.js"; +import { icon } from "./icon.js"; +import type { SolanaChain } from "./solana.js"; +import { + isSolanaChain, + isVersionedTransaction, + SOLANA_CHAINS, +} from "./solana.js"; +import { bytesEqual } from "./util.js"; +import type { Enkrypt } from "./window.js"; + +export const EnkryptNamespace = "enkrypt:"; + +export type EnkryptFeature = { + [EnkryptNamespace]: { + enkrypt: Enkrypt; + }; +}; +const hexToUint8Array = (hexString: string) => { + hexString = hexString.replace("0x", ""); + return Uint8Array.from( + hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)) + ); +}; +const uint8ArrayToHex = (buffer: Uint8Array) => { + const str = Array.prototype.map + .call(buffer, (x) => ("00" + x.toString(16)).slice(-2)) + .join(""); + return `0x${str}`; +}; + +export class EnkryptWallet implements Wallet { + readonly #listeners: { + [E in StandardEventsNames]?: StandardEventsListeners[E][]; + } = {}; + readonly #version = "1.0.0" as const; + readonly #name = "Enkrypt" as const; + readonly #icon = icon; + #accounts: EnkryptWalletAccount[] | null = null; + readonly #enkrypt: Enkrypt; + + get version() { + return this.#version; + } + + get name() { + return this.#name; + } + + get icon() { + return this.#icon; + } + + get chains() { + return SOLANA_CHAINS.slice(); + } + + get features(): StandardConnectFeature & + StandardDisconnectFeature & + StandardEventsFeature & + SolanaSignAndSendTransactionFeature & + SolanaSignTransactionFeature & + SolanaSignMessageFeature & + SolanaSignInFeature & + EnkryptFeature { + return { + [StandardConnect]: { + version: "1.0.0", + connect: this.#connect, + }, + [StandardDisconnect]: { + version: "1.0.0", + disconnect: this.#disconnect, + }, + [StandardEvents]: { + version: "1.0.0", + on: this.#on, + }, + [SolanaSignAndSendTransaction]: { + version: "1.0.0", + supportedTransactionVersions: ["legacy", 0], + signAndSendTransaction: this.#signAndSendTransaction, + }, + [SolanaSignTransaction]: { + version: "1.0.0", + supportedTransactionVersions: ["legacy", 0], + signTransaction: this.#signTransaction, + }, + [SolanaSignMessage]: { + version: "1.0.0", + signMessage: this.#signMessage, + }, + [SolanaSignIn]: { + version: "1.0.0", + signIn: this.#signIn, + }, + [EnkryptNamespace]: { + enkrypt: this.#enkrypt, + }, + }; + } + get accounts() { + return this.#accounts && this.#accounts.length ? this.#accounts : []; + } + + constructor(enkrypt: Enkrypt) { + if (new.target === EnkryptWallet) { + Object.freeze(this); + } + this.#enkrypt = enkrypt; + enkrypt.on("connect", this.#connected, this); + enkrypt.on("disconnect", this.#disconnected, this); + enkrypt.on("accountChanged", this.#reconnected, this); + this.#connected(); + } + + #on: StandardEventsOnMethod = (event, listener) => { + this.#listeners[event]?.push(listener) || + (this.#listeners[event] = [listener]); + return (): void => this.#off(event, listener); + }; + + #emit( + event: E, + ...args: Parameters + ): void { + // eslint-disable-next-line prefer-spread + this.#listeners[event]?.forEach((listener) => listener.apply(null, args)); + } + + #off( + event: E, + listener: StandardEventsListeners[E] + ): void { + this.#listeners[event] = this.#listeners[event]?.filter( + (existingListener) => listener !== existingListener + ); + } + + #connected = () => { + if (this.#enkrypt.accounts.length) { + if (this.#accounts?.length !== this.#enkrypt.accounts.length) { + this.#accounts = this.#enkrypt.accounts.map((acc) => { + return new EnkryptWalletAccount({ + address: acc.address, + publicKey: hexToUint8Array(acc.pubkey), + }); + }); + this.#emit("change", { accounts: this.accounts }); + } + } + }; + + #disconnected = () => { + if (this.#accounts?.length) { + this.#accounts = null; + this.#emit("change", { accounts: this.accounts }); + } + }; + + #reconnected = () => { + if (this.#enkrypt.accounts.length) { + this.#connected(); + } else { + this.#disconnected(); + } + }; + + #connect: StandardConnectMethod = async ({ silent } = {}) => { + if (!this.#accounts?.length) { + await this.#enkrypt.connect(silent ? { onlyIfTrusted: true } : undefined); + } + this.#connected(); + return { accounts: this.accounts }; + }; + + #disconnect: StandardDisconnectMethod = async () => { + await this.#enkrypt.disconnect(); + }; + + #signAndSendTransaction: SolanaSignAndSendTransactionMethod = async ( + ...inputs + ) => { + if (!this.#accounts?.length) throw new Error("not connected"); + console.log(inputs); + throw new Error("not implemented: signAndSendTransaction"); + // const outputs: SolanaSignAndSendTransactionOutput[] = []; + + // if (inputs.length === 1) { + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // const { transaction, account, chain, options } = inputs[0]!; + // const { minContextSlot, preflightCommitment, skipPreflight, maxRetries } = + // options || {}; + // if (account !== this.#account) throw new Error("invalid account"); + // if (!isSolanaChain(chain)) throw new Error("invalid chain"); + + // const { signature } = await this.#enkrypt.signAndSendTransaction( + // VersionedTransaction.deserialize(transaction), + // { + // preflightCommitment, + // minContextSlot, + // maxRetries, + // skipPreflight, + // } + // ); + + // outputs.push({ signature: bs58.decode(signature) }); + // } else if (inputs.length > 1) { + // for (const input of inputs) { + // outputs.push(...(await this.#signAndSendTransaction(input))); + // } + // } + + // return outputs; + }; + + #signTransaction: SolanaSignTransactionMethod = async (...inputs) => { + if (!this.#accounts?.length) throw new Error("not connected"); + const outputs: SolanaSignTransactionOutput[] = []; + + if (inputs.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { transaction, account, chain } = inputs[0]!; + const validAccount = this.#accounts.find( + (acc) => acc.address === account.address + ); + if (!validAccount) throw new Error("invalid account"); + if (chain && !isSolanaChain(chain)) throw new Error("invalid chain"); + + const signedTransaction = await this.#enkrypt.signTransaction({ + address: account.address, + hex: uint8ArrayToHex(transaction), + chain: chain, + }); + + // const serializedTransaction = isVersionedTransaction(signedTransaction) + // ? signedTransaction.serialize() + // : new Uint8Array( + // (signedTransaction as Transaction).serialize({ + // requireAllSignatures: false, + // verifySignatures: false, + // }) + // ); + + outputs.push({ signedTransaction: hexToUint8Array(signedTransaction) }); + } else if (inputs.length > 1) { + // let chain: SolanaChain | undefined = undefined; + // for (const input of inputs) { + // const validAccount = this.#accounts.find( + // (acc) => acc.address === input.account.address + // ); + // if (!validAccount) throw new Error("invalid account"); + // if (input.chain) { + // if (!isSolanaChain(input.chain)) throw new Error("invalid chain"); + // if (chain) { + // if (input.chain !== chain) throw new Error("conflicting chain"); + // } else { + // chain = input.chain; + // } + // } + // } + // const transactions = inputs.map(({ transaction }) => + // VersionedTransaction.deserialize(transaction) + // ); + // const signedTransactions = await this.#enkrypt.signAllTransactions( + // transactions + // ); + // outputs.push( + // ...signedTransactions.map((signedTransaction) => { + // const serializedTransaction = isVersionedTransaction( + // signedTransaction + // ) + // ? signedTransaction.serialize() + // : new Uint8Array( + // (signedTransaction as Transaction).serialize({ + // requireAllSignatures: false, + // verifySignatures: false, + // }) + // ); + // return { signedTransaction: serializedTransaction }; + // }) + // ); + } + + return outputs; + }; + + #signMessage: SolanaSignMessageMethod = async (...inputs) => { + if (!this.#accounts?.length) throw new Error("not connected"); + const outputs: SolanaSignMessageOutput[] = []; + if (inputs.length === 1) { + const { message, account } = inputs[0]!; + let isValidAccount = false; + for (const acc of this.#accounts) { + if (acc.address === account.address) { + isValidAccount = true; + break; + } + } + if (!isValidAccount) throw new Error("invalid account"); + const { signature } = await this.#enkrypt.signMessage({ + address: account.address, + message: uint8ArrayToHex(message), + }); + outputs.push({ + signedMessage: message, + signature: hexToUint8Array(signature), + }); + } else if (inputs.length > 1) { + for (const input of inputs) { + outputs.push(...(await this.#signMessage(input))); + } + } + return outputs; + }; + + #signIn: SolanaSignInMethod = async (...inputs) => { + const outputs: SolanaSignInOutput[] = []; + for (const input of inputs) { + const res = await this.#enkrypt.signIn(input); + const output = { + account: new EnkryptWalletAccount({ + address: res.address, + publicKey: hexToUint8Array(res.pubkey), + }), + signature: hexToUint8Array(res.signature), + signedMessage: hexToUint8Array(res.signedMessage), + signatureType: res.signatureType, + }; + outputs.push(output); + } + this.#connected(); + return outputs; + }; +} diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/window.ts b/packages/extension/src/providers/solana/libs/wallet-standard/window.ts new file mode 100644 index 000000000..491fc3fb6 --- /dev/null +++ b/packages/extension/src/providers/solana/libs/wallet-standard/window.ts @@ -0,0 +1,57 @@ +import type { + SolanaSignInInput, + SolanaSignInOutput, +} from "@solana/wallet-standard-features"; +import type { + SendOptions, + Transaction, + TransactionSignature, + VersionedTransaction, +} from "@solana/web3.js"; +import { + SolSignInResponse, + SolSignTransactionRequest, +} from "@/providers/solana/ui/types"; + +export interface EnkryptEvent { + connect(...args: unknown[]): unknown; + disconnect(...args: unknown[]): unknown; + accountChanged(...args: unknown[]): unknown; +} + +export interface EnkryptSolAccount { + address: string; + pubkey: string; +} + +export interface EnkryptEventEmitter { + on( + event: E, + listener: EnkryptEvent[E], + context?: any + ): void; + off( + event: E, + listener: EnkryptEvent[E], + context?: any + ): void; +} + +export interface Enkrypt extends EnkryptEventEmitter { + accounts: EnkryptSolAccount[]; + connect(options?: { onlyIfTrusted?: boolean }): Promise; + disconnect(): Promise; + signAndSendTransaction( + transaction: T, + options?: SendOptions + ): Promise<{ signature: TransactionSignature }>; + signTransaction(transaction: SolSignTransactionRequest): Promise; + signAllTransactions( + transactions: T[] + ): Promise; + signMessage(options: { + address: string; + message: string; + }): Promise; + signIn(input?: SolanaSignInInput): Promise; +} diff --git a/packages/extension/src/providers/solana/methods/btc_getBalance.ts b/packages/extension/src/providers/solana/methods/btc_getBalance.ts deleted file mode 100644 index 8e9ac1bd2..000000000 --- a/packages/extension/src/providers/solana/methods/btc_getBalance.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MiddlewareFunction } from "@enkryptcom/types"; -import { ProviderRPCRequest } from "@/types/provider"; -import { getCustomError } from "@/libs/error"; -import BitcoinProvider from ".."; -import AccountState from "../libs/accounts-state"; -const method: MiddlewareFunction = function ( - this: BitcoinProvider, - payload: ProviderRPCRequest, - res, - next -): void { - if (payload.method !== "btc_getBalance") return next(); - else { - if (!payload.options || !payload.options.domain) { - return res(getCustomError("btc_getNetwork: invalid domain")); - } - const accountsState = new AccountState(); - - accountsState - .getApprovedAddresses(payload.options!.domain) - .then((accounts) => { - if (!accounts.length) { - return res(null, ""); - } - this.network.api().then((api) => { - api - .getBalance(this.network.displayAddress(accounts[0])) - .then((bal) => { - res(null, { - confirmed: parseInt(bal), - unconfirmed: 0, - total: parseInt(bal), - }); - }); - }); - }); - } -}; -export default method; diff --git a/packages/extension/src/providers/solana/methods/index.ts b/packages/extension/src/providers/solana/methods/index.ts index 07e8bab18..00a87e4a2 100644 --- a/packages/extension/src/providers/solana/methods/index.ts +++ b/packages/extension/src/providers/solana/methods/index.ts @@ -1,3 +1,5 @@ -import btcGetBalance from "./btc_getBalance"; +import solSignInMessage from "./sol_signInMessage"; +import solConnect from "./sol_connect"; +import solSignTransaction from "./sol_signTransaction"; -export default [btcGetBalance]; +export default [solSignInMessage, solConnect, solSignTransaction]; diff --git a/packages/extension/src/providers/solana/methods/sol_connect.ts b/packages/extension/src/providers/solana/methods/sol_connect.ts new file mode 100644 index 000000000..40a3595b7 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/sol_connect.ts @@ -0,0 +1,89 @@ +import { CallbackFunction, MiddlewareFunction } from "@enkryptcom/types"; +import { ProviderRPCRequest } from "@/types/provider"; +import { WindowPromise } from "@/libs/window-promise"; +import AccountState from "../libs/accounts-state"; +import { getCustomError } from "@/libs/error"; +import SolanaProvider from ".."; +let isAccountAccessPending = false; +const pendingPromises: { + payload: ProviderRPCRequest; + res: CallbackFunction; +}[] = []; +const method: MiddlewareFunction = function ( + this: SolanaProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if (payload.method !== "sol_connect") 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 accountsState = new AccountState(); + accountsState + .getApprovedAddresses(_payload.options.domain) + .then((accounts) => { + if (accounts.length) { + _res( + null, + accounts.map((acc) => { + return { + address: this.network.displayAddress(acc), + pubkey: acc, + }; + }) + ); + handleRemainingPromises(); + } else { + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.solConnectDApp.path), + JSON.stringify({ + ..._payload, + params: [this.network.name], + }) + ) + .then(({ error, result }) => { + if (error) _res(error as any); + const accounts = JSON.parse(result || "[]"); + _res( + null, + accounts.map((acc: string) => { + return { + address: this.network.displayAddress(acc), + pubkey: acc, + }; + }) + ); + }) + .finally(handleRemainingPromises); + } + }); + } else { + _res(getCustomError("No domain set!")); + } + }; + handleAccountAccess(payload, res); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/sol_signInMessage.ts b/packages/extension/src/providers/solana/methods/sol_signInMessage.ts new file mode 100644 index 000000000..ca2b677ae --- /dev/null +++ b/packages/extension/src/providers/solana/methods/sol_signInMessage.ts @@ -0,0 +1,37 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import EthereumProvider from ".."; +import { WindowPromise } from "@/libs/window-promise"; +import { ProviderRPCRequest } from "@/types/provider"; +const method: MiddlewareFunction = function ( + this: EthereumProvider, + payload: ProviderRPCRequest, + res, + next +): void { + if ( + payload.method !== "sol_signInMessage" && + payload.method !== "sol_signMessage" + ) + return next(); + else { + if (!payload.params || payload.params.length < 1) { + return res(getCustomError("sol_signInMessage: invalid params")); + } + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.solSign.path), + JSON.stringify({ + ...payload, + params: [payload.method, payload.params[0], this.network.name], + }), + true + ) + .then(({ error, result }) => { + if (error) return res(error); + res(null, JSON.parse(result as string)); + }); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/methods/sol_signTransaction.ts b/packages/extension/src/providers/solana/methods/sol_signTransaction.ts new file mode 100644 index 000000000..33e09b9f4 --- /dev/null +++ b/packages/extension/src/providers/solana/methods/sol_signTransaction.ts @@ -0,0 +1,44 @@ +import { getCustomError } from "@/libs/error"; +import { MiddlewareFunction } from "@enkryptcom/types"; +import SolanaProvider from ".."; +import { WindowPromise } from "@/libs/window-promise"; +import bs58 from "bs58"; +import { bufferToHex } from "@enkryptcom/utils"; +import { SolSignTransactionRequest } from "../ui/types"; +const method: MiddlewareFunction = function ( + this: SolanaProvider, + payload, + res, + next +): void { + if (payload.method !== "sol_signTransaction") return next(); + else { + if (!payload.params || payload.params.length < 1) { + return res( + getCustomError("eth_sendTransaction: invalid request not enough params") + ); + } + const txMessage = JSON.parse( + payload.params[0] + ) as SolSignTransactionRequest; + this.KeyRing.getAccount(bufferToHex(bs58.decode(txMessage.address))).then( + (account) => { + const windowPromise = new WindowPromise(); + windowPromise + .getResponse( + this.getUIPath(this.UIRoutes.solSendTransaction.path), + JSON.stringify({ + ...payload, + params: [payload.params![0], account, this.network.name], + }), + true + ) + .then(({ error, result }) => { + if (error) return res(error); + res(null, JSON.parse(result as string)); + }); + } + ); + } +}; +export default method; diff --git a/packages/extension/src/providers/solana/networks/solana.ts b/packages/extension/src/providers/solana/networks/solana.ts index b9b6128b7..e16936eb1 100644 --- a/packages/extension/src/providers/solana/networks/solana.ts +++ b/packages/extension/src/providers/solana/networks/solana.ts @@ -1,4 +1,4 @@ -import { NetworkNames } from "@enkryptcom/types"; +import { CoingeckoPlatform, NetworkNames } from "@enkryptcom/types"; import { SolanaNetwork, SolanaNetworkOptions } from "../types/sol-network"; import wrapActivityHandler from "@/libs/activity-state/wrap-activity-handler"; import assetsInfoHandler from "@/providers/ethereum/libs/assets-handlers/assetinfo-mew"; @@ -21,6 +21,7 @@ const solanaOptions: SolanaNetworkOptions = { basePath: "m/44'/501'", assetsInfoHandler, NFTHandler: shNFTHandler, + coingeckoPlatform: CoingeckoPlatform.Solana, }; const bitcoin = new SolanaNetwork(solanaOptions); diff --git a/packages/extension/src/providers/solana/types/sol-network.ts b/packages/extension/src/providers/solana/types/sol-network.ts index 46a4784bd..8b732d2dc 100644 --- a/packages/extension/src/providers/solana/types/sol-network.ts +++ b/packages/extension/src/providers/solana/types/sol-network.ts @@ -3,7 +3,7 @@ import SolAPI from "@/providers/solana/libs/api"; import { AssetsType } from "@/types/provider"; import { BaseToken } from "@/types/base-token"; import { ProviderName } from "@/types/provider"; -import { NetworkNames, SignerType } from "@enkryptcom/types"; +import { CoingeckoPlatform, NetworkNames, SignerType } from "@enkryptcom/types"; import createIcon from "../libs/blockies"; import { Activity } from "@/types/activity"; import { @@ -33,6 +33,7 @@ export interface SolanaNetworkOptions { decimals: number; node: string; coingeckoID?: string; + coingeckoPlatform: CoingeckoPlatform; basePath: string; NFTHandler?: ( network: BaseNetwork, diff --git a/packages/extension/src/providers/solana/ui/libs/signin-message.ts b/packages/extension/src/providers/solana/ui/libs/signin-message.ts new file mode 100644 index 000000000..9cbf6cc00 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/libs/signin-message.ts @@ -0,0 +1,47 @@ +import { SolanaSignInInput } from "@solana/wallet-standard-features"; + +export function createSignInMessageText(input: SolanaSignInInput): string { + let message = `${input.domain} wants you to sign in with your Solana account:\n`; + message += `${input.address}`; + + if (input.statement) { + message += `\n\n${input.statement}`; + } + + const fields: string[] = []; + if (input.uri) { + fields.push(`URI: ${input.uri}`); + } + if (input.version) { + fields.push(`Version: ${input.version}`); + } + if (input.chainId) { + fields.push(`Chain ID: ${input.chainId}`); + } + if (input.nonce) { + fields.push(`Nonce: ${input.nonce}`); + } + if (input.issuedAt) { + fields.push(`Issued At: ${input.issuedAt}`); + } + if (input.expirationTime) { + fields.push(`Expiration Time: ${input.expirationTime}`); + } + if (input.notBefore) { + fields.push(`Not Before: ${input.notBefore}`); + } + if (input.requestId) { + fields.push(`Request ID: ${input.requestId}`); + } + if (input.resources) { + fields.push(`Resources:`); + for (const resource of input.resources) { + fields.push(`- ${resource}`); + } + } + if (fields.length) { + message += `\n\n${fields.join("\n")}`; + } + + return message; +} diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 0747ff2d8..2b6f1d4d6 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -168,7 +168,6 @@ import { SystemProgram, PublicKey, ComputeBudgetProgram, - TransactionInstruction, } from "@solana/web3.js"; import { createTransferInstruction, @@ -179,9 +178,7 @@ import { } from "@solana/spl-token"; import getPriorityFees from "../libs/get-priority-fees"; import bs58 from "bs58"; - import SolanaAPI from "@/providers/solana/libs/api"; -import { getSimulationComputeUnits } from "@solana-developers/helpers"; const props = defineProps({ network: { diff --git a/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue b/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue index 0dd5ec9b8..d9f8c81d6 100644 --- a/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue +++ b/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue @@ -72,18 +72,22 @@ import SelectAccountInput from "@action/components/select-account-input/index.vu import ModalAccounts from "@action/views/modal-accounts/index.vue"; import { AccountsHeaderData } from "@action/types/account"; import { EnkryptAccount } from "@enkryptcom/types"; -import { DEFAULT_BTC_NETWORK, getNetworkByName } from "@/libs/utils/networks"; +import { + DEFAULT_BTC_NETWORK, + DEFAULT_SOLANA_NETWORK, + getNetworkByName, +} from "@/libs/utils/networks"; import { WindowPromiseHandler } from "@/libs/window-promise"; -import { BitcoinNetwork } from "../types/bitcoin-network"; import { ProviderRequestOptions } from "@/types/provider"; import PublicKeyRing from "@/libs/keyring/public-keyring"; import { fromBase } from "@enkryptcom/utils"; import { getError } from "@/libs/error"; import { ErrorCodes } from "@/providers/ethereum/types"; import AccountState from "../libs/accounts-state"; +import { SolanaNetwork } from "../types/sol-network"; const windowPromise = WindowPromiseHandler(1); -const network = ref(DEFAULT_BTC_NETWORK); +const network = ref(DEFAULT_SOLANA_NETWORK); const identicon = ref(""); const displayAddress = ref(""); const accountHeaderData = ref({ @@ -108,7 +112,7 @@ onBeforeMount(async () => { const { Request, options } = await windowPromise; network.value = (await getNetworkByName( Request.value.params![0] - )) as BitcoinNetwork; + )) as SolanaNetwork; const keyring = new PublicKeyRing(); Options.value = options; const accounts = await keyring.getAccounts(network.value.signer); diff --git a/packages/extension/src/providers/solana/ui/sol-sign-message.vue b/packages/extension/src/providers/solana/ui/sol-sign-message.vue index 219ff065e..51cb0ebf6 100644 --- a/packages/extension/src/providers/solana/ui/sol-sign-message.vue +++ b/packages/extension/src/providers/solana/ui/sol-sign-message.vue @@ -28,7 +28,7 @@
- +

{{ Options.title }}

{{ Options.domain }}

@@ -59,14 +59,30 @@ import { getError } from "@/libs/error"; import { ErrorCodes } from "@/providers/ethereum/types"; import { WindowPromiseHandler } from "@/libs/window-promise"; import { onBeforeMount, ref } from "vue"; -import { DEFAULT_BTC_NETWORK, getNetworkByName } from "@/libs/utils/networks"; +import { + DEFAULT_SOLANA_NETWORK, + getNetworkByName, +} from "@/libs/utils/networks"; import { ProviderRequestOptions } from "@/types/provider"; -import { BitcoinNetwork } from "../types/bitcoin-network"; -import { EnkryptAccount } from "@enkryptcom/types"; -import { MessageSigner } from "./libs/signer"; +import { SolanaNetwork } from "../types/sol-network"; +import { EnkryptAccount, SignerType } from "@enkryptcom/types"; +import { + SolanaSignInInput, + SolanaSignInOutput, +} from "@solana/wallet-standard-features"; +import bs58 from "bs58"; +import { bufferToHex, hexToBuffer, utf8ToHex } from "@enkryptcom/utils"; +import PublicKeyRing from "@/libs/keyring/public-keyring"; +import { createSignInMessageText } from "./libs/signin-message"; +import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger"; +import { InternalMethods } from "@/types/messenger"; +import { SolSignInResponse } from "./types"; +import { isUtf8 } from "@polkadot/util"; +import { hexToUtf8 } from "web3-utils"; -const windowPromise = WindowPromiseHandler(4); -const network = ref(DEFAULT_BTC_NETWORK); +const windowPromise = WindowPromiseHandler(3); +const keyring = new PublicKeyRing(); +const network = ref(DEFAULT_SOLANA_NETWORK); const account = ref({ name: "", address: "", @@ -80,31 +96,114 @@ const Options = ref({ tabId: 0, }); +const signInMessage = ref({}); +const signMessage = ref<{ address: string; message: string }>(); const message = ref(""); -const type = ref(""); +const reqMethod = ref<"sol_signInMessage" | "sol_signMessage">( + "sol_signInMessage" +); onBeforeMount(async () => { - const { Request, options } = await windowPromise; + const { Request, Resolve, options } = await windowPromise; network.value = (await getNetworkByName( - Request.value.params![3] - )) as BitcoinNetwork; - account.value = Request.value.params![2] as EnkryptAccount; - identicon.value = network.value.identicon(account.value.address); + Request.value.params![2] + )) as SolanaNetwork; + reqMethod.value = Request.value.params![0]; + if (reqMethod.value === "sol_signInMessage") { + signInMessage.value = JSON.parse( + Request.value.params![1] + ) as SolanaSignInInput; + message.value = createSignInMessageText(signInMessage.value); + if ( + signInMessage.value.address && + network.value.isAddress(signInMessage.value.address) + ) { + const pubKey = bufferToHex(bs58.decode(signInMessage.value.address)); + keyring + .getAccount(pubKey) + .then((acc) => { + account.value = acc; + identicon.value = network.value.identicon(account.value.address); + }) + .catch((e) => { + console.log(e); + Resolve.value({ + error: getError(ErrorCodes.unauthorized), + }); + }); + } else { + keyring.getAccounts([SignerType.ed25519sol]).then((accs) => { + account.value = accs[0]; + identicon.value = network.value.identicon(account.value.address); + message.value = createSignInMessageText({ + ...signInMessage.value, + address: network.value.displayAddress(account.value.address), + }); + }); + } + } else if (reqMethod.value === "sol_signMessage") { + signMessage.value = JSON.parse(Request.value.params![1]); + message.value = isUtf8(signMessage.value!.message) + ? hexToUtf8(signMessage.value!.message) + : signMessage.value!.message; + console.log(message.value); + keyring + .getAccount(bufferToHex(bs58.decode(signMessage.value!.address))) + .then((acc) => { + account.value = acc; + identicon.value = network.value.identicon(account.value.address); + }) + .catch((e) => { + console.log(e); + Resolve.value({ + error: getError(ErrorCodes.unauthorized), + }); + }); + } Options.value = options; - message.value = Request.value.params![0]; - type.value = Request.value.params![1]; }); const approve = async () => { - const { Request, Resolve } = await windowPromise; - const msg = Request.value.params![0] as string; - MessageSigner({ - account: account.value, - network: network.value as BitcoinNetwork, - payload: Buffer.from(msg, "utf8"), - type: type.value, + const { Resolve } = await windowPromise; + sendUsingInternalMessengers({ + method: InternalMethods.sign, + params: [ + reqMethod.value === "sol_signInMessage" + ? utf8ToHex(message.value) + : signMessage.value?.message, + account.value, + ], }) - .then(Resolve.value) - .catch(Resolve.value); + .then((res) => { + if (res.error) { + return Promise.reject({ + error: res.error, + }); + } else { + const response: SolSignInResponse = { + address: bs58.encode(hexToBuffer(account.value.address)), + pubkey: account.value.address, + signature: JSON.parse(res.result!), + signedMessage: utf8ToHex(message.value), + signatureType: "ed25519", + }; + Resolve.value({ + result: JSON.stringify(response), + }); + } + }) + .catch((e) => { + Resolve.value({ + error: e.message, + }); + }); + // MessageSigner({ + // account: account.value, + // network: network.value as BitcoinNetwork, + // payload: Buffer.from(msg, "utf8"), + // type: type.value, + // }) + // .then(Resolve.value) + // .catch(Resolve.value); }; const deny = async () => { const { Resolve } = await windowPromise; diff --git a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue index 484cda6c5..4a19ef023 100644 --- a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue +++ b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue @@ -19,18 +19,16 @@

{{ - $filters.formatFloatingPointValue( - fromBase(TokenBalance, network.decimals) - ).value + TokenBalance == "~" + ? "~" + : $filters.formatFloatingPointValue(TokenBalance).value }} {{ network.currencyName }}

{{ $filters.replaceWithEllipsis( - account.address - ? network.displayAddress(account.address) - : "", + network.displayAddress(account.address), 6, 4 ) @@ -42,56 +40,79 @@

- +
-

{{ Options.title }}

-

{{ Options.domain }}

+

{{ Options.domain }}

-
- +
+

+ {{ parseFloat(item.change) < 0 ? "-" : "" }} {{ $filters.formatFloatingPointValue( - fromBase(getTotalOut.toString(), network.decimals) + Math.abs(parseFloat(item.change)) ).value }} - {{ network.currencyName }} + {{ item.name }}

+ {{ parseFloat(item.change) < 0 ? "-" : "" }} ${{ - fiatValue !== "~" - ? $filters.formatFiatValue(fiatValue).value - : fiatValue + $filters.formatFiatValue(Math.abs(parseFloat(item.usdval))) + .value }}

-
+
-

You don't have enough balance for this transaction

+

+ Warning: you will allow this DApp to spend {{ approvalAmount }} of + {{ decodedTx?.tokenName || network.currencyName }} at any time in + the future. Please proceed only if you are trust this DApp. +

-
- -
+ + +
+ Show data + +
+

Data Hex: {{ decodedTx?.dataHex || "0x" }}

+
+
+

+ {{ errorMsg }} +

+ @@ -106,9 +127,9 @@ @@ -117,52 +138,74 @@ diff --git a/packages/extension/src/providers/solana/ui/types.ts b/packages/extension/src/providers/solana/ui/types.ts index 1085e6da4..5c6438db4 100644 --- a/packages/extension/src/providers/solana/ui/types.ts +++ b/packages/extension/src/providers/solana/ui/types.ts @@ -1,6 +1,6 @@ -import { ToTokenData } from "@/ui/action/types/token"; -import { GasFeeInfo, GasPriceTypes } from "@/providers/common/types"; -import { NFTItemWithCollectionName } from "@/types/nft"; +import type { ToTokenData } from "@/ui/action/types/token"; +import type { GasFeeInfo, GasPriceTypes } from "@/providers/common/types"; +import type { NFTItemWithCollectionName } from "@/types/nft"; export interface SendTransactionDataType { from: string; @@ -21,3 +21,17 @@ export interface VerifyTransactionParams { gasPriceType: GasPriceTypes; encodedTx: string; } + +export interface SolSignInResponse { + address: string; + pubkey: string; + signature: string; + signedMessage: string; + signatureType: "ed25519"; +} + +export interface SolSignTransactionRequest { + hex: string; + address: string; + chain?: string; +} diff --git a/packages/extension/src/scripts/inject.ts b/packages/extension/src/scripts/inject.ts index 5b3186114..6a95ddda5 100644 --- a/packages/extension/src/scripts/inject.ts +++ b/packages/extension/src/scripts/inject.ts @@ -8,6 +8,7 @@ import EthereumProvider from "@/providers/ethereum/inject"; 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 { InternalMethods } from "@/types/messenger"; @@ -37,6 +38,11 @@ const loadInjectedProviders = () => { type: ProviderType.kadena, sendMessageHandler: providerSendMessage, }); + SolanaProvider(window, { + name: ProviderName.solana, + type: ProviderType.solana, + sendMessageHandler: providerSendMessage, + }); }; loadInjectedProviders(); diff --git a/packages/extension/src/types/provider.ts b/packages/extension/src/types/provider.ts index fc5a55d19..8bce232a5 100644 --- a/packages/extension/src/types/provider.ts +++ b/packages/extension/src/types/provider.ts @@ -62,6 +62,7 @@ export enum ProviderType { substrate, bitcoin, kadena, + solana, } export type SendMessageHandler = ( diff --git a/packages/extension/src/ui/provider-pages/routes.ts b/packages/extension/src/ui/provider-pages/routes.ts index a74d99f21..5bf69cf6f 100644 --- a/packages/extension/src/ui/provider-pages/routes.ts +++ b/packages/extension/src/ui/provider-pages/routes.ts @@ -5,8 +5,16 @@ import EthereumUI from "@/providers/ethereum/ui"; 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 EnkryptUI from "./enkrypt"; -const uiProviders = [EthereumUI, PolkadotUI, BitcoinUI, EnkryptUI, KadenaUI]; +const uiProviders = [ + EthereumUI, + PolkadotUI, + BitcoinUI, + EnkryptUI, + KadenaUI, + SolanaUI, +]; let uiRoutes: RouteRecordRaw[] = []; uiProviders.forEach((provider) => { uiRoutes = uiRoutes.concat(provider.routes); diff --git a/yarn.lock b/yarn.lock index cd7126f0b..a0d7239f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2792,11 +2792,13 @@ __metadata: "@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-replace": ^5.0.7 "@rollup/plugin-typescript": ^11.1.6 "@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/bs58": ^4.0.4 "@types/chrome": ^0.0.269 @@ -2818,6 +2820,7 @@ __metadata: "@vue/cli-service": ~5.0.8 "@vue/eslint-config-typescript": ^11.0.3 "@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 @@ -7439,6 +7442,20 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-json@npm:^6.1.0": + version: 6.1.0 + resolution: "@rollup/plugin-json@npm:6.1.0" + dependencies: + "@rollup/pluginutils": ^5.1.0 + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: cc018d20c80242a2b8b44fae61a968049cf31bb8406218187cc7cda35747616594e79452dd65722e7da6dd825b392e90d4599d43cd4461a02fefa2865945164e + languageName: node + linkType: hard + "@rollup/plugin-node-resolve@npm:^15.2.3": version: 15.2.3 resolution: "@rollup/plugin-node-resolve@npm:15.2.3" @@ -8068,6 +8085,16 @@ __metadata: languageName: node linkType: hard +"@solana/wallet-standard-features@npm:^1.2.0": + version: 1.2.0 + resolution: "@solana/wallet-standard-features@npm:1.2.0" + dependencies: + "@wallet-standard/base": ^1.0.1 + "@wallet-standard/features": ^1.0.3 + checksum: 03ec568f9a4a945970dda57abed4c5a9f669798b272bd7c2d6c135c8baeba8fd78a5fd6d850364dba8257eaf84b7bf8578ba039666f377206c2fd89f6093fca3 + languageName: node + linkType: hard + "@solana/web3.js@npm:1.77.3": version: 1.77.3 resolution: "@solana/web3.js@npm:1.77.3" @@ -10729,6 +10756,29 @@ __metadata: languageName: node linkType: hard +"@wallet-standard/base@npm:^0.0.0-20240703212708": + version: 0.0.0-20240802213516 + resolution: "@wallet-standard/base@npm:0.0.0-20240802213516" + checksum: e117172f5d1d71fcedcf028ebfa9e16c300873f8b0625f5937adba5b254fdb9e36b69e01151414025c8760f2283dd38261d74c0eeda4ce773d5bbb4dd2f9dd98 + languageName: node + linkType: hard + +"@wallet-standard/base@npm:^1.0.1": + version: 1.0.1 + resolution: "@wallet-standard/base@npm:1.0.1" + checksum: 8f40bee1c69d2c3c0a4c317b3069301406025f8dc1a794b4b4b6d36d2e8e1e38193e6db1c3ac729c773d380f4e2c3a7454bfd3e138708aa6821b6e2029063590 + languageName: node + linkType: hard + +"@wallet-standard/features@npm:^1.0.3": + version: 1.0.3 + resolution: "@wallet-standard/features@npm:1.0.3" + dependencies: + "@wallet-standard/base": ^1.0.1 + checksum: 000a4087d6fd70aaed7f66b118c85097f1d75aa29b962467ee7dee179d01ce009ee685552f53caaef67fdfab63d452073f6fb3bcc0471e3c88fc7e742d2c4e05 + languageName: node + linkType: hard + "@walletconnect/core@npm:2.8.6": version: 2.8.6 resolution: "@walletconnect/core@npm:2.8.6" From 90fe8a07c93546e1d1d65843b6caa336cfa426a9 Mon Sep 17 00:00:00 2001 From: kvhnuke <10602065+kvhnuke@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:12:16 -0700 Subject: [PATCH 13/20] devop: solana verify tx --- .../libs/assets-handlers/solanachain.ts | 1 + .../extension/src/providers/solana/inject.ts | 33 +- .../src/providers/solana/libs/api.ts | 32 +- .../solana/libs/wallet-standard/solana.ts | 10 +- .../solana/libs/wallet-standard/wallet.ts | 112 +---- .../solana/libs/wallet-standard/window.ts | 14 +- .../solana/methods/sol_signTransaction.ts | 14 +- .../src/providers/solana/types/sol-token.ts | 6 + .../src/providers/solana/ui/libs/decode-tx.ts | 158 ++++++ .../components/send-fee-select.vue | 5 +- .../solana/ui/send-transaction/index.vue | 2 +- .../verify-transaction/index.vue | 2 +- .../providers/solana/ui/sol-connect-dapp.vue | 1 - .../providers/solana/ui/sol-sign-message.vue | 5 +- .../solana/ui/sol-verify-transaction.vue | 475 ++++-------------- 15 files changed, 356 insertions(+), 514 deletions(-) create mode 100644 packages/extension/src/providers/solana/ui/libs/decode-tx.ts diff --git a/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts index babb42ba5..4c0e18441 100644 --- a/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts +++ b/packages/extension/src/providers/ethereum/libs/assets-handlers/solanachain.ts @@ -33,6 +33,7 @@ const getBalances = (network: BaseNetwork, address: string) => { const balance = numberToHex( (acc.account.data as any).parsed.info.tokenAmount.amount ); + if (balance === "0x0") return; const contract = (acc.account.data as any).parsed.info.mint; if (!balanceObj[contract]) balanceObj[contract] = toBN(0); balanceObj[contract] = balanceObj[contract].add(toBN(balance)); diff --git a/packages/extension/src/providers/solana/inject.ts b/packages/extension/src/providers/solana/inject.ts index 9a9ddc259..25a3022c4 100644 --- a/packages/extension/src/providers/solana/inject.ts +++ b/packages/extension/src/providers/solana/inject.ts @@ -9,21 +9,10 @@ import { SendMessageHandler, } from "@/types/provider"; import { EnkryptWindow } from "@/types/globals"; -import { InternalMethods } from "@/types/messenger"; -import { SettingsType } from "@/libs/settings-state/types"; import { Enkrypt, EnkryptSolAccount } from "./libs/wallet-standard/window"; -import type { - PublicKey, - SendOptions, - Transaction, - VersionedTransaction, -} from "@solana/web3.js"; -import type { - SolanaSignInInput, - SolanaSignInOutput, -} from "@solana/wallet-standard-features"; +import type { SendOptions } from "@solana/web3.js"; +import type { SolanaSignInInput } from "@solana/wallet-standard-features"; import { initialize } from "./libs/wallet-standard"; -import { EnkryptWalletAccount } from "./libs/wallet-standard/account"; import { SolSignInResponse, SolSignTransactionRequest } from "./ui/types"; export class Provider @@ -60,18 +49,15 @@ export class Provider console.log("disconnect"); return Promise.reject("not implemented"); } - signAndSendTransaction( - transaction: T, + signAndSendTransaction( + transaction: SolSignTransactionRequest, options?: SendOptions | undefined - ): Promise<{ signature: string }> { + ): Promise { console.log("signAndSendTransaction"); - return Promise.reject("not implemented"); - } - signAllTransactions( - transactions: T[] - ): Promise { - console.log("signAllTransactions"); - return Promise.reject("not implemented"); + return this.request({ + method: "sol_signAndSendTransaction", + params: [JSON.stringify(transaction), JSON.stringify(options)], + }).then((res: string) => res); } signIn(input?: SolanaSignInInput | undefined): Promise { return this.request({ @@ -97,7 +83,6 @@ export class Provider }).then((res: SolSignInResponse) => res); } signTransaction(transaction: SolSignTransactionRequest): Promise { - console.log("signTransaction"); return this.request({ method: "sol_signTransaction", params: [JSON.stringify(transaction)], diff --git a/packages/extension/src/providers/solana/libs/api.ts b/packages/extension/src/providers/solana/libs/api.ts index b350069f4..7445d4cdd 100644 --- a/packages/extension/src/providers/solana/libs/api.ts +++ b/packages/extension/src/providers/solana/libs/api.ts @@ -2,9 +2,9 @@ import { SOLRawInfo } from "@/types/activity"; import { ProviderAPIInterface } from "@/types/provider"; import { getAddress as getSolAddress } from "../types/sol-network"; import { Connection, PublicKey } from "@solana/web3.js"; -import { numberToHex } from "@enkryptcom/utils"; -import { ERC20TokenInfo } from "@/providers/ethereum/types"; +import { hexToBuffer, numberToHex } from "@enkryptcom/utils"; import cacheFetch from "@/libs/cache-fetch"; +import { SPLTokenInfo } from "../types/sol-token"; class API implements ProviderAPIInterface { node: string; @@ -47,15 +47,19 @@ class API implements ProviderAPIInterface { return numberToHex(balance); } async broadcastTx(rawtx: string): Promise { - console.log(rawtx, "broadcasttx"); - return true; + return this.web3 + .sendRawTransaction(hexToBuffer(rawtx)) + .then(() => true) + .catch(() => false); } - getTokenInfo = async (contractAddress: string): Promise => { + getTokenInfo = async (contractAddress: string): Promise => { interface TokenDetails { address: string; decimals: number; name: string; symbol: string; + logoURI: string; + extensions?: { coingeckoId: string }; } const allTokensResponse = await cacheFetch( { @@ -72,17 +76,33 @@ class API implements ProviderAPIInterface { 60 * 60 * 1000 ); const allTokens = allTokensResponse as Record; + let decimals = 9; if (allTokens[contractAddress]) { return { name: allTokens[contractAddress].name, symbol: allTokens[contractAddress].symbol, decimals: allTokens[contractAddress].decimals, + icon: allTokens[contractAddress].logoURI, + cgId: allTokens[contractAddress].extensions?.coingeckoId + ? allTokens[contractAddress].extensions?.coingeckoId + : undefined, }; + } else { + await this.web3 + .getParsedAccountInfo(new PublicKey(contractAddress)) + .then((info) => { + decimals = (info.value?.data as any).parsed.info.decimals; + }) + .catch(() => { + decimals = 9; + }); } return { name: "Unknown", symbol: "UNKNWN", - decimals: 9, + decimals, + icon: undefined, + cgId: undefined, }; }; } diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts b/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts index 0763451f6..95725a680 100644 --- a/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts +++ b/packages/extension/src/providers/solana/libs/wallet-standard/solana.ts @@ -6,17 +6,13 @@ import type { Transaction, VersionedTransaction } from "@solana/web3.js"; /** Solana Mainnet (beta) cluster, e.g. https://api.mainnet-beta.solana.com */ export const SOLANA_MAINNET_CHAIN = "solana:mainnet"; -/** Solana Devnet cluster, e.g. https://api.devnet.solana.com */ -export const SOLANA_DEVNET_CHAIN = "solana:devnet"; - -/** Solana Testnet cluster, e.g. https://api.testnet.solana.com */ -export const SOLANA_TESTNET_CHAIN = "solana:testnet"; +// /** Solana Devnet cluster, e.g. https://api.devnet.solana.com */ +// export const SOLANA_DEVNET_CHAIN = "solana:devnet"; /** Array of all Solana clusters */ export const SOLANA_CHAINS = [ SOLANA_MAINNET_CHAIN, - SOLANA_DEVNET_CHAIN, - SOLANA_TESTNET_CHAIN, + // SOLANA_DEVNET_CHAIN, ] as const; /** Type of all Solana clusters */ diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts b/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts index 398becc9a..3941679c5 100644 --- a/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts +++ b/packages/extension/src/providers/solana/libs/wallet-standard/wallet.ts @@ -16,7 +16,6 @@ import { type SolanaSignTransactionMethod, type SolanaSignTransactionOutput, } from "@solana/wallet-standard-features"; -import { Transaction, VersionedTransaction } from "@solana/web3.js"; import type { Wallet } from "@wallet-standard/base"; import { StandardConnect, @@ -33,13 +32,7 @@ import { } from "@wallet-standard/features"; import { EnkryptWalletAccount } from "./account.js"; import { icon } from "./icon.js"; -import type { SolanaChain } from "./solana.js"; -import { - isSolanaChain, - isVersionedTransaction, - SOLANA_CHAINS, -} from "./solana.js"; -import { bytesEqual } from "./util.js"; +import { isSolanaChain, SOLANA_CHAINS } from "./solana.js"; import type { Enkrypt } from "./window.js"; export const EnkryptNamespace = "enkrypt:"; @@ -215,45 +208,32 @@ export class EnkryptWallet implements Wallet { ...inputs ) => { if (!this.#accounts?.length) throw new Error("not connected"); - console.log(inputs); - throw new Error("not implemented: signAndSendTransaction"); - // const outputs: SolanaSignAndSendTransactionOutput[] = []; - - // if (inputs.length === 1) { - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // const { transaction, account, chain, options } = inputs[0]!; - // const { minContextSlot, preflightCommitment, skipPreflight, maxRetries } = - // options || {}; - // if (account !== this.#account) throw new Error("invalid account"); - // if (!isSolanaChain(chain)) throw new Error("invalid chain"); - - // const { signature } = await this.#enkrypt.signAndSendTransaction( - // VersionedTransaction.deserialize(transaction), - // { - // preflightCommitment, - // minContextSlot, - // maxRetries, - // skipPreflight, - // } - // ); - - // outputs.push({ signature: bs58.decode(signature) }); - // } else if (inputs.length > 1) { - // for (const input of inputs) { - // outputs.push(...(await this.#signAndSendTransaction(input))); - // } - // } - - // return outputs; + const outputs: SolanaSignAndSendTransactionOutput[] = []; + for (const input of inputs) { + const { transaction, account, chain, options } = input; + const validAccount = this.#accounts.find( + (acc) => acc.address === account.address + ); + if (!validAccount) throw new Error("invalid account"); + if (!isSolanaChain(chain)) throw new Error("invalid chain"); + const signature = await this.#enkrypt.signAndSendTransaction( + { + address: account.address, + hex: uint8ArrayToHex(transaction), + chain: chain, + }, + options || {} + ); + outputs.push({ signature: hexToUint8Array(signature) }); + } + return outputs; }; #signTransaction: SolanaSignTransactionMethod = async (...inputs) => { if (!this.#accounts?.length) throw new Error("not connected"); const outputs: SolanaSignTransactionOutput[] = []; - - if (inputs.length === 1) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { transaction, account, chain } = inputs[0]!; + for (const input of inputs) { + const { transaction, account, chain } = input; const validAccount = this.#accounts.find( (acc) => acc.address === account.address ); @@ -265,56 +245,8 @@ export class EnkryptWallet implements Wallet { hex: uint8ArrayToHex(transaction), chain: chain, }); - - // const serializedTransaction = isVersionedTransaction(signedTransaction) - // ? signedTransaction.serialize() - // : new Uint8Array( - // (signedTransaction as Transaction).serialize({ - // requireAllSignatures: false, - // verifySignatures: false, - // }) - // ); - outputs.push({ signedTransaction: hexToUint8Array(signedTransaction) }); - } else if (inputs.length > 1) { - // let chain: SolanaChain | undefined = undefined; - // for (const input of inputs) { - // const validAccount = this.#accounts.find( - // (acc) => acc.address === input.account.address - // ); - // if (!validAccount) throw new Error("invalid account"); - // if (input.chain) { - // if (!isSolanaChain(input.chain)) throw new Error("invalid chain"); - // if (chain) { - // if (input.chain !== chain) throw new Error("conflicting chain"); - // } else { - // chain = input.chain; - // } - // } - // } - // const transactions = inputs.map(({ transaction }) => - // VersionedTransaction.deserialize(transaction) - // ); - // const signedTransactions = await this.#enkrypt.signAllTransactions( - // transactions - // ); - // outputs.push( - // ...signedTransactions.map((signedTransaction) => { - // const serializedTransaction = isVersionedTransaction( - // signedTransaction - // ) - // ? signedTransaction.serialize() - // : new Uint8Array( - // (signedTransaction as Transaction).serialize({ - // requireAllSignatures: false, - // verifySignatures: false, - // }) - // ); - // return { signedTransaction: serializedTransaction }; - // }) - // ); } - return outputs; }; diff --git a/packages/extension/src/providers/solana/libs/wallet-standard/window.ts b/packages/extension/src/providers/solana/libs/wallet-standard/window.ts index 491fc3fb6..3291e359c 100644 --- a/packages/extension/src/providers/solana/libs/wallet-standard/window.ts +++ b/packages/extension/src/providers/solana/libs/wallet-standard/window.ts @@ -1,7 +1,4 @@ -import type { - SolanaSignInInput, - SolanaSignInOutput, -} from "@solana/wallet-standard-features"; +import type { SolanaSignInInput } from "@solana/wallet-standard-features"; import type { SendOptions, Transaction, @@ -41,14 +38,11 @@ export interface Enkrypt extends EnkryptEventEmitter { accounts: EnkryptSolAccount[]; connect(options?: { onlyIfTrusted?: boolean }): Promise; disconnect(): Promise; - signAndSendTransaction( - transaction: T, + signAndSendTransaction( + transaction: SolSignTransactionRequest, options?: SendOptions - ): Promise<{ signature: TransactionSignature }>; + ): Promise; signTransaction(transaction: SolSignTransactionRequest): Promise; - signAllTransactions( - transactions: T[] - ): Promise; signMessage(options: { address: string; message: string; diff --git a/packages/extension/src/providers/solana/methods/sol_signTransaction.ts b/packages/extension/src/providers/solana/methods/sol_signTransaction.ts index 33e09b9f4..2c1345d28 100644 --- a/packages/extension/src/providers/solana/methods/sol_signTransaction.ts +++ b/packages/extension/src/providers/solana/methods/sol_signTransaction.ts @@ -11,7 +11,11 @@ const method: MiddlewareFunction = function ( res, next ): void { - if (payload.method !== "sol_signTransaction") return next(); + if ( + payload.method !== "sol_signTransaction" && + payload.method !== "sol_signAndSendTransaction" + ) + return next(); else { if (!payload.params || payload.params.length < 1) { return res( @@ -29,7 +33,13 @@ const method: MiddlewareFunction = function ( this.getUIPath(this.UIRoutes.solSendTransaction.path), JSON.stringify({ ...payload, - params: [payload.params![0], account, this.network.name], + params: [ + payload.method, + payload.params![0], + payload.params![1], + account, + this.network.name, + ], }), true ) diff --git a/packages/extension/src/providers/solana/types/sol-token.ts b/packages/extension/src/providers/solana/types/sol-token.ts index 636b7befa..402b49bc1 100644 --- a/packages/extension/src/providers/solana/types/sol-token.ts +++ b/packages/extension/src/providers/solana/types/sol-token.ts @@ -1,5 +1,11 @@ import { BaseToken, BaseTokenOptions } from "@/types/base-token"; import SolanaAPI from "@/providers/bitcoin/libs/api"; +import { ERC20TokenInfo } from "@/providers/ethereum/types"; + +export interface SPLTokenInfo extends ERC20TokenInfo { + icon: string | undefined; + cgId: string | undefined; +} export interface SolTokenOptions extends BaseTokenOptions { contract: string; diff --git a/packages/extension/src/providers/solana/ui/libs/decode-tx.ts b/packages/extension/src/providers/solana/ui/libs/decode-tx.ts new file mode 100644 index 000000000..03b0d4051 --- /dev/null +++ b/packages/extension/src/providers/solana/ui/libs/decode-tx.ts @@ -0,0 +1,158 @@ +import { NATIVE_TOKEN_ADDRESS } from "@/providers/ethereum/libs/common"; +import { + ACCOUNT_SIZE, + AccountLayout, + TOKEN_PROGRAM_ID, +} from "@solana/spl-token"; +import { VersionedTransaction, PublicKey } from "@solana/web3.js"; +import SolanaAPI from "@/providers/solana/libs/api"; +import { SolanaNetwork } from "../../types/sol-network"; +import { toBN } from "web3-utils"; +import BigNumber from "bignumber.js"; +import { fromBase } from "@enkryptcom/utils"; +import MarketData from "@/libs/market-data"; + +export interface DecodedTxResponseType { + contract: string; + decimals: number; + icon: string; + change: number; + isNegative: boolean; + name: string; + symbol: string; + USDval: string; + price: string; +} +const assetFetch = ( + node: string, + method: string, + params: any +): Promise => { + return fetch(node, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "rpd-op-123", + method, + params, + }), + }).then((res) => res.json()); +}; + +const decodeTransaction = async ( + tx: VersionedTransaction, + from: PublicKey, + network: SolanaNetwork +) => { + const solAPI = (await network.api()).api as SolanaAPI; + const allBalances = await network.getAllTokenInfo(from.toBase58()); + const marketData = new MarketData(); + return solAPI.web3 + .simulateTransaction(tx, { + accounts: { + addresses: tx.message.staticAccountKeys.map((k) => k.toBase58()), + encoding: "base64", + }, + }) + .then(async (result) => { + if (result.value.err) return null; + const nativeChange = { + contract: NATIVE_TOKEN_ADDRESS, + amount: BigInt(result.value.accounts![0]!.lamports), + }; + const balanceChanges = result.value + .accounts!.filter((a) => { + const data = Buffer.from(a!.data[0], "base64"); + return ( + a!.owner === TOKEN_PROGRAM_ID.toBase58() && + data.length === ACCOUNT_SIZE + ); + }) + .map((a) => { + const data = Buffer.from(a!.data[0], "base64"); + return AccountLayout.decode(data); + }) + .filter((val) => val.owner.toBase58() === from.toBase58()) + .map((val) => { + return { + contract: val.mint.toBase58(), + amount: val.amount, + }; + }); + const getTokenInfoPromises = await Promise.all( + balanceChanges.map((val) => { + return solAPI.getTokenInfo(val.contract).then((info) => { + return { + ...info, + ...val, + }; + }); + }) + ); + getTokenInfoPromises.unshift({ + amount: nativeChange.amount, + cgId: network.coingeckoID, + contract: NATIVE_TOKEN_ADDRESS, + decimals: network.decimals, + icon: network.icon, + name: network.currencyNameLong, + symbol: network.currencyName, + }); + const retVal: DecodedTxResponseType[] = []; + for (const token of getTokenInfoPromises) { + const res: DecodedTxResponseType = { + change: 0, + contract: token.contract, + decimals: token.decimals, + icon: token.icon || "", + isNegative: false, + name: token.name, + symbol: token.symbol, + price: "0", + USDval: "0", + }; + const balInfo = allBalances.find((b) => b.contract === token.contract); + if (balInfo) { + const curBalance = toBN(balInfo.balance); + const newBalance = toBN(token.amount.toString()); + const diff = newBalance.sub(curBalance).toNumber(); + res.change = Math.abs(diff); + res.isNegative = diff < 0; + res.USDval = new BigNumber(balInfo.value) + .times(fromBase(res.change.toString(), res.decimals)) + .toString(); + res.price = balInfo.value; + } else { + res.change = toBN(token.amount.toString()).toNumber(); + res.isNegative = false; + if (token.cgId) { + const val = await marketData.getMarketData([token.cgId]); + res.USDval = new BigNumber(val[0]!.current_price) + .times(fromBase(res.change.toString(), res.decimals)) + .toString(); + res.price = val[0]!.current_price.toString(); + } + } + if (token.decimals === 0) { + const assetDetails = await assetFetch(network.node, "getAsset", { + id: token.contract, + }).catch(() => { + return null; + }); + if (assetDetails) { + res.icon = `https://img.mewapi.io/?image=${assetDetails.result.content.links.image}`; + res.name = assetDetails.result.content.metadata.name; + res.symbol = assetDetails.result.content.metadata.symbol; + } + } + retVal.push(res); + } + retVal.sort((v) => (v.isNegative ? -1 * v.change : v.change)); + return retVal; + }); +}; + +export default decodeTransaction; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue b/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue index 31fac7429..21add0fda 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue @@ -46,12 +46,13 @@ defineProps({ min-height: 40px; height: auto; background: #ffffff; - margin: 0px 32px 8px 32px; + margin-right: 32px; + margin-bottom: 8px; box-sizing: border-box; border: 1px solid @gray02; box-sizing: border-box; border-radius: 10px; - width: calc(~"100% - 64px"); + width: calc(~"100%"); padding: 10px 16px; display: flex; justify-content: flex-start; diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 2b6f1d4d6..31b59ae81 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -88,6 +88,7 @@ /> @@ -222,7 +223,6 @@ const hasEnoughBalance = computed(() => { if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { return false; } - return toBN(selectedAsset.value.balance ?? "0").gte( toBN(toBase(sendAmount.value ?? "0", selectedAsset.value.decimals!)) ); diff --git a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue index 22824a093..01b53a1a8 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/verify-transaction/index.vue @@ -240,7 +240,7 @@ const sendAction = async () => { } }; solAPI.web3 - .sendRawTransaction(Buffer.from(transaction.serialize())) + .sendRawTransaction(transaction.serialize()) .then((hash) => { onHash(hash); }) diff --git a/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue b/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue index d9f8c81d6..b489cbfc4 100644 --- a/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue +++ b/packages/extension/src/providers/solana/ui/sol-connect-dapp.vue @@ -73,7 +73,6 @@ import ModalAccounts from "@action/views/modal-accounts/index.vue"; import { AccountsHeaderData } from "@action/types/account"; import { EnkryptAccount } from "@enkryptcom/types"; import { - DEFAULT_BTC_NETWORK, DEFAULT_SOLANA_NETWORK, getNetworkByName, } from "@/libs/utils/networks"; diff --git a/packages/extension/src/providers/solana/ui/sol-sign-message.vue b/packages/extension/src/providers/solana/ui/sol-sign-message.vue index 51cb0ebf6..00206ee43 100644 --- a/packages/extension/src/providers/solana/ui/sol-sign-message.vue +++ b/packages/extension/src/providers/solana/ui/sol-sign-message.vue @@ -66,10 +66,7 @@ import { import { ProviderRequestOptions } from "@/types/provider"; import { SolanaNetwork } from "../types/sol-network"; import { EnkryptAccount, SignerType } from "@enkryptcom/types"; -import { - SolanaSignInInput, - SolanaSignInOutput, -} from "@solana/wallet-standard-features"; +import { SolanaSignInInput } from "@solana/wallet-standard-features"; import bs58 from "bs58"; import { bufferToHex, hexToBuffer, utf8ToHex } from "@enkryptcom/utils"; import PublicKeyRing from "@/libs/keyring/public-keyring"; diff --git a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue index 4a19ef023..89b4d9e22 100644 --- a/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue +++ b/packages/extension/src/providers/solana/ui/sol-verify-transaction.vue @@ -46,74 +46,50 @@
-
- +
+
+ -
-

- {{ parseFloat(item.change) < 0 ? "-" : "" }} - {{ - $filters.formatFloatingPointValue( - Math.abs(parseFloat(item.change)) - ).value - }} - {{ item.name }} -

-

- {{ parseFloat(item.change) < 0 ? "-" : "" }} - ${{ - $filters.formatFiatValue(Math.abs(parseFloat(item.usdval))) - .value - }} -

+
+

+ {{ item.isNegative ? "-" : "" }} + {{ + $filters.formatFloatingPointValue( + fromBase(item.change.toString(), item.decimals) + ).value + }} + {{ item.symbol }} +

+

+ {{ item.isNegative ? "-" : "" }} + ${{ $filters.formatFiatValue(parseFloat(item.USDval)).value }} +

+
-
+

- Warning: you will allow this DApp to spend {{ approvalAmount }} of - {{ decodedTx?.tokenName || network.currencyName }} at any time in - the future. Please proceed only if you are trust this DApp. + Warning: This transaction failed during simulation, which means this + transaction will most likely fail!

- -
- Show data - -
-

Data Hex: {{ decodedTx?.dataHex || "0x" }}

-
-

{{ errorMsg }}

-