UNPKG

@broxus/tvm-connect

Version:

TypeScript SDK for connecting to Nekoton-compatible wallets using a unified interface.

652 lines (651 loc) 24.4 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.TvmConnectService = 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 misc_1 = require("../misc"); const utils_1 = require("../utils"); class TvmConnectService extends js_core_1.AbstractStore { params; name = 'TvmWalletService'; constructor(params) { super(); this.params = params; const recentMeta = (0, utils_1.getRecentConnectionMeta)(params?.recentMetaStorageKey); const networks = (!params?.networks || params.networks.length === 0) ? (0, misc_1.getPredefinedNetworks)() : params.networks; // eslint-disable-next-line max-len const providers = (!params?.providers || params.providers.length === 0) ? (0, misc_1.getPredefinedProviders)() : params.providers; this.setData(() => ({ networks, providers })); const defaultNetworkId = params?.defaultNetworkId ?? recentMeta?.chainId; const providerId = params?.providerId || (0, misc_1.getPredefinedProviderId)(recentMeta); const hasProvider = providers.some(provider => provider.id === providerId); const isNetworkSupported = networks.some(network => network.chainId === defaultNetworkId?.toString()); let chainId; if (isNetworkSupported || (params?.allowUnsupportedNetworks ?? true)) { chainId = Number(defaultNetworkId); } this.setState(() => ({ chainId, isSyncing: undefined, providerId: hasProvider ? providerId : undefined, })); (0, mobx_1.makeObservable)(this); if (params?.autoInit ?? true) { this._init().catch(reason => { (0, js_utils_1.error)(`${this.constructor.name} 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.toChainParams)(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)(`${this.connector?.constructor.name || 'Wallet'} disconnect failed with an 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); } /** * Change current account in the wallet. * @returns {Promise<void>} */ async changeAccount() { if (!this.provider) { throw new Error('Provider is not defined'); } await this.provider.changeAccount(); } /** * 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 {TvmConnectServiceData["contractState"]} */ get contract() { return this._data.contractState; } /** * The list of the supported networks * @returns {TvmConnectServiceData["networks"]} */ get networks() { return this._data.networks ?? []; } /** * The list of the supported providers * @returns {TvmConnectServiceData["providers"]} */ get providers() { return this._data.providers ?? []; } /** * Returns current network chain id * @returns {TvmConnectServiceState["chainId"]} */ get chainId() { return this._state.chainId; } /** * Returns `true` if wallet contract is updating * @returns {TvmConnectServiceState["isSyncing"]} */ get isSyncing() { return this._state.isSyncing; } /** * A unique identifier of the connected wallet (provider) * @returns {TvmConnectServiceState["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 != null; } /** * 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 {TvmWalletProviderInfo | undefined} */ get providerInfo() { return this.providers?.find(config => config.id === this.providerId)?.info ?? this.connector?.info; } /** * Returns computed account * @returns {NekotonConnector["account"]} */ get account() { return this.connector?.account; } /** * 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; } /** * Synchronizes the contract state of the current account. * * If `force` is not set, it will only sync if not already syncing. * * @param {Forceable} [options] - Options to control the sync behavior. * @returns {Promise<void>} */ 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); } } /** * Initializes the reactive logic for synchronizing the state of the contract and network. * Sets up disposers for reacting to address changes and network chain ID updates. * * @return {Promise<void>} */ 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 }); } }, { delay: 100, equals: mobx_1.comparer.shallow }); this._networkDisposer = (0, mobx_1.reaction)(() => this.connector?.chainId, async (chainId, prevChainId) => { const isSupported = this.networks?.some(network => network.chainId === chainId?.toString()); if (isSupported || (this.params?.allowUnsupportedNetworks ?? true)) { if (chainId !== prevChainId || !prevChainId) { this.setState('chainId', chainId); this.setState('isSyncing', undefined); if (this.address) { try { await this._watch(); } catch (e) { await this._unwatch(); } await this.syncContractState({ force: true }); } } } }, { delay: 100 }); } /** * Subscribe to current account address contract state updates * and update the local data while updates is coming. * @returns {Promise<void>} * @protected */ 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; } }); }); } /** * Unsubscribe from current account address contract state updates. * * @returns {Promise<void>} * @protected */ async _unwatch() { if (!this.provider || !this.address) { return; } 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.TvmConnectService = TvmConnectService; __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "connector", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "init", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "connect", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "disconnect", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "addAsset", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Boolean]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "addNetwork", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "switchNetwork", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "changeAccount", null); __decorate([ mobx_1.computed, __metadata("design:type", everscale_inpage_provider_1.ProviderRpcClient), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "connection", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "provider", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "contract", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "networks", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "providers", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "chainId", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isSyncing", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "providerId", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "address", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "balance", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "currency", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "hasProvider", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isConnected", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isOutdated", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isReady", null); __decorate([ mobx_1.computed, __metadata("design:type", Boolean), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isUnsupportedNetwork", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "network", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "providerInfo", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "account", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isConnecting", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isDisconnecting", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isInitialized", null); __decorate([ mobx_1.computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], TvmConnectService.prototype, "isInitializing", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "syncContractState", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "_init", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "_watch", null); __decorate([ mobx_1.action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], TvmConnectService.prototype, "_unwatch", null);