UNPKG

@broxus/tvm-connect

Version:

Nekoton-compatible wallets connector.

595 lines (594 loc) 21.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TvmWalletService = void 0; const js_core_1 = require("@broxus/js-core"); const js_utils_1 = require("@broxus/js-utils"); const everscale_inpage_provider_1 = require("everscale-inpage-provider"); const mobx_1 = require("mobx"); const utils_1 = require("../utils"); class TvmWalletService extends js_core_1.AbstractStore { params; name = 'TvmWalletService'; constructor(params) { super(); this.params = params; this.setData(() => ({ networks: params?.networks || [], })); const hasProvider = params?.providers?.some(provider => provider.id === params.providerId); this.setState(() => ({ isSyncing: undefined, providerId: hasProvider ? params?.providerId : undefined, })); (0, mobx_1.makeObservable)(this); if (params?.autoInit ?? true) { this._init().catch(reason => { (0, js_utils_1.error)('Wallet init failed with an error', reason); }); } } /** * Define current provider connector * @returns {NekotonConnector|undefined} */ get connector() { return this.providers?.find(config => config.id === this.providerId)?.connector; } /** * Initialize the wallet connection manually * @returns {Promise<ProviderRpcClient | undefined>} */ async init() { const provider = await this.connector?.init(); await this._init(); return provider; } /** * Manually connect to the wallet * @returns {Promise<void>} */ async connect(networkIdOParams, options) { if (this.isConnecting || this.isDisconnecting) { return; } try { const defaultNetworkId = typeof networkIdOParams === 'number' ? networkIdOParams : networkIdOParams?.networkId ?? this.params?.defaultNetworkId ?? this.network?.chainId ?? js_core_1.TvmChains.EverscaleMainnet; const network = this.networks.find(_network => _network.chainId === defaultNetworkId?.toString()); // eslint-disable-next-line no-nested-ternary const networkParams = typeof networkIdOParams === 'object' ? networkIdOParams : network ? (0, utils_1.convertNetworkToChainParams)(network) : undefined; await this.connector?.connect(networkParams, options); } catch (e) { (0, js_utils_1.error)('Tvm Wallet connect error', e); throw e; } } /** * Manually disconnect from the wallet * @param {DisconnectOptions} [options] * @returns {Promise<void>} */ async disconnect(options) { if ((this.isConnecting || this.isDisconnecting) && !options?.force) { return; } try { await this.connector?.disconnect(options); this.setData('contractState', undefined); } catch (e) { (0, js_utils_1.error)('TVM Wallet disconnect error', e); } } /** * Add custom token asset to the TVM Wallet * @param {Address | string} address * @param {AssetType} [type] */ async addAsset(address, type = 'tip3_token') { return this.connector?.addAsset(address, type); } /** * Add new network to the wallet. * Supported since version `0.3.40` of the `everscale-inpage-provider` * @param {AddNetwork} network * @param {boolean} switchNetwork * @returns {Promise<Network | null>} */ async addNetwork(network, switchNetwork) { if (!this.connector) { throw new Error('Provider is not defined'); } return this.connector.addNetwork(network, switchNetwork); } /** * Switch to network by the given networkId or network params. * Supported since version `0.3.40` of the `everscale-inpage-provider` * @param {number | AddNetwork} networkIdOParams * @returns {Promise<Network | null>} */ async switchNetwork(networkIdOParams) { if (!this.connector) { throw new Error('Provider is not defined'); } return this.connector.switchNetwork(networkIdOParams); } /** * An independent RPC connection that allows you to receive data from the blockchain without being * able to send transactions. * * This connection does not require `accountInteraction` permissions. * @returns {ProviderRpcClient} */ get connection() { const params = this.params?.connectionParams; return params?.factory?.(this.network) ?? new everscale_inpage_provider_1.ProviderRpcClient({ provider: params?.provider, }); } /** * A Provider that requires a `basic` and `accountInteraction` permissions for the ability to send transactions. * @returns {ProviderRpcClient|undefined} */ get provider() { return this.connector?.provider; } /** * Returns computed wallet contract state * @returns {TvmWalletServiceData["contractState"]} */ get contract() { return this._data.contractState; } /** * The list of the supported networks * @returns {TvmWalletServiceData["networks"]} */ get networks() { return this._data.networks; } /** * Returns `true` if wallet contract is updating * @returns {TvmWalletServiceState["isSyncing"]} */ get isSyncing() { return this._state.isSyncing; } /** * A unique identifier of the connected wallet (provider) * @returns {TvmWalletServiceState["providerId"]} */ get providerId() { return this._state.providerId; } /** * Returns computed wallet address value * @returns {Address|undefined} */ get address() { return this.account?.address; } /** * Returns computed wallet normalized balance value * @returns {FullContractState["balance"]} */ get balance() { return this.contract?.balance ?? '0'; } /** * Returns network native currency * @returns {NativeCurrency<Address>} */ get currency() { return { decimals: this.network?.currency.decimals ?? js_core_1.DEFAULT_NATIVE_CURRENCY_DECIMALS, icon: this.network?.currency.icon, name: this.network?.currency.name, symbol: this.network?.currency.symbol || 'EVER', wrappedCurrencyAddress: this.network?.currency.wrappedCurrencyAddress ? (0, js_core_1.resolveTvmAddress)(this.network.currency.wrappedCurrencyAddress) : undefined, }; } /** * Whether provider is available. * * That means extension is installed and activated, else `false` * @returns {boolean} */ get hasProvider() { return this.connector?.provider != null; } /** * Returns `true` if wallet is connected * @returns {boolean} */ get isConnected() { return this.address !== undefined; } /** * Returns `true` if installed wallet has outdated version */ get isOutdated() { const providerConfig = this.params?.providers?.find(config => config.id === this.providerId); if (this.connector?.version === undefined || providerConfig?.minVersion === undefined) { return false; } const [currentMajorVersion = '0', currentMinorVersion = '0', currentPatchVersion = '0'] = this.connector.version.split('.'); const [minMajorVersion, minMinorVersion, minPatchVersion] = providerConfig.minVersion.split('.'); return (currentMajorVersion < minMajorVersion || (currentMajorVersion <= minMajorVersion && currentMinorVersion < minMinorVersion) || (currentMajorVersion <= minMajorVersion && currentMinorVersion <= minMinorVersion && currentPatchVersion < minPatchVersion)); } /** * Returns `true` if connection to RPC is initialized and connected * @returns {boolean} */ get isReady() { return (!this.isInitializing && !this.isConnecting && !this.isDisconnecting && this.isInitialized === true && this.isConnected); } /** * Checks for supported network * @returns {boolean} */ get isUnsupportedNetwork() { if (this.chainId == null) { return false; } return !(this.networks?.map(network => network.chainId)?.includes(this.chainId?.toString()) ?? false); } /** * Returns current network configuration * @returns {TvmNetworkConfig|undefined} */ get network() { const chainId = this.chainId?.toString() ?? this.params?.defaultNetworkId?.toString(); return this.networks?.find(network => network.type === 'tvm' && network.chainId === chainId); } /** * Returns details about current connected provider * @returns {TvmWalletProviderConfig["info"]|undefined} */ get providerInfo() { return this.providers?.find(config => config.id === this.providerId)?.info; } /** * The list of the supported providers * @returns {Readonly<TvmWalletServiceCtorParams["providers"]>} */ get providers() { return this.params?.providers ?? []; } /** * Returns computed account * @returns {NekotonConnector["account"]} */ get account() { return this.connector?.account; } /** * Returns current network chain id * @returns {NekotonConnector["chainId"]} */ get chainId() { return this.connector?.chainId; } /** * Returns `true` if wallet is connecting * @returns {NekotonConnector["isConnecting"]} */ get isConnecting() { return this.connector?.isConnecting; } /** * Returns `true` if wallet is disconnecting * @returns {NekotonConnector["isDisconnecting"]} */ get isDisconnecting() { return this.connector?.isDisconnecting; } /** * Returns `true` if wallet is initialized * @returns {NekotonConnector["isInitialized"]} */ get isInitialized() { return this.connector?.isInitialized; } /** * Returns `true` if wallet is initializing * @returns {NekotonConnector["isInitializing"]} */ get isInitializing() { return this.connector?.isInitializing; } async syncContractState(options) { if (!this.provider || (!options?.force && this.isSyncing)) { return; } try { if (this.account?.address) { this.setState('isSyncing', this.isSyncing === undefined); const state = await (0, js_core_1.getFullContractState)(this.provider, this.account.address, { force: options?.force }); this.setData('contractState', state); } } catch (e) { (0, js_utils_1.groupCollapsed)(`%c${this.constructor.name}%c get account full contract state error`, js_core_1.errorLabelStyle, js_core_1.inheritTextStyle); (0, js_utils_1.error)(e); (0, js_utils_1.groupEnd)(); throw e; } finally { this.setState('isSyncing', false); } } /** * Trying to resolve TVM Wallet connection * @protected */ async _init() { this.accountDisposer = (0, mobx_1.reaction)(() => this.address, async (address, prevAddress) => { if (!(0, js_core_1.isAddressesEquals)(address, prevAddress)) { this.setState('isSyncing', undefined); } if (address) { try { await this.watch(); } catch (e) { await this.unwatch(); } await this.syncContractState({ force: !this.isSyncing }); } }, { equals: mobx_1.comparer.shallow }); this.networkDisposer = (0, mobx_1.reaction)(() => this.chainId, async (chainId, prevChainId) => { if (chainId !== prevChainId || !prevChainId) { this.setState('isSyncing', undefined); if (this.address) { try { await this.watch(); } catch (e) { await this.unwatch(); } await this.syncContractState({ force: true }); } } }); } async watch() { if (!this.provider || !this.address) { await this.unwatch(); return; } await this.unwatch(); this.contractSubscriber = new this.provider.Subscriber(); await this.contractSubscriber.states(this.address).delayed(stream => { (0, js_utils_1.debug)(`%c${this.constructor.name}%c subscribes to the user wallet [%c${(0, js_utils_1.sliceAddress)(this.address?.toString())}%c] contract updates`, js_core_1.successLabelStyle, js_core_1.inheritTextStyle, js_core_1.successTextStyle, js_core_1.inheritTextStyle); return stream.on(async (e) => { (0, js_utils_1.debug)(`%c${this.constructor.name}%c %ccontractStateChanged%c event was captured for current connected wallet %c${(0, js_utils_1.sliceAddress)(this.address?.toString())}`, js_core_1.successLabelStyle, js_core_1.inheritTextStyle, js_core_1.successTextStyle, js_core_1.inheritTextStyle, js_core_1.successTextStyle, e); if ((0, js_core_1.isAddressesEquals)(this.address, e.address)) { await this.syncContractState({ force: !this.isSyncing }); } else { await this.contractSubscriber?.unsubscribe(); this.contractSubscriber = undefined; } }); }); } async unwatch() { try { await this.contractSubscriber?.unsubscribe(); (0, js_utils_1.debug)(`%c${this.constructor.name}%c unsubscribe from the user wallet [%c${(0, js_utils_1.sliceAddress)(this.address?.toString())}%c] contract updates`, js_core_1.successLabelStyle, js_core_1.inheritTextStyle, js_core_1.successTextStyle, js_core_1.inheritTextStyle); this.contractSubscriber = undefined; } catch (e) { this.contractSubscriber = undefined; } } accountDisposer; contractSubscriber; networkDisposer; } exports.TvmWalletService = TvmWalletService; __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "connector", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "init", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "connect", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "disconnect", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "addAsset", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Boolean]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "addNetwork", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "switchNetwork", null); __decorate([ mobx_1.computed, __metadata("design:type", everscale_inpage_provider_1.ProviderRpcClient), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "connection", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "provider", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "contract", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "networks", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isSyncing", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "providerId", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "address", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "balance", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "currency", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "hasProvider", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isConnected", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isOutdated", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isReady", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isUnsupportedNetwork", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "network", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "providerInfo", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "providers", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "account", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "chainId", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isConnecting", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isDisconnecting", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isInitialized", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmWalletService.prototype, "isInitializing", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "syncContractState", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "_init", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "watch", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmWalletService.prototype, "unwatch", null);