@broxus/tvm-connect
Version:
Nekoton-compatible wallets connector.
595 lines (594 loc) • 21.7 kB
JavaScript
"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);