@tronweb3/tronwallet-adapter-tronlink
Version:
Wallet adapter for TronLink Wallet extension and TronLink app.
1,211 lines (1,119 loc) • 82.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global["@tronweb3/tronwallet-adapter-tronlink"] = factory());
})(this, (function () { 'use strict';
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var cjs$1 = {};
var adapter$1 = {};
var cjs = {};
var adapter = {};
var eventemitter3 = {exports: {}};
var hasRequiredEventemitter3;
function requireEventemitter3 () {
if (hasRequiredEventemitter3) return eventemitter3.exports;
hasRequiredEventemitter3 = 1;
(function (module) {
var has = Object.prototype.hasOwnProperty
, prefix = '~';
/**
* Constructor to create a storage for our `EE` objects.
* An `Events` instance is a plain object whose properties are event names.
*
* @constructor
* @private
*/
function Events() {}
//
// We try to not inherit from `Object.prototype`. In some engines creating an
// instance in this way is faster than calling `Object.create(null)` directly.
// If `Object.create(null)` is not supported we prefix the event names with a
// character to make sure that the built-in object properties are not
// overridden or used as an attack vector.
//
if (Object.create) {
Events.prototype = Object.create(null);
//
// This hack is needed because the `__proto__` property is still inherited in
// some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
//
if (!new Events().__proto__) prefix = false;
}
/**
* Representation of a single event listener.
*
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} [once=false] Specify if the listener is a one-time listener.
* @constructor
* @private
*/
function EE(fn, context, once) {
this.fn = fn;
this.context = context;
this.once = once || false;
}
/**
* Add a listener for a given event.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} context The context to invoke the listener with.
* @param {Boolean} once Specify if the listener is a one-time listener.
* @returns {EventEmitter}
* @private
*/
function addListener(emitter, event, fn, context, once) {
if (typeof fn !== 'function') {
throw new TypeError('The listener must be a function');
}
var listener = new EE(fn, context || emitter, once)
, evt = prefix ? prefix + event : event;
if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
else emitter._events[evt] = [emitter._events[evt], listener];
return emitter;
}
/**
* Clear event by name.
*
* @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
* @param {(String|Symbol)} evt The Event name.
* @private
*/
function clearEvent(emitter, evt) {
if (--emitter._eventsCount === 0) emitter._events = new Events();
else delete emitter._events[evt];
}
/**
* Minimal `EventEmitter` interface that is molded against the Node.js
* `EventEmitter` interface.
*
* @constructor
* @public
*/
function EventEmitter() {
this._events = new Events();
this._eventsCount = 0;
}
/**
* Return an array listing the events for which the emitter has registered
* listeners.
*
* @returns {Array}
* @public
*/
EventEmitter.prototype.eventNames = function eventNames() {
var names = []
, events
, name;
if (this._eventsCount === 0) return names;
for (name in (events = this._events)) {
if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
}
if (Object.getOwnPropertySymbols) {
return names.concat(Object.getOwnPropertySymbols(events));
}
return names;
};
/**
* Return the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Array} The registered listeners.
* @public
*/
EventEmitter.prototype.listeners = function listeners(event) {
var evt = prefix ? prefix + event : event
, handlers = this._events[evt];
if (!handlers) return [];
if (handlers.fn) return [handlers.fn];
for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
ee[i] = handlers[i].fn;
}
return ee;
};
/**
* Return the number of listeners listening to a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Number} The number of listeners.
* @public
*/
EventEmitter.prototype.listenerCount = function listenerCount(event) {
var evt = prefix ? prefix + event : event
, listeners = this._events[evt];
if (!listeners) return 0;
if (listeners.fn) return 1;
return listeners.length;
};
/**
* Calls each of the listeners registered for a given event.
*
* @param {(String|Symbol)} event The event name.
* @returns {Boolean} `true` if the event had listeners, else `false`.
* @public
*/
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return false;
var listeners = this._events[evt]
, len = arguments.length
, args
, i;
if (listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
switch (len) {
case 1: return listeners.fn.call(listeners.context), true;
case 2: return listeners.fn.call(listeners.context, a1), true;
case 3: return listeners.fn.call(listeners.context, a1, a2), true;
case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
}
for (i = 1, args = new Array(len -1); i < len; i++) {
args[i - 1] = arguments[i];
}
listeners.fn.apply(listeners.context, args);
} else {
var length = listeners.length
, j;
for (i = 0; i < length; i++) {
if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
switch (len) {
case 1: listeners[i].fn.call(listeners[i].context); break;
case 2: listeners[i].fn.call(listeners[i].context, a1); break;
case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
default:
if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
args[j - 1] = arguments[j];
}
listeners[i].fn.apply(listeners[i].context, args);
}
}
}
return true;
};
/**
* Add a listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.on = function on(event, fn, context) {
return addListener(this, event, fn, context, false);
};
/**
* Add a one-time listener for a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn The listener function.
* @param {*} [context=this] The context to invoke the listener with.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.once = function once(event, fn, context) {
return addListener(this, event, fn, context, true);
};
/**
* Remove the listeners of a given event.
*
* @param {(String|Symbol)} event The event name.
* @param {Function} fn Only remove the listeners that match this function.
* @param {*} context Only remove the listeners that have this context.
* @param {Boolean} once Only remove one-time listeners.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
var evt = prefix ? prefix + event : event;
if (!this._events[evt]) return this;
if (!fn) {
clearEvent(this, evt);
return this;
}
var listeners = this._events[evt];
if (listeners.fn) {
if (
listeners.fn === fn &&
(!once || listeners.once) &&
(!context || listeners.context === context)
) {
clearEvent(this, evt);
}
} else {
for (var i = 0, events = [], length = listeners.length; i < length; i++) {
if (
listeners[i].fn !== fn ||
(once && !listeners[i].once) ||
(context && listeners[i].context !== context)
) {
events.push(listeners[i]);
}
}
//
// Reset the array, or remove it completely if we have no more listeners.
//
if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
else clearEvent(this, evt);
}
return this;
};
/**
* Remove all listeners, or those of the specified event.
*
* @param {(String|Symbol)} [event] The event name.
* @returns {EventEmitter} `this`.
* @public
*/
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
var evt;
if (event) {
evt = prefix ? prefix + event : event;
if (this._events[evt]) clearEvent(this, evt);
} else {
this._events = new Events();
this._eventsCount = 0;
}
return this;
};
//
// Alias methods names because people roll like that.
//
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
//
// Expose the prefix.
//
EventEmitter.prefixed = prefix;
//
// Allow `EventEmitter` to be imported as module namespace.
//
EventEmitter.EventEmitter = EventEmitter;
//
// Expose the module.
//
{
module.exports = EventEmitter;
}
} (eventemitter3));
return eventemitter3.exports;
}
var typedData = {};
var errors = {};
var hasRequiredErrors;
function requireErrors () {
if (hasRequiredErrors) return errors;
hasRequiredErrors = 1;
Object.defineProperty(errors, "__esModule", { value: true });
errors.WalletSignTypedDataError = errors.WalletGetNetworkError = errors.WalletSwitchChainError = errors.WalletWindowClosedError = errors.WalletWalletLoadError = errors.WalletSignTransactionError = errors.WalletSignMessageError = errors.WalletDisconnectionError = errors.WalletConnectionError = errors.WalletDisconnectedError = errors.WalletNotSelectedError = errors.WalletNotFoundError = errors.WalletError = void 0;
class WalletError extends Error {
constructor(message, error) {
super(message);
this.error = error;
}
}
errors.WalletError = WalletError;
/**
* Occurs when wallet is not installed.
*/
class WalletNotFoundError extends WalletError {
constructor(message, error) {
super(message || 'The wallet is not found.', error);
this.name = 'WalletNotFoundError';
}
}
errors.WalletNotFoundError = WalletNotFoundError;
/**
* Occurs when connect to a wallet but there is no wallet selected.
*/
class WalletNotSelectedError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletNotSelectedError';
this.message = 'No wallet is selected. Please select a wallet.';
}
}
errors.WalletNotSelectedError = WalletNotSelectedError;
/**
* Occurs when wallet is disconnected.
* Used by some wallets which won't connect automatically when call `signMessage()` or `signTransaction()`.
*/
class WalletDisconnectedError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletDisconnectedError';
this.message = 'The wallet is disconnected. Please connect first.';
}
}
errors.WalletDisconnectedError = WalletDisconnectedError;
/**
* Occurs when try to connect a wallet.
*/
class WalletConnectionError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletConnectionError';
}
}
errors.WalletConnectionError = WalletConnectionError;
/**
* Occurs when try to disconnect a wallet.
*/
class WalletDisconnectionError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletDisconnectionError';
}
}
errors.WalletDisconnectionError = WalletDisconnectionError;
/**
* Occurs when call `signMessage()`.
*/
class WalletSignMessageError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletSignMessageError';
}
}
errors.WalletSignMessageError = WalletSignMessageError;
/**
* Occurs when call `signTransaction()`.
*/
class WalletSignTransactionError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletSignTransactionError';
}
}
errors.WalletSignTransactionError = WalletSignTransactionError;
/**
* Occurs when load wallet
*/
class WalletWalletLoadError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletWalletLoadError';
}
}
errors.WalletWalletLoadError = WalletWalletLoadError;
/**
* Occurs when walletconnect QR window is closed.
*/
class WalletWindowClosedError extends WalletError {
constructor(message, error) {
super(message || 'The QR window is closed.', error);
this.name = 'WalletWindowClosedError';
}
}
errors.WalletWindowClosedError = WalletWindowClosedError;
/**
* Occurs when request wallet to switch chain.
*/
class WalletSwitchChainError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletSwitchChainError';
}
}
errors.WalletSwitchChainError = WalletSwitchChainError;
/**
* Occurs when get network infomation.
*/
class WalletGetNetworkError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletGetNetworkError';
}
}
errors.WalletGetNetworkError = WalletGetNetworkError;
/**
* Occurs when call `signTypedData()`.
*/
class WalletSignTypedDataError extends WalletError {
constructor() {
super(...arguments);
this.name = 'WalletSignTypedDataError';
}
}
errors.WalletSignTypedDataError = WalletSignTypedDataError;
return errors;
}
var hasRequiredTypedData;
function requireTypedData () {
if (hasRequiredTypedData) return typedData;
hasRequiredTypedData = 1;
Object.defineProperty(typedData, "__esModule", { value: true });
typedData.normalizeAndValidateTypedData = normalizeAndValidateTypedData;
const errors_js_1 = requireErrors();
function isPlainObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
/**
* Validate a TypedData payload and return a normalized copy safe to pass to wallets.
*
* Current normalization: convert `domain.chainId` from string to number (some wallets,
* e.g. TronLink, require a numeric chainId).
*
* Throws {@link WalletSignTypedDataError} if the payload is not a valid TypedData object.
*/
function normalizeAndValidateTypedData(typedData) {
if (!isPlainObject(typedData)) {
throw new errors_js_1.WalletSignTypedDataError('typedData must be an object.');
}
if (!isPlainObject(typedData.domain)) {
throw new errors_js_1.WalletSignTypedDataError('typedData.domain must be an object.');
}
if (!isPlainObject(typedData.types)) {
throw new errors_js_1.WalletSignTypedDataError('typedData.types must be an object.');
}
if (!isPlainObject(typedData.message)) {
throw new errors_js_1.WalletSignTypedDataError('typedData.message must be an object.');
}
const { chainId } = typedData.domain;
if (typeof chainId === 'string') {
const parsed = Number(chainId);
if (!Number.isFinite(parsed)) {
throw new errors_js_1.WalletSignTypedDataError(`Invalid domain.chainId: ${chainId}.`);
}
return Object.assign(Object.assign({}, typedData), { domain: Object.assign(Object.assign({}, typedData.domain), { chainId: parsed }) });
}
return typedData;
}
return typedData;
}
var types$1 = {};
var hasRequiredTypes$1;
function requireTypes$1 () {
if (hasRequiredTypes$1) return types$1;
hasRequiredTypes$1 = 1;
Object.defineProperty(types$1, "__esModule", { value: true });
types$1.TIP6963RequestProviderEventName = types$1.TIP6963AnnounceProviderEventName = types$1.ChainNetwork = types$1.NetworkType = types$1.AdapterState = types$1.WalletReadyState = void 0;
/**
* Wallet ready state.
*/
var WalletReadyState;
(function (WalletReadyState) {
/**
* Adapter will start to check if wallet exists after adapter instance is created.
*/
WalletReadyState["Loading"] = "Loading";
/**
* When checking ends and wallet is not found, readyState will be NotFound.
*/
WalletReadyState["NotFound"] = "NotFound";
/**
* When checking ends and wallet is found, readyState will be Found.
*/
WalletReadyState["Found"] = "Found";
})(WalletReadyState || (types$1.WalletReadyState = WalletReadyState = {}));
/**
* Adapter state
*/
var AdapterState;
(function (AdapterState) {
/**
* If adapter is checking the wallet, the state is Loading.
*/
AdapterState["Loading"] = "Loading";
/**
* If wallet is not installed, the state is NotFound.
*/
AdapterState["NotFound"] = "NotFound";
/**
* If wallet is installed but is not connected to current Dapp, the state is Disconnected.
*/
AdapterState["Disconnect"] = "Disconnected";
/**
* Wallet is connected to current Dapp.
*/
AdapterState["Connected"] = "Connected";
})(AdapterState || (types$1.AdapterState = AdapterState = {}));
var NetworkType;
(function (NetworkType) {
NetworkType["Mainnet"] = "Mainnet";
NetworkType["Shasta"] = "Shasta";
NetworkType["Nile"] = "Nile";
/**
* When use custom node
*/
NetworkType["Unknown"] = "Unknown";
})(NetworkType || (types$1.NetworkType = NetworkType = {}));
var ChainNetwork;
(function (ChainNetwork) {
ChainNetwork["Mainnet"] = "Mainnet";
ChainNetwork["Shasta"] = "Shasta";
ChainNetwork["Nile"] = "Nile";
})(ChainNetwork || (types$1.ChainNetwork = ChainNetwork = {}));
types$1.TIP6963AnnounceProviderEventName = 'TIP6963:announceProvider';
types$1.TIP6963RequestProviderEventName = 'TIP6963:requestProvider';
return types$1;
}
var hasRequiredAdapter$1;
function requireAdapter$1 () {
if (hasRequiredAdapter$1) return adapter;
hasRequiredAdapter$1 = 1;
var __importDefault = (adapter && adapter.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(adapter, "__esModule", { value: true });
adapter.Adapter = adapter.EventEmitter = void 0;
const eventemitter3_1 = __importDefault(requireEventemitter3());
adapter.EventEmitter = eventemitter3_1.default;
const typedData_js_1 = requireTypedData();
const types_js_1 = requireTypes$1();
class Adapter extends eventemitter3_1.default {
get connected() {
return this.state === types_js_1.AdapterState.Connected;
}
/**
* Some wallets such as TronLink don't support disconnect() method.
*/
disconnect() {
console.info("The current adapter doesn't support disconnect by DApp.");
return Promise.resolve();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
multiSign(transaction, options = {}) {
return Promise.reject("The current wallet doesn't support multiSign.");
}
signTypedData(typedData) {
let normalized;
try {
normalized = (0, typedData_js_1.normalizeAndValidateTypedData)(typedData);
}
catch (error) {
return Promise.reject(error);
}
return this._signTypedData(normalized);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_signTypedData(_typedData) {
return Promise.reject("The current wallet doesn't support signTypedData.");
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
switchChain(_chainId) {
return Promise.reject("The current wallet doesn't support switch chain.");
}
}
adapter.Adapter = Adapter;
return adapter;
}
var utils$1 = {};
var hasRequiredUtils$1;
function requireUtils$1 () {
if (hasRequiredUtils$1) return utils$1;
hasRequiredUtils$1 = 1;
Object.defineProperty(utils$1, "__esModule", { value: true });
utils$1.isInBrowser = isInBrowser;
utils$1.checkAdapterState = checkAdapterState;
utils$1.isInMobileBrowser = isInMobileBrowser;
/**
* check simply if current environment is browser or not
* @returns boolean
*/
function isInBrowser() {
return typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';
}
/**
*
* @param {Function} check funcion to check if wallet is installed. return true if wallet is detected.
* @returns
*/
function checkAdapterState(check) {
if (!isInBrowser())
return;
const disposers = [];
function dispose() {
for (const dispose of disposers) {
dispose();
}
}
function checkAndDispose() {
if (check()) {
dispose();
}
}
const interval = setInterval(checkAndDispose, 500);
disposers.push(() => clearInterval(interval));
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkAndDispose, { once: true });
disposers.push(() => document.removeEventListener('DOMContentLoaded', checkAndDispose));
}
if (document.readyState !== 'complete') {
window.addEventListener('load', checkAndDispose, { once: true });
disposers.push(() => window.removeEventListener('load', checkAndDispose));
}
checkAndDispose();
// stop all task after 1min
setTimeout(dispose, 60 * 1000);
}
/**
* Simplily detect mobile device
*/
function isInMobileBrowser() {
return (typeof navigator !== 'undefined' &&
navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i));
}
return utils$1;
}
var addonAdapter = {};
var security = {};
var hasRequiredSecurity;
function requireSecurity () {
if (hasRequiredSecurity) return security;
hasRequiredSecurity = 1;
(function (exports$1) {
var __awaiter = (security && security.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.defaultSecurityOptions = void 0;
exports$1.clearCache = clearCache;
exports$1.fetchJsonWithCache = fetchJsonWithCache;
exports$1.defaultSecurityOptions = {
onRiskDetected: (result) => __awaiter(void 0, void 0, void 0, function* () {
console.log(`[WalletAdapter] Risk detected:`, result);
}),
configUrls: [],
enabled: false,
timeout: 2000,
retries: 0,
cacheTTL: 10 * 60 * 1000,
};
function isValidRisk(risk) {
return (!!risk &&
typeof risk === 'object' &&
typeof risk.title === 'string' &&
risk.title.length > 0 &&
(risk.noticeType === 1 || risk.noticeType === 2 || risk.noticeType === 3));
}
function sanitizeRisk(risk) {
const clean = { title: risk.title, noticeType: risk.noticeType };
if (typeof risk.ext === 'string')
clean.ext = risk.ext;
if (typeof risk.ios === 'string')
clean.ios = risk.ios;
if (typeof risk.and === 'string')
clean.and = risk.and;
return clean;
}
/**
* Defensively coerce an arbitrary remote payload into a well-formed RiskConfig.
*
* The config comes from a remote URL, so a bad release (or a tampered response)
* must never break the connect flow. Anything malformed is dropped with a
* warning rather than thrown: `wallets` must be a record whose values are
* arrays, and each risk must have a non-empty string `title` and a `noticeType`
* of 1/2/3. Invalid entries are discarded so downstream merging/consumption
* only ever sees clean `Risk[]`.
*/
function normalizeRiskConfig(data) {
const source = data && typeof data === 'object' ? data : {};
const wallets = {};
if (source.wallets && typeof source.wallets === 'object') {
for (const [walletName, rawRisks] of Object.entries(source.wallets)) {
if (!Array.isArray(rawRisks)) {
console.warn(`[WalletAdapter] Ignoring malformed risks for "${walletName}": expected an array, got ${typeof rawRisks}.`);
continue;
}
const validRisks = [];
for (const risk of rawRisks) {
if (isValidRisk(risk)) {
validRisks.push(sanitizeRisk(risk));
}
else {
console.warn(`[WalletAdapter] Dropping malformed risk entry for "${walletName}":`, risk);
}
}
if (validRisks.length > 0) {
wallets[walletName] = validRisks;
}
}
}
return {
v: typeof source.v === 'string' ? source.v : '',
ts: typeof source.ts === 'number' ? source.ts : 0,
wallets,
};
}
const _jsonCache = {};
function clearCache() {
Object.keys(_jsonCache).forEach((key) => {
Reflect.deleteProperty(_jsonCache, key);
});
}
function fetchJsonWithCache(options) {
return __awaiter(this, void 0, void 0, function* () {
const { configUrls = exports$1.defaultSecurityOptions.configUrls, timeout = exports$1.defaultSecurityOptions.timeout, retries = exports$1.defaultSecurityOptions.retries, onConfigFallback, cacheTTL = exports$1.defaultSecurityOptions.cacheTTL, } = options;
const now = Date.now();
function fetchOneUrl(url) {
return __awaiter(this, void 0, void 0, function* () {
const cache = _jsonCache[url];
if (cache && now - cache.timestamp < cacheTTL) {
return cache.data;
}
function fetchWithTimeout() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
fetch(url, { signal: controller.signal })
.then((resp) => {
clearTimeout(timer);
if (!resp.ok)
reject(new Error('Fetch failed with code: ' + resp.status));
else
resp.json().then((data) => resolve(normalizeRiskConfig(data)), reject);
})
.catch((err) => {
clearTimeout(timer);
reject(err);
});
});
});
}
for (let i = 0; i <= retries; i++) {
try {
const data = yield fetchWithTimeout();
_jsonCache[url] = { data, timestamp: Date.now() };
return data;
}
catch (e) {
console.warn(`[WalletAdapter] Fetch attempt ${i + 1} for ${url} failed:`, e);
// continue to next retry
}
}
// All retries failed — let the outer caller decide (onConfigFallback or safe empty config)
return null;
});
}
const results = yield Promise.all(configUrls.map(fetchOneUrl));
const configs = results.filter((r) => r !== null);
if (configs.length > 0) {
return configs.reduce((merged, config) => {
var _a;
if (config.ts > merged.ts) {
merged.v = config.v;
merged.ts = config.ts;
}
// Pass every risk from every source through to the DApp without
// deduplication. Conflict resolution (highest severity wins, source
// weighting, etc.) is the DApp's responsibility — the adapter just
// delivers the union.
for (const [wallet, risks] of Object.entries(config.wallets)) {
merged.wallets[wallet] = [...((_a = merged.wallets[wallet]) !== null && _a !== void 0 ? _a : []), ...risks];
}
return merged;
}, { v: '', ts: 0, wallets: {} });
}
// All URLs failed with no cache — call custom fallback or default to safe (allow connection)
if (onConfigFallback) {
return normalizeRiskConfig(yield onConfigFallback());
}
return { v: '', ts: 0, wallets: {} };
});
}
} (security));
return security;
}
var hasRequiredAddonAdapter;
function requireAddonAdapter () {
if (hasRequiredAddonAdapter) return addonAdapter;
hasRequiredAddonAdapter = 1;
var __awaiter = (addonAdapter && addonAdapter.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(addonAdapter, "__esModule", { value: true });
addonAdapter.AddonAdapter = void 0;
const adapter_js_1 = requireAdapter$1();
const errors_js_1 = requireErrors();
const security_js_1 = requireSecurity();
const types_js_1 = requireTypes$1();
const utils_js_1 = requireUtils$1();
/**
* Class to provide security check for wallets.
*/
class AddonAdapter extends adapter_js_1.Adapter {
constructor(params) {
super();
this.commonConfig = {
checkTimeout: 2 * 1000,
openAppWithDeeplink: true,
openUrlWhenWalletNotFound: true,
securityOptions: security_js_1.defaultSecurityOptions,
};
this._securityCheckCache = null;
this.commonConfig = Object.assign(Object.assign({}, this.commonConfig), params);
if (typeof this.commonConfig.checkTimeout !== 'number') {
throw new Error(`[WalletAdapter] config.checkTimeout should be a number`);
}
AddonAdapter._validateSecurityOptions(this.commonConfig.securityOptions);
}
static _validateSecurityOptions(securityOptions) {
const { enabled, configUrls } = securityOptions;
if (enabled && (!configUrls || configUrls.length === 0)) {
throw new Error(`[WalletAdapter] config.securityOptions.configUrls is required when securityOptions.enabled is true`);
}
}
/**
* Update the security options at runtime. The given options are merged into
* the existing config, validated, and the cached security check result is
* cleared so the next connect runs a fresh check with the new configuration.
*/
updateSecurityOptions(securityOptions) {
const merged = Object.assign(Object.assign({}, this.commonConfig.securityOptions), securityOptions);
AddonAdapter._validateSecurityOptions(merged);
this.commonConfig.securityOptions = merged;
this._securityCheckCache = null;
}
/**
* Run the pre-connect pipeline (wallet discovery, deeplink/url fallback,
* security check).
*
* @returns `true` when the caller should continue with the actual connect
* flow, `false` when the adapter is already connected/connecting and the
* caller should bail out. Throws `WalletNotFoundError` when the wallet
* cannot be found.
*/
_beforeConnect() {
return __awaiter(this, void 0, void 0, function* () {
if (this.connected || this.connecting)
return false;
yield this._checkWallet();
if (this.readyState === types_js_1.WalletReadyState.NotFound) {
if ((0, utils_js_1.isInBrowser)() &&
!this._openAppByDeepLinkIfNeed() &&
this.commonConfig.openUrlWhenWalletNotFound !== false) {
window.open(this.url, '_blank');
}
throw new errors_js_1.WalletNotFoundError();
}
yield this.checkSecurity();
return true;
});
}
/**
* Fetch remote config and do risk check.
* In-flight calls share the same promise (so `onRiskDetected` fires at most
* once even on a slow network). After the promise settles, the result is
* cached for SECURITY_CHECK_CACHE_TTL; the next call after that window
* runs a fresh check.
*/
checkSecurity() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.commonConfig.securityOptions.enabled)
return;
const cache = this._securityCheckCache;
if (cache &&
(cache.settledAt === null || Date.now() - cache.settledAt < AddonAdapter.SECURITY_CHECK_CACHE_TTL)) {
return cache.promise;
}
const promise = (() => __awaiter(this, void 0, void 0, function* () {
const result = yield (0, security_js_1.fetchJsonWithCache)(this.commonConfig.securityOptions);
const risks = result.wallets[this.name];
if (risks && risks.length > 0) {
const callback = this.commonConfig.securityOptions.onRiskDetected || security_js_1.defaultSecurityOptions.onRiskDetected;
yield callback({ risks });
}
}))();
const entry = { promise, settledAt: null };
this._securityCheckCache = entry;
// The `.finally` chain returns a new promise that mirrors `promise`'s rejection.
// Attach a noop `.catch` to avoid an unhandled rejection on that chain — the
// original `promise` is still surfaced to callers via the return below.
promise
.finally(() => {
if (this._securityCheckCache === entry) {
entry.settledAt = Date.now();
}
})
.catch(() => { });
return promise;
});
}
}
addonAdapter.AddonAdapter = AddonAdapter;
AddonAdapter.SECURITY_CHECK_CACHE_TTL = 5 * 1000;
return addonAdapter;
}
var hasRequiredCjs$1;
function requireCjs$1 () {
if (hasRequiredCjs$1) return cjs;
hasRequiredCjs$1 = 1;
(function (exports$1) {
var __createBinding = (cjs && cjs.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (cjs && cjs.__exportStar) || function(m, exports$1) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
};
Object.defineProperty(exports$1, "__esModule", { value: true });
__exportStar(requireAdapter$1(), exports$1);
__exportStar(requireErrors(), exports$1);
__exportStar(requireTypes$1(), exports$1);
__exportStar(requireTypedData(), exports$1);
__exportStar(requireUtils$1(), exports$1);
__exportStar(requireAddonAdapter(), exports$1);
__exportStar(requireSecurity(), exports$1);
} (cjs));
return cjs;
}
var utils = {};
var hasRequiredUtils;
function requireUtils () {
if (hasRequiredUtils) return utils;
hasRequiredUtils = 1;
var __awaiter = (utils && utils.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(utils, "__esModule", { value: true });
utils.supportTron = supportTron;
utils.supportTronLink = supportTronLink;
utils.isInTronLinkApp = isInTronLinkApp;
utils.openTronLink = openTronLink;
utils.waitTronwebReady = waitTronwebReady;
const tronwallet_abstract_adapter_1 = /*@__PURE__*/ requireCjs$1();
function supportTron() {
return (0, tronwallet_abstract_adapter_1.isInBrowser)() && !!(window.tron && window.tron.isTronLink);
}
function supportTronLink() {
return (0, tronwallet_abstract_adapter_1.isInBrowser)() && !!(supportTron() || window.tronLink || window.tronWeb);
}
/**
* Detect if in TronLinkApp
* Tron DApp running in the DApp Explorer injects iTron objects automatically to offer customized App service.
* See [here](https://docs.tronlink.org/tronlink-app/dapp-support/dapp-explorer)
*/
function isInTronLinkApp() {
return (0, tronwallet_abstract_adapter_1.isInBrowser)() && typeof window.iTron !== 'undefined';
}
function openTronLink({ dappIcon, dappName } = { dappIcon: '', dappName: '' }) {
if (!supportTronLink() && (0, tronwallet_abstract_adapter_1.isInMobileBrowser)() && !isInTronLinkApp()) {
let defaultDappName = '', defaultDappIcon = '';
try {
defaultDappName = document.title;
const link = document.querySelector('link[rel*="icon"]');
if (link) {
defaultDappIcon = new URL(link.getAttribute('href') || '', location.href).toString();
}
}
catch (_a) {
// console.error(e);
}
const { origin, pathname, search, hash } = window.location;
const url = origin + pathname + search + (hash.includes('?') ? hash : `${hash}?_=1`);
const params = {
action: 'open',
actionId: Date.now() + '',
callbackUrl: 'http://someurl.com', // no need callback
dappIcon: dappIcon || defaultDappIcon,
dappName: dappName || defaultDappName,
url,
protocol: 'TronLink',
version: '1.0',
chainId: '0x2b6653dc',
};
window.location.href = `tronlinkoutside://pull.activity?param=${encodeURIComponent(JSON.stringify(params))}`;
return true;
}
return false;
}
function waitTronwebReady(tronObj) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
if (tronObj.tronWeb) {
clearInterval(interval);
clearTimeout(timeout);
resolve();
}
}, 50);
const timeout = setTimeout(() => {
clearInterval(interval);
reject('`window.tron.tronweb` is not ready.');
}, 2000);
});
});
}
return utils;
}
var hasRequiredAdapter;
function requireAdapter () {
if (hasRequiredAdapter) return adapter$1;
hasRequiredAdapter = 1;
(function (exports$1) {
var __awaiter = (adapter$1 && adapter$1.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.TronLinkAdapter = exports$1.TronLinkAdapterName = exports$1.chainIdNetworkMap = void 0;
exports$1.getNetworkInfoByTronWeb = getNetworkInfoByTronWeb;
const tronwallet_abstract_adapter_1 = /*@__PURE__*/ requireCjs$1();
const utils_js_1 = requireUtils();
exports$1.chainIdNetworkMap = {
'0x2b6653dc': tronwallet_abstract_adapter_1.NetworkType.Mainnet,
'0x94a9059e': tronwallet_abstract_adapter_1.NetworkType.Shasta,
'0xcd8690dc': tronwallet_abstract_adapter_1.NetworkType.Nile,
};
function getNetworkInfoByTronWeb(tronWeb) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
const { blockID = '' } = yield tronWeb.trx.getBlockByNumber(0);
const chainId = `0x${blockID.slice(-8)}`;
return {
networkType: exports$1.chainIdNetworkMap[chainId] || tronwallet_abstract_adapter_1.NetworkType.Unknown,
chainId,
fullNode: ((_a = tronWeb.fullNode) === null || _a === void 0 ? void 0 : _a.host) || '',
solidityNode: ((_b = tronWeb.solidityNode) === null || _b === void 0 ? void 0 : _b.host) || '',
eventServer: ((_c = tronWeb.eventServer) === null || _c === void 0 ? void 0 : _c.host) || '',
};
});
}
exports$1.TronLinkAdapterName = 'TronLink';
class TronLinkAdapter extends tronwallet_abstract_adapter_1.AddonAdapter {
// record if first connect event has emitted or not
constructor(config = {}) {
super(config);
this.name = exports$1.TronLinkAdapterName;
this.url = 'https://www.tronlink.org/';
this.icon = 'data:image/png;base64,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