UNPKG

@broxus/tvm-connect

Version:

Nekoton-compatible wallets connector.

294 lines (293 loc) 11.7 kB
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); }; import { AbstractStore, errorLabelStyle, inheritTextStyle, isAddressesEquals, resolveTvmAddress, warningLabelStyle, } from '@broxus/js-core'; import { debounce, debug, error, groupCollapsed, groupEnd, timeoutPromise } from '@broxus/js-utils'; import { action, computed, makeObservable, observable } from 'mobx'; export class NekotonConnector extends AbstractStore { params; provider; constructor(params) { super(); this.params = params; makeObservable(this); } async connect(networkIdOParams, options) { if (!this.provider) { throw new Error('Provider is not defined'); } try { this.setState('isConnecting', true); await this.provider.hasProvider(); let state = await timeoutPromise(this.provider.getProviderState(), 2000).catch(() => undefined); if (state?.permissions.accountInteraction) { this.setData({ account: state.permissions.accountInteraction, chainId: state.networkId, version: state.version, }); this.setState({ isConnecting: false, isInitialized: true }); return; } await this.stopSubscriptions(); await this.runSubscriptions(); const request = await this.provider.requestPermissions({ permissions: ['basic', 'accountInteraction'], }); this.setData('account', request.accountInteraction); state = await timeoutPromise(this.provider.getProviderState(), 2000); const receivedNetworkId = state.networkId; const desiredNetworkId = typeof networkIdOParams === 'number' ? networkIdOParams : networkIdOParams?.networkId; const name = this.params?.info?.name ?? this.constructor.name ?? this.constructor.prototype.name; if (!desiredNetworkId || receivedNetworkId === desiredNetworkId) { this.setData({ chainId: state.networkId, version: state.version }); this.setState({ isConnecting: false, isInitialized: true }); debug(`${name} connection has skipped network switching.`); await options?.onConnect?.(this); return; } if (!networkIdOParams) { debug(`${name} connection has skipped network switching. Chain params or chainId is not provided.`); return; } debug(`${name} connection trying to switch network.`); await this.switchNetwork(networkIdOParams).then(async () => { await this.connect(networkIdOParams, options); }).catch(async () => { await this.connect(undefined, options); }); } catch (e) { this.setState('isConnecting', false); await this.stopSubscriptions(); throw e; } } async disconnect(options) { if ((this.isConnecting || this.isDisconnecting) && !options?.force) { return; } try { this.setState('isDisconnecting', true); await this.provider?.disconnect(); await this.stopSubscriptions(); this.setData(() => ({})); } catch (e) { groupCollapsed(`%c${this.constructor.name}%c Wallet disconnecting error`, errorLabelStyle, inheritTextStyle); error(e); groupEnd(); } finally { this.setState('isDisconnecting', false); await options?.onDisconnect?.(this); } } async addAsset(address, type = 'tip3_token') { if (!this.provider) { throw new Error('Provider is not defined'); } if (!this.account?.address) { throw new Error('No connected account'); } const result = await this.provider.addAsset({ account: this.account.address, params: { rootContract: resolveTvmAddress(address) }, type, }); return result.newAsset; } async addNetwork(network, switchNetwork) { if (!this.provider) { throw new Error('Provider is not defined'); } const result = await this.provider.addNetwork({ network, switchNetwork, }); return result.network; } async switchNetwork(networkIdOParams) { if (!this.provider) { throw new Error('Provider is not defined'); } const networkId = typeof networkIdOParams === 'number' ? networkIdOParams : networkIdOParams.networkId; const result = await this.provider.changeNetwork({ networkId }); if (result.network == null && typeof networkIdOParams !== 'number') { result.network = await this.addNetwork(networkIdOParams, true); } return result.network; } get account() { return this._data.account; } get chainId() { return this._data.chainId; } get version() { return this._data.version; } get isConnecting() { return this._state.isConnecting; } get isDisconnecting() { return this._state.isDisconnecting; } get isInitialized() { return this._state.isInitialized; } get isInitializing() { return this._state.isInitializing; } async handlePermissionsChanged(event) { const account = event.permissions.accountInteraction; if (!account) { debug(`%c${this.constructor.name}%c Permissions have been revoked. Disconnect dApp!`, warningLabelStyle, inheritTextStyle); await this.disconnect({ force: true }); return; } if (isAddressesEquals(this.account?.address, account.address)) { return; } this.setData('account', account); debug(`%c${this.constructor.name}%c Permissions have been changed`, warningLabelStyle, inheritTextStyle); } async handleNetworkChanged(event) { const chainId = event.networkId; if (this.chainId === chainId) { return; } debug(`%c${this.constructor.name}%c Network has been changed from ${this.chainId} to ${chainId}`, warningLabelStyle, inheritTextStyle); this.setData('chainId', chainId); } handleDisconnected(err) { this.setData(() => ({})); this.params?.onDisconnect?.(err); } async runSubscriptions() { debug('runSubscriptions', this.constructor.name); const [networkChangeSubscription, permissionsSubscription] = await Promise.all([ this.provider?.subscribe('networkChanged'), this.provider?.subscribe('permissionsChanged'), ]); this.networkChangeSubscription = networkChangeSubscription; this.permissionsSubscription = permissionsSubscription; this.networkChangeSubscription?.on('data', debounce(this.handleNetworkChanged, 400)); this.permissionsSubscription?.on('data', debounce(this.handlePermissionsChanged, 400)); } async stopSubscriptions() { await Promise.allSettled([ this.networkChangeSubscription?.unsubscribe(), this.permissionsSubscription?.unsubscribe(), ]); this.networkChangeSubscription = undefined; this.permissionsSubscription = undefined; } networkChangeSubscription; permissionsSubscription; } __decorate([ observable, __metadata("design:type", Function) ], NekotonConnector.prototype, "provider", void 0); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "connect", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "disconnect", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "addAsset", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Boolean]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "addNetwork", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "switchNetwork", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "account", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "chainId", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "version", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "isConnecting", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "isDisconnecting", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "isInitialized", null); __decorate([ computed, __metadata("design:type", Object), __metadata("design:paramtypes", []) ], NekotonConnector.prototype, "isInitializing", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "handlePermissionsChanged", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "handleNetworkChanged", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", [Error]), __metadata("design:returntype", void 0) ], NekotonConnector.prototype, "handleDisconnected", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "runSubscriptions", null); __decorate([ action.bound, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], NekotonConnector.prototype, "stopSubscriptions", null);