Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@0xsequence/auth

Package Overview
Dependencies
Maintainers
5
Versions
598
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@0xsequence/auth - npm Package Compare versions

Comparing version
2.3.19
to
2.3.20
+673
dist/0xsequence-auth.cjs.dev.js
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var ethers = require('ethers');
var ethauth = require('@0xsequence/ethauth');
var network = require('@0xsequence/network');
var account = require('@0xsequence/account');
var api = require('@0xsequence/api');
var indexer = require('@0xsequence/indexer');
var metadata = require('@0xsequence/metadata');
var utils = require('@0xsequence/utils');
var sessions = require('@0xsequence/sessions');
var signhub = require('@0xsequence/signhub');
var core = require('@0xsequence/core');
// Default session expiration of ETHAuth token (1 week)
const DEFAULT_SESSION_EXPIRATION = 60 * 60 * 24 * 7;
const EXPIRATION_JWT_MARGIN = 60; // seconds
class Services {
constructor(account, settings, status = {}, projectAccessKey) {
this.account = account;
this.settings = settings;
this.status = status;
this._initialAuthRequest = void 0;
// proof strings are indexed by account address and app name, see getProofStringKey()
this.proofStrings = new Map();
this.onAuthCallbacks = [];
this.apiClient = void 0;
this.metadataClient = void 0;
this.indexerClients = new Map();
this.indexerGateway = void 0;
this.projectAccessKey = void 0;
this.projectAccessKey = projectAccessKey;
}
now() {
return Math.floor(Date.now() / 1000);
}
get expiration() {
var _this$settings$metada;
return Math.max((_this$settings$metada = this.settings.metadata.expiration) != null ? _this$settings$metada : DEFAULT_SESSION_EXPIRATION, 120);
}
onAuth(cb) {
this.onAuthCallbacks.push(cb);
return () => this.onAuthCallbacks = this.onAuthCallbacks.filter(c => c !== cb);
}
async dump() {
if (!this.status.jwt) return {
metadata: this.settings.metadata
};
return {
jwt: {
token: await this.status.jwt.token,
expiration: this.status.jwt.expiration
},
metadata: this.status.metadata
};
}
auth(maxTries = 5) {
var _this = this;
if (this._initialAuthRequest) return this._initialAuthRequest;
this._initialAuthRequest = async function () {
const url = _this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
let jwtAuth;
for (let i = 1;; i++) {
try {
jwtAuth = (await _this.getJWT(true)).token;
break;
} catch (error) {
if (i === maxTries) {
console.error(`couldn't authenticate after ${maxTries} attempts`, error);
throw error;
}
}
}
return new api.SequenceAPIClient(url, undefined, jwtAuth);
}();
return this._initialAuthRequest;
}
async getJWT(tryAuth) {
var _this2 = this;
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
// check if we already have or are waiting for a token
if (this.status.jwt) {
const _jwt = this.status.jwt;
const _token = await _jwt.token;
if (this.now() < _jwt.expiration) {
return {
token: _token,
expiration: _jwt.expiration
};
}
// token expired, delete it and get a new one
this.status.jwt = undefined;
}
if (!tryAuth) {
throw new Error('no auth token in memory');
}
const proofStringKey = this.getProofStringKey();
const {
proofString,
expiration
} = this.getProofString(proofStringKey);
const jwt = {
token: proofString.then(async function (proofString) {
const api$1 = new api.SequenceAPIClient(url);
const authResp = await api$1.getAuthToken({
ewtString: proofString
});
if ((authResp == null ? void 0 : authResp.status) === true && authResp.jwtToken.length !== 0) {
return authResp.jwtToken;
} else {
if (!(await _this2.isProofStringValid(proofString))) {
_this2.proofStrings.delete(proofStringKey);
}
throw new Error('no auth token from server');
}
}).catch(reason => {
this.status.jwt = undefined;
throw reason;
}),
expiration
};
this.status.jwt = jwt;
jwt.token.then(token => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'fulfilled',
value: token
});
} catch (_unused) {}
});
}).catch(reason => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'rejected',
reason
});
} catch (_unused2) {}
});
});
const token = await jwt.token;
return {
token,
expiration
};
}
getProofStringKey() {
return `${this.account.address} - ${this.settings.metadata.name}`;
}
async isProofStringValid(proofString) {
try {
const ethAuth = new ethauth.ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = network.findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network$1 = new ethers.ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.ethers.JsonRpcProvider(utils.getFetchRequest(found.rpcUrl, this.projectAccessKey), network$1, {
staticNetwork: network$1
});
await ethAuth.decodeProof(proofString);
return true;
} catch (_unused3) {
return false;
}
}
async getAPIClient(tryAuth = true) {
if (!this.apiClient) {
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.apiClient = new api.SequenceAPIClient(url, undefined, jwtAuth);
}
return this.apiClient;
}
async getMetadataClient(tryAuth = true) {
if (!this.metadataClient) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.metadataClient = new metadata.SequenceMetadata(this.settings.sequenceMetadataUrl, undefined, jwtAuth);
}
return this.metadataClient;
}
async getIndexerClient(chainId, tryAuth = true) {
const network$1 = network.findNetworkConfig(this.account.networks, chainId);
if (!network$1) {
throw Error(`No network for chain ${chainId}`);
}
if (!this.indexerClients.has(network$1.chainId)) {
if (network$1.indexer) {
this.indexerClients.set(network$1.chainId, network$1.indexer);
} else if (network$1.indexerUrl) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerClients.set(network$1.chainId, new indexer.SequenceIndexer(network$1.indexerUrl, undefined, jwtAuth));
} else {
throw Error(`No indexer url for chain ${chainId}`);
}
}
return this.indexerClients.get(network$1.chainId);
}
async getIndexerGateway(tryAuth = true) {
if (!this.indexerGateway) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerGateway = new indexer.SequenceIndexerGateway(this.settings.sequenceIndexerGatewayUrl, undefined, jwtAuth);
}
return this.indexerGateway;
}
getProofString(key) {
// check if we already have or are waiting for a proof string
if (this.proofStrings.has(key)) {
const _proofString = this.proofStrings.get(key);
if (this.now() < _proofString.expiration) {
return _proofString;
}
// proof string expired, delete it and make a new one
this.proofStrings.delete(key);
}
const proof = new ethauth.Proof({
address: this.account.address
});
proof.claims.app = this.settings.metadata.name;
if (typeof window === 'object') {
proof.claims.ogn = window.location.origin;
}
proof.setExpiryIn(this.expiration);
const ethAuth = new ethauth.ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = network.findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network$1 = new ethers.ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.ethers.JsonRpcProvider(utils.getFetchRequest(found.rpcUrl, this.projectAccessKey), network$1, {
staticNetwork: network$1
});
const expiration = this.now() + this.expiration - EXPIRATION_JWT_MARGIN;
const proofString = {
proofString: Promise.resolve(
// NOTICE: TODO: Here we ask the account to sign the message
// using whatever configuration we have ON-CHAIN, this means
// that the account will still use the v1 wallet, even if the migration
// was signed.
//
// This works for Sequence webapp v1 -> v2 because all v1 configurations share the same formula
// (torus + guard), but if we ever decide to allow cross-device login, then it will not work, because
// those other signers may not be part of the configuration.
//
this.account.signDigest(proof.messageDigest(), this.settings.sequenceApiChainId, true, 'eip6492')).then(s => {
proof.signature = s;
return ethAuth.encodeProof(proof, true);
}).catch(reason => {
this.proofStrings.delete(key);
throw reason;
}),
expiration
};
this.proofStrings.set(key, proofString);
return proofString;
}
}
// signAuthorization will perform an EIP712 typed-data message signing of ETHAuth domain via the provided
// Signer and authorization options.
const signAuthorization = async (signer, chainId, options) => {
const address = ethers.ethers.getAddress(await signer.getAddress());
if (!address || address === '' || address === '0x') {
throw ErrAccountIsRequired;
}
const proof = new ethauth.Proof();
proof.address = address;
if (!options || !options.app || options.app === '') {
throw new AuthError('authorization options requires app to be set');
}
proof.claims.app = options.app;
proof.claims.ogn = options.origin;
proof.claims.n = options.nonce;
proof.setExpiryIn(options.expiry ? Math.max(options.expiry, 200) : DEFAULT_SESSION_EXPIRATION);
const typedData = proof.messageTypedData();
const chainIdNumber = network.toChainIdNumber(chainId);
proof.signature = await (signer instanceof account.Account ?
// Account can sign EIP-6492 signatures, so it doesn't require deploying the wallet
signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber, 'eip6492') : signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber));
const ethAuth = new ethauth.ETHAuth();
const proofString = await ethAuth.encodeProof(proof, true);
return {
typedData,
proofString
};
};
// TODO: review......
class AuthError extends Error {
constructor(message) {
super(message);
this.name = 'AuthError';
}
}
const ErrAccountIsRequired = new AuthError('auth error: account address is empty');
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function isSessionDumpV1(obj) {
return obj.config && obj.metadata && obj.version === undefined;
}
function isSessionDumpV2(obj) {
return obj.version === 2 && obj.address;
}
// These chains are always validated for migrations
// if they are not available, the login will fail
const CRITICAL_CHAINS = [1, 137];
const SessionSettingsDefault = {
contexts: core.commons.context.defaultContexts,
networks: network.allNetworks,
tracker: new sessions.trackers.remote.RemoteConfigTracker('https://sessions.sequence.app')
};
class Session {
constructor(networks, contexts, account, services) {
this.networks = networks;
this.contexts = contexts;
this.account = account;
this.services = services;
}
async dump() {
const base = {
version: 2,
address: this.account.address
};
if (this.services) {
return _extends({}, base, await this.services.dump());
}
return base;
}
static async singleSigner(args) {
let {
signer
} = args;
if (typeof signer === 'string') {
signer = new ethers.ethers.Wallet(signer);
}
const orchestrator = new signhub.Orchestrator([signer]);
const referenceSigner = await signer.getAddress();
const threshold = 1;
const addSigners = [{
weight: 1,
address: referenceSigner
}];
const selectWallet = args.selectWallet || async function (wallets) {
var _args$settings$tracke, _args$settings;
if (wallets.length === 0) return undefined;
// Find a wallet that was originally created
// as a 1/1 of the reference signer
const tracker = (_args$settings$tracke = (_args$settings = args.settings) == null ? void 0 : _args$settings.tracker) != null ? _args$settings$tracke : SessionSettingsDefault.tracker;
const configs = await Promise.all(wallets.map(async function (wallet) {
const imageHash = await tracker.imageHashOfCounterfactualWallet({
wallet
});
return {
wallet,
config: imageHash && (await tracker.configOfImageHash({
imageHash: imageHash.imageHash
}))
};
}));
for (const config of configs) {
if (!config.config) {
continue;
}
const coder = core.universal.genericCoderFor(config.config.version);
const signers = coder.config.signersOf(config.config);
if (signers.length === 1 && signers[0].address === referenceSigner) {
return config.wallet;
}
}
return undefined;
};
return Session.open(_extends({}, args, {
orchestrator,
referenceSigner,
threshold,
addSigners,
selectWallet
}));
}
static async open(args) {
var _findNetworkConfig$ch, _findNetworkConfig, _settings$services$se, _settings$services, _networks$;
const {
referenceSigner,
threshold,
addSigners,
selectWallet,
onAccountAddress,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
// The reference network is mainnet, if mainnet is not available, we use the first network
const referenceChainId = (_findNetworkConfig$ch = (_findNetworkConfig = network.findNetworkConfig(networks, (_settings$services$se = settings == null || (_settings$services = settings.services) == null ? void 0 : _settings$services.sequenceApiChainId) != null ? _settings$services$se : network.ChainId.MAINNET)) == null ? void 0 : _findNetworkConfig.chainId) != null ? _findNetworkConfig$ch : (_networks$ = networks[0]) == null ? void 0 : _networks$.chainId;
if (!referenceChainId) throw Error('No reference chain found');
const foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner
});
const selectedWallet = await selectWallet(foundWallets.map(w => w.wallet));
let account$1;
if (selectedWallet) {
onAccountAddress == null || onAccountAddress(selectedWallet);
// existing account, lets update it
account$1 = new account.Account({
address: selectedWallet,
tracker,
networks,
contexts,
orchestrator,
projectAccessKey
});
// Get the latest configuration of the wallet (on the reference chain)
// now this configuration should be of the latest version, so we can start
// manipulating it.
// NOTICE: We are performing the wallet update on a single chain, assuming that
// all other networks have the same configuration. This is not always true.
if (addSigners && addSigners.length > 0) {
// New wallets never need migrations
// (because we create them on the latest version)
let status = await account$1.status(referenceChainId);
// If the wallet was created originally on v2, then we can skip
// the migration checks all together.
if (status.original.version !== status.version || account$1.version !== status.version) {
// Account may not have been migrated yet, so we need to check
// if it has been migrated and if not, migrate it (in all chains)
const {
migratedAllChains: isFullyMigrated,
failedChains
} = await account$1.isMigratedAllChains();
// Failed chains must not contain mainnet or polygon, otherwise we cannot proceed.
if (failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to fetch account status on ${failedChains.join(', ')}`);
}
if (!isFullyMigrated) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account$1))) {
throw Error('Migration cancelled, cannot open session');
}
const {
failedChains: _failedChains
} = await account$1.signAllMigrations(editConfigOnMigration || (c => c));
if (_failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to sign migrations on ${_failedChains.join(', ')}`);
}
// If we are using a dedupped tracker we need to invalidate the cache
// otherwise we run the risk of not seeing the signed migrations reflected.
if (sessions.trackers.isDedupedTracker(tracker)) {
tracker.invalidateCache();
}
let isFullyMigrated2;
[isFullyMigrated2, status] = await Promise.all([account$1.isMigratedAllChains().then(r => r.migratedAllChains), account$1.status(referenceChainId)]);
if (!isFullyMigrated2) throw Error('Failed to migrate account');
}
}
// NOTICE: We only need to do this because the API will not be able to
// validate the v2 signature (if the account has an onchain version of 1)
// we could speed this up by sending the migration alongside the jwt request
// and letting the API validate it offchain.
if (status.onChain.version !== status.version) {
await account$1.doBootstrap(referenceChainId, undefined, status);
}
const prevConfig = status.config;
const nextConfig = account$1.coders.config.editConfig(prevConfig, {
add: addSigners,
threshold
});
// Only update the onchain config if the imageHash has changed
if (account$1.coders.config.imageHashOf(prevConfig) !== account$1.coders.config.imageHashOf(nextConfig)) {
const newConfig = account$1.coders.config.editConfig(nextConfig, {
checkpoint: account$1.coders.config.checkpointOf(prevConfig) + 1n
});
await account$1.updateConfig(newConfig);
}
}
} else {
if (!addSigners || addSigners.length === 0) {
throw Error('Cannot create new account without signers');
}
if (!threshold) {
throw Error('Cannot create new account without threshold');
}
// fresh account
account$1 = await account.Account.new({
config: {
threshold,
checkpoint: 0,
signers: addSigners
},
tracker,
contexts,
orchestrator,
networks,
projectAccessKey
});
onAccountAddress == null || onAccountAddress(account$1.address);
// sign a digest and send it to the tracker
// otherwise the tracker will not know about this account
await account$1.publishWitness();
// safety check, the remove tracker should be able to find
// this account for the reference signer
const _foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner,
noCache: true
});
if (!_foundWallets.some(w => w.wallet === account$1.address)) {
throw Error('Account not found on tracker');
}
}
let servicesObj;
if (services) {
servicesObj = new Services(account$1, services);
servicesObj.auth(); // fire and forget
servicesObj.onAuth(result => {
if (result.status === 'fulfilled') {
account$1.setJwt(result.value);
}
});
}
return new Session(networks, contexts, account$1, servicesObj);
}
static async load(args) {
const {
dump,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
let account$1;
if (isSessionDumpV1(dump)) {
var _dump$jwt$expiration, _dump$jwt, _dump$jwt2;
// Old configuration format used to also contain an "address" field
// but if it doesn't, it means that it was a "counterfactual" account
// not yet updated, so we need to compute the address
const oldAddress = dump.config.address || core.commons.context.addressOf(contexts[1], core.v1.config.ConfigCoder.imageHashOf(_extends({}, dump.config, {
version: 1
})));
const jwtExpired = ((_dump$jwt$expiration = (_dump$jwt = dump.jwt) == null ? void 0 : _dump$jwt.expiration) != null ? _dump$jwt$expiration : 0) < Math.floor(Date.now() / 1000);
account$1 = new account.Account({
address: oldAddress,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt2 = dump.jwt) == null ? void 0 : _dump$jwt2.token,
projectAccessKey
});
// TODO: This property may not hold if the user adds a new network
if (!(await account$1.isMigratedAllChains().then(r => r.migratedAllChains))) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account$1))) {
throw Error('Migration cancelled, cannot open session');
}
console.log('Migrating account...');
await account$1.signAllMigrations(editConfigOnMigration);
if (!(await account$1.isMigratedAllChains().then(r => r.migratedAllChains))) throw Error('Failed to migrate account');
}
// We may need to update the JWT if the account has been migrated
} else if (isSessionDumpV2(dump)) {
var _dump$jwt$expiration2, _dump$jwt3, _dump$jwt4;
const jwtExpired = ((_dump$jwt$expiration2 = (_dump$jwt3 = dump.jwt) == null ? void 0 : _dump$jwt3.expiration) != null ? _dump$jwt$expiration2 : 0) < Math.floor(Date.now() / 1000);
account$1 = new account.Account({
address: dump.address,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt4 = dump.jwt) == null ? void 0 : _dump$jwt4.token,
projectAccessKey
});
} else {
throw Error('Invalid dump format');
}
let servicesObj;
if (services) {
var _dump$jwt$expiration3;
servicesObj = new Services(account$1, services, dump.jwt && {
jwt: {
token: Promise.resolve(dump.jwt.token),
expiration: (_dump$jwt$expiration3 = dump.jwt.expiration) != null ? _dump$jwt$expiration3 : utils.jwtDecodeClaims(dump.jwt.token).exp
},
metadata: dump.metadata
});
}
return new Session(networks, contexts, account$1, servicesObj);
}
}
const ValidateSequenceWalletProof = (readerFor, tracker, context) => {
return async (_provider, chainId, proof) => {
const digest = proof.messageDigest();
const isValid = await readerFor(chainId).isValidSignature(proof.address, digest, proof.signature);
return {
isValid
};
};
};
exports.AuthError = AuthError;
exports.CRITICAL_CHAINS = CRITICAL_CHAINS;
exports.ErrAccountIsRequired = ErrAccountIsRequired;
exports.Session = Session;
exports.SessionSettingsDefault = SessionSettingsDefault;
exports.ValidateSequenceWalletProof = ValidateSequenceWalletProof;
exports.isSessionDumpV1 = isSessionDumpV1;
exports.isSessionDumpV2 = isSessionDumpV2;
exports.signAuthorization = signAuthorization;
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var ethers = require('ethers');
var ethauth = require('@0xsequence/ethauth');
var network = require('@0xsequence/network');
var account = require('@0xsequence/account');
var api = require('@0xsequence/api');
var indexer = require('@0xsequence/indexer');
var metadata = require('@0xsequence/metadata');
var utils = require('@0xsequence/utils');
var sessions = require('@0xsequence/sessions');
var signhub = require('@0xsequence/signhub');
var core = require('@0xsequence/core');
// Default session expiration of ETHAuth token (1 week)
const DEFAULT_SESSION_EXPIRATION = 60 * 60 * 24 * 7;
const EXPIRATION_JWT_MARGIN = 60; // seconds
class Services {
constructor(account, settings, status = {}, projectAccessKey) {
this.account = account;
this.settings = settings;
this.status = status;
this._initialAuthRequest = void 0;
// proof strings are indexed by account address and app name, see getProofStringKey()
this.proofStrings = new Map();
this.onAuthCallbacks = [];
this.apiClient = void 0;
this.metadataClient = void 0;
this.indexerClients = new Map();
this.indexerGateway = void 0;
this.projectAccessKey = void 0;
this.projectAccessKey = projectAccessKey;
}
now() {
return Math.floor(Date.now() / 1000);
}
get expiration() {
var _this$settings$metada;
return Math.max((_this$settings$metada = this.settings.metadata.expiration) != null ? _this$settings$metada : DEFAULT_SESSION_EXPIRATION, 120);
}
onAuth(cb) {
this.onAuthCallbacks.push(cb);
return () => this.onAuthCallbacks = this.onAuthCallbacks.filter(c => c !== cb);
}
async dump() {
if (!this.status.jwt) return {
metadata: this.settings.metadata
};
return {
jwt: {
token: await this.status.jwt.token,
expiration: this.status.jwt.expiration
},
metadata: this.status.metadata
};
}
auth(maxTries = 5) {
var _this = this;
if (this._initialAuthRequest) return this._initialAuthRequest;
this._initialAuthRequest = async function () {
const url = _this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
let jwtAuth;
for (let i = 1;; i++) {
try {
jwtAuth = (await _this.getJWT(true)).token;
break;
} catch (error) {
if (i === maxTries) {
console.error(`couldn't authenticate after ${maxTries} attempts`, error);
throw error;
}
}
}
return new api.SequenceAPIClient(url, undefined, jwtAuth);
}();
return this._initialAuthRequest;
}
async getJWT(tryAuth) {
var _this2 = this;
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
// check if we already have or are waiting for a token
if (this.status.jwt) {
const _jwt = this.status.jwt;
const _token = await _jwt.token;
if (this.now() < _jwt.expiration) {
return {
token: _token,
expiration: _jwt.expiration
};
}
// token expired, delete it and get a new one
this.status.jwt = undefined;
}
if (!tryAuth) {
throw new Error('no auth token in memory');
}
const proofStringKey = this.getProofStringKey();
const {
proofString,
expiration
} = this.getProofString(proofStringKey);
const jwt = {
token: proofString.then(async function (proofString) {
const api$1 = new api.SequenceAPIClient(url);
const authResp = await api$1.getAuthToken({
ewtString: proofString
});
if ((authResp == null ? void 0 : authResp.status) === true && authResp.jwtToken.length !== 0) {
return authResp.jwtToken;
} else {
if (!(await _this2.isProofStringValid(proofString))) {
_this2.proofStrings.delete(proofStringKey);
}
throw new Error('no auth token from server');
}
}).catch(reason => {
this.status.jwt = undefined;
throw reason;
}),
expiration
};
this.status.jwt = jwt;
jwt.token.then(token => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'fulfilled',
value: token
});
} catch (_unused) {}
});
}).catch(reason => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'rejected',
reason
});
} catch (_unused2) {}
});
});
const token = await jwt.token;
return {
token,
expiration
};
}
getProofStringKey() {
return `${this.account.address} - ${this.settings.metadata.name}`;
}
async isProofStringValid(proofString) {
try {
const ethAuth = new ethauth.ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = network.findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network$1 = new ethers.ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.ethers.JsonRpcProvider(utils.getFetchRequest(found.rpcUrl, this.projectAccessKey), network$1, {
staticNetwork: network$1
});
await ethAuth.decodeProof(proofString);
return true;
} catch (_unused3) {
return false;
}
}
async getAPIClient(tryAuth = true) {
if (!this.apiClient) {
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.apiClient = new api.SequenceAPIClient(url, undefined, jwtAuth);
}
return this.apiClient;
}
async getMetadataClient(tryAuth = true) {
if (!this.metadataClient) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.metadataClient = new metadata.SequenceMetadata(this.settings.sequenceMetadataUrl, undefined, jwtAuth);
}
return this.metadataClient;
}
async getIndexerClient(chainId, tryAuth = true) {
const network$1 = network.findNetworkConfig(this.account.networks, chainId);
if (!network$1) {
throw Error(`No network for chain ${chainId}`);
}
if (!this.indexerClients.has(network$1.chainId)) {
if (network$1.indexer) {
this.indexerClients.set(network$1.chainId, network$1.indexer);
} else if (network$1.indexerUrl) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerClients.set(network$1.chainId, new indexer.SequenceIndexer(network$1.indexerUrl, undefined, jwtAuth));
} else {
throw Error(`No indexer url for chain ${chainId}`);
}
}
return this.indexerClients.get(network$1.chainId);
}
async getIndexerGateway(tryAuth = true) {
if (!this.indexerGateway) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerGateway = new indexer.SequenceIndexerGateway(this.settings.sequenceIndexerGatewayUrl, undefined, jwtAuth);
}
return this.indexerGateway;
}
getProofString(key) {
// check if we already have or are waiting for a proof string
if (this.proofStrings.has(key)) {
const _proofString = this.proofStrings.get(key);
if (this.now() < _proofString.expiration) {
return _proofString;
}
// proof string expired, delete it and make a new one
this.proofStrings.delete(key);
}
const proof = new ethauth.Proof({
address: this.account.address
});
proof.claims.app = this.settings.metadata.name;
if (typeof window === 'object') {
proof.claims.ogn = window.location.origin;
}
proof.setExpiryIn(this.expiration);
const ethAuth = new ethauth.ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = network.findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network$1 = new ethers.ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.ethers.JsonRpcProvider(utils.getFetchRequest(found.rpcUrl, this.projectAccessKey), network$1, {
staticNetwork: network$1
});
const expiration = this.now() + this.expiration - EXPIRATION_JWT_MARGIN;
const proofString = {
proofString: Promise.resolve(
// NOTICE: TODO: Here we ask the account to sign the message
// using whatever configuration we have ON-CHAIN, this means
// that the account will still use the v1 wallet, even if the migration
// was signed.
//
// This works for Sequence webapp v1 -> v2 because all v1 configurations share the same formula
// (torus + guard), but if we ever decide to allow cross-device login, then it will not work, because
// those other signers may not be part of the configuration.
//
this.account.signDigest(proof.messageDigest(), this.settings.sequenceApiChainId, true, 'eip6492')).then(s => {
proof.signature = s;
return ethAuth.encodeProof(proof, true);
}).catch(reason => {
this.proofStrings.delete(key);
throw reason;
}),
expiration
};
this.proofStrings.set(key, proofString);
return proofString;
}
}
// signAuthorization will perform an EIP712 typed-data message signing of ETHAuth domain via the provided
// Signer and authorization options.
const signAuthorization = async (signer, chainId, options) => {
const address = ethers.ethers.getAddress(await signer.getAddress());
if (!address || address === '' || address === '0x') {
throw ErrAccountIsRequired;
}
const proof = new ethauth.Proof();
proof.address = address;
if (!options || !options.app || options.app === '') {
throw new AuthError('authorization options requires app to be set');
}
proof.claims.app = options.app;
proof.claims.ogn = options.origin;
proof.claims.n = options.nonce;
proof.setExpiryIn(options.expiry ? Math.max(options.expiry, 200) : DEFAULT_SESSION_EXPIRATION);
const typedData = proof.messageTypedData();
const chainIdNumber = network.toChainIdNumber(chainId);
proof.signature = await (signer instanceof account.Account ?
// Account can sign EIP-6492 signatures, so it doesn't require deploying the wallet
signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber, 'eip6492') : signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber));
const ethAuth = new ethauth.ETHAuth();
const proofString = await ethAuth.encodeProof(proof, true);
return {
typedData,
proofString
};
};
// TODO: review......
class AuthError extends Error {
constructor(message) {
super(message);
this.name = 'AuthError';
}
}
const ErrAccountIsRequired = new AuthError('auth error: account address is empty');
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function isSessionDumpV1(obj) {
return obj.config && obj.metadata && obj.version === undefined;
}
function isSessionDumpV2(obj) {
return obj.version === 2 && obj.address;
}
// These chains are always validated for migrations
// if they are not available, the login will fail
const CRITICAL_CHAINS = [1, 137];
const SessionSettingsDefault = {
contexts: core.commons.context.defaultContexts,
networks: network.allNetworks,
tracker: new sessions.trackers.remote.RemoteConfigTracker('https://sessions.sequence.app')
};
class Session {
constructor(networks, contexts, account, services) {
this.networks = networks;
this.contexts = contexts;
this.account = account;
this.services = services;
}
async dump() {
const base = {
version: 2,
address: this.account.address
};
if (this.services) {
return _extends({}, base, await this.services.dump());
}
return base;
}
static async singleSigner(args) {
let {
signer
} = args;
if (typeof signer === 'string') {
signer = new ethers.ethers.Wallet(signer);
}
const orchestrator = new signhub.Orchestrator([signer]);
const referenceSigner = await signer.getAddress();
const threshold = 1;
const addSigners = [{
weight: 1,
address: referenceSigner
}];
const selectWallet = args.selectWallet || async function (wallets) {
var _args$settings$tracke, _args$settings;
if (wallets.length === 0) return undefined;
// Find a wallet that was originally created
// as a 1/1 of the reference signer
const tracker = (_args$settings$tracke = (_args$settings = args.settings) == null ? void 0 : _args$settings.tracker) != null ? _args$settings$tracke : SessionSettingsDefault.tracker;
const configs = await Promise.all(wallets.map(async function (wallet) {
const imageHash = await tracker.imageHashOfCounterfactualWallet({
wallet
});
return {
wallet,
config: imageHash && (await tracker.configOfImageHash({
imageHash: imageHash.imageHash
}))
};
}));
for (const config of configs) {
if (!config.config) {
continue;
}
const coder = core.universal.genericCoderFor(config.config.version);
const signers = coder.config.signersOf(config.config);
if (signers.length === 1 && signers[0].address === referenceSigner) {
return config.wallet;
}
}
return undefined;
};
return Session.open(_extends({}, args, {
orchestrator,
referenceSigner,
threshold,
addSigners,
selectWallet
}));
}
static async open(args) {
var _findNetworkConfig$ch, _findNetworkConfig, _settings$services$se, _settings$services, _networks$;
const {
referenceSigner,
threshold,
addSigners,
selectWallet,
onAccountAddress,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
// The reference network is mainnet, if mainnet is not available, we use the first network
const referenceChainId = (_findNetworkConfig$ch = (_findNetworkConfig = network.findNetworkConfig(networks, (_settings$services$se = settings == null || (_settings$services = settings.services) == null ? void 0 : _settings$services.sequenceApiChainId) != null ? _settings$services$se : network.ChainId.MAINNET)) == null ? void 0 : _findNetworkConfig.chainId) != null ? _findNetworkConfig$ch : (_networks$ = networks[0]) == null ? void 0 : _networks$.chainId;
if (!referenceChainId) throw Error('No reference chain found');
const foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner
});
const selectedWallet = await selectWallet(foundWallets.map(w => w.wallet));
let account$1;
if (selectedWallet) {
onAccountAddress == null || onAccountAddress(selectedWallet);
// existing account, lets update it
account$1 = new account.Account({
address: selectedWallet,
tracker,
networks,
contexts,
orchestrator,
projectAccessKey
});
// Get the latest configuration of the wallet (on the reference chain)
// now this configuration should be of the latest version, so we can start
// manipulating it.
// NOTICE: We are performing the wallet update on a single chain, assuming that
// all other networks have the same configuration. This is not always true.
if (addSigners && addSigners.length > 0) {
// New wallets never need migrations
// (because we create them on the latest version)
let status = await account$1.status(referenceChainId);
// If the wallet was created originally on v2, then we can skip
// the migration checks all together.
if (status.original.version !== status.version || account$1.version !== status.version) {
// Account may not have been migrated yet, so we need to check
// if it has been migrated and if not, migrate it (in all chains)
const {
migratedAllChains: isFullyMigrated,
failedChains
} = await account$1.isMigratedAllChains();
// Failed chains must not contain mainnet or polygon, otherwise we cannot proceed.
if (failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to fetch account status on ${failedChains.join(', ')}`);
}
if (!isFullyMigrated) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account$1))) {
throw Error('Migration cancelled, cannot open session');
}
const {
failedChains: _failedChains
} = await account$1.signAllMigrations(editConfigOnMigration || (c => c));
if (_failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to sign migrations on ${_failedChains.join(', ')}`);
}
// If we are using a dedupped tracker we need to invalidate the cache
// otherwise we run the risk of not seeing the signed migrations reflected.
if (sessions.trackers.isDedupedTracker(tracker)) {
tracker.invalidateCache();
}
let isFullyMigrated2;
[isFullyMigrated2, status] = await Promise.all([account$1.isMigratedAllChains().then(r => r.migratedAllChains), account$1.status(referenceChainId)]);
if (!isFullyMigrated2) throw Error('Failed to migrate account');
}
}
// NOTICE: We only need to do this because the API will not be able to
// validate the v2 signature (if the account has an onchain version of 1)
// we could speed this up by sending the migration alongside the jwt request
// and letting the API validate it offchain.
if (status.onChain.version !== status.version) {
await account$1.doBootstrap(referenceChainId, undefined, status);
}
const prevConfig = status.config;
const nextConfig = account$1.coders.config.editConfig(prevConfig, {
add: addSigners,
threshold
});
// Only update the onchain config if the imageHash has changed
if (account$1.coders.config.imageHashOf(prevConfig) !== account$1.coders.config.imageHashOf(nextConfig)) {
const newConfig = account$1.coders.config.editConfig(nextConfig, {
checkpoint: account$1.coders.config.checkpointOf(prevConfig) + 1n
});
await account$1.updateConfig(newConfig);
}
}
} else {
if (!addSigners || addSigners.length === 0) {
throw Error('Cannot create new account without signers');
}
if (!threshold) {
throw Error('Cannot create new account without threshold');
}
// fresh account
account$1 = await account.Account.new({
config: {
threshold,
checkpoint: 0,
signers: addSigners
},
tracker,
contexts,
orchestrator,
networks,
projectAccessKey
});
onAccountAddress == null || onAccountAddress(account$1.address);
// sign a digest and send it to the tracker
// otherwise the tracker will not know about this account
await account$1.publishWitness();
// safety check, the remove tracker should be able to find
// this account for the reference signer
const _foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner,
noCache: true
});
if (!_foundWallets.some(w => w.wallet === account$1.address)) {
throw Error('Account not found on tracker');
}
}
let servicesObj;
if (services) {
servicesObj = new Services(account$1, services);
servicesObj.auth(); // fire and forget
servicesObj.onAuth(result => {
if (result.status === 'fulfilled') {
account$1.setJwt(result.value);
}
});
}
return new Session(networks, contexts, account$1, servicesObj);
}
static async load(args) {
const {
dump,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
let account$1;
if (isSessionDumpV1(dump)) {
var _dump$jwt$expiration, _dump$jwt, _dump$jwt2;
// Old configuration format used to also contain an "address" field
// but if it doesn't, it means that it was a "counterfactual" account
// not yet updated, so we need to compute the address
const oldAddress = dump.config.address || core.commons.context.addressOf(contexts[1], core.v1.config.ConfigCoder.imageHashOf(_extends({}, dump.config, {
version: 1
})));
const jwtExpired = ((_dump$jwt$expiration = (_dump$jwt = dump.jwt) == null ? void 0 : _dump$jwt.expiration) != null ? _dump$jwt$expiration : 0) < Math.floor(Date.now() / 1000);
account$1 = new account.Account({
address: oldAddress,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt2 = dump.jwt) == null ? void 0 : _dump$jwt2.token,
projectAccessKey
});
// TODO: This property may not hold if the user adds a new network
if (!(await account$1.isMigratedAllChains().then(r => r.migratedAllChains))) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account$1))) {
throw Error('Migration cancelled, cannot open session');
}
console.log('Migrating account...');
await account$1.signAllMigrations(editConfigOnMigration);
if (!(await account$1.isMigratedAllChains().then(r => r.migratedAllChains))) throw Error('Failed to migrate account');
}
// We may need to update the JWT if the account has been migrated
} else if (isSessionDumpV2(dump)) {
var _dump$jwt$expiration2, _dump$jwt3, _dump$jwt4;
const jwtExpired = ((_dump$jwt$expiration2 = (_dump$jwt3 = dump.jwt) == null ? void 0 : _dump$jwt3.expiration) != null ? _dump$jwt$expiration2 : 0) < Math.floor(Date.now() / 1000);
account$1 = new account.Account({
address: dump.address,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt4 = dump.jwt) == null ? void 0 : _dump$jwt4.token,
projectAccessKey
});
} else {
throw Error('Invalid dump format');
}
let servicesObj;
if (services) {
var _dump$jwt$expiration3;
servicesObj = new Services(account$1, services, dump.jwt && {
jwt: {
token: Promise.resolve(dump.jwt.token),
expiration: (_dump$jwt$expiration3 = dump.jwt.expiration) != null ? _dump$jwt$expiration3 : utils.jwtDecodeClaims(dump.jwt.token).exp
},
metadata: dump.metadata
});
}
return new Session(networks, contexts, account$1, servicesObj);
}
}
const ValidateSequenceWalletProof = (readerFor, tracker, context) => {
return async (_provider, chainId, proof) => {
const digest = proof.messageDigest();
const isValid = await readerFor(chainId).isValidSignature(proof.address, digest, proof.signature);
return {
isValid
};
};
};
exports.AuthError = AuthError;
exports.CRITICAL_CHAINS = CRITICAL_CHAINS;
exports.ErrAccountIsRequired = ErrAccountIsRequired;
exports.Session = Session;
exports.SessionSettingsDefault = SessionSettingsDefault;
exports.ValidateSequenceWalletProof = ValidateSequenceWalletProof;
exports.isSessionDumpV1 = isSessionDumpV1;
exports.isSessionDumpV2 = isSessionDumpV2;
exports.signAuthorization = signAuthorization;
import { ethers } from 'ethers';
import { ETHAuth, Proof } from '@0xsequence/ethauth';
import { findNetworkConfig, toChainIdNumber, allNetworks, ChainId } from '@0xsequence/network';
import { Account } from '@0xsequence/account';
import { SequenceAPIClient } from '@0xsequence/api';
import { SequenceIndexer, SequenceIndexerGateway } from '@0xsequence/indexer';
import { SequenceMetadata } from '@0xsequence/metadata';
import { getFetchRequest, jwtDecodeClaims } from '@0xsequence/utils';
import { trackers } from '@0xsequence/sessions';
import { Orchestrator } from '@0xsequence/signhub';
import { commons, universal, v1 } from '@0xsequence/core';
// Default session expiration of ETHAuth token (1 week)
const DEFAULT_SESSION_EXPIRATION = 60 * 60 * 24 * 7;
const EXPIRATION_JWT_MARGIN = 60; // seconds
class Services {
constructor(account, settings, status = {}, projectAccessKey) {
this.account = account;
this.settings = settings;
this.status = status;
this._initialAuthRequest = void 0;
// proof strings are indexed by account address and app name, see getProofStringKey()
this.proofStrings = new Map();
this.onAuthCallbacks = [];
this.apiClient = void 0;
this.metadataClient = void 0;
this.indexerClients = new Map();
this.indexerGateway = void 0;
this.projectAccessKey = void 0;
this.projectAccessKey = projectAccessKey;
}
now() {
return Math.floor(Date.now() / 1000);
}
get expiration() {
var _this$settings$metada;
return Math.max((_this$settings$metada = this.settings.metadata.expiration) != null ? _this$settings$metada : DEFAULT_SESSION_EXPIRATION, 120);
}
onAuth(cb) {
this.onAuthCallbacks.push(cb);
return () => this.onAuthCallbacks = this.onAuthCallbacks.filter(c => c !== cb);
}
async dump() {
if (!this.status.jwt) return {
metadata: this.settings.metadata
};
return {
jwt: {
token: await this.status.jwt.token,
expiration: this.status.jwt.expiration
},
metadata: this.status.metadata
};
}
auth(maxTries = 5) {
var _this = this;
if (this._initialAuthRequest) return this._initialAuthRequest;
this._initialAuthRequest = async function () {
const url = _this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
let jwtAuth;
for (let i = 1;; i++) {
try {
jwtAuth = (await _this.getJWT(true)).token;
break;
} catch (error) {
if (i === maxTries) {
console.error(`couldn't authenticate after ${maxTries} attempts`, error);
throw error;
}
}
}
return new SequenceAPIClient(url, undefined, jwtAuth);
}();
return this._initialAuthRequest;
}
async getJWT(tryAuth) {
var _this2 = this;
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
// check if we already have or are waiting for a token
if (this.status.jwt) {
const _jwt = this.status.jwt;
const _token = await _jwt.token;
if (this.now() < _jwt.expiration) {
return {
token: _token,
expiration: _jwt.expiration
};
}
// token expired, delete it and get a new one
this.status.jwt = undefined;
}
if (!tryAuth) {
throw new Error('no auth token in memory');
}
const proofStringKey = this.getProofStringKey();
const {
proofString,
expiration
} = this.getProofString(proofStringKey);
const jwt = {
token: proofString.then(async function (proofString) {
const api = new SequenceAPIClient(url);
const authResp = await api.getAuthToken({
ewtString: proofString
});
if ((authResp == null ? void 0 : authResp.status) === true && authResp.jwtToken.length !== 0) {
return authResp.jwtToken;
} else {
if (!(await _this2.isProofStringValid(proofString))) {
_this2.proofStrings.delete(proofStringKey);
}
throw new Error('no auth token from server');
}
}).catch(reason => {
this.status.jwt = undefined;
throw reason;
}),
expiration
};
this.status.jwt = jwt;
jwt.token.then(token => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'fulfilled',
value: token
});
} catch (_unused) {}
});
}).catch(reason => {
this.onAuthCallbacks.forEach(cb => {
try {
cb({
status: 'rejected',
reason
});
} catch (_unused2) {}
});
});
const token = await jwt.token;
return {
token,
expiration
};
}
getProofStringKey() {
return `${this.account.address} - ${this.settings.metadata.name}`;
}
async isProofStringValid(proofString) {
try {
const ethAuth = new ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network = new ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.JsonRpcProvider(getFetchRequest(found.rpcUrl, this.projectAccessKey), network, {
staticNetwork: network
});
await ethAuth.decodeProof(proofString);
return true;
} catch (_unused3) {
return false;
}
}
async getAPIClient(tryAuth = true) {
if (!this.apiClient) {
const url = this.settings.sequenceApiUrl;
if (!url) throw Error('No sequence api url');
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.apiClient = new SequenceAPIClient(url, undefined, jwtAuth);
}
return this.apiClient;
}
async getMetadataClient(tryAuth = true) {
if (!this.metadataClient) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.metadataClient = new SequenceMetadata(this.settings.sequenceMetadataUrl, undefined, jwtAuth);
}
return this.metadataClient;
}
async getIndexerClient(chainId, tryAuth = true) {
const network = findNetworkConfig(this.account.networks, chainId);
if (!network) {
throw Error(`No network for chain ${chainId}`);
}
if (!this.indexerClients.has(network.chainId)) {
if (network.indexer) {
this.indexerClients.set(network.chainId, network.indexer);
} else if (network.indexerUrl) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerClients.set(network.chainId, new SequenceIndexer(network.indexerUrl, undefined, jwtAuth));
} else {
throw Error(`No indexer url for chain ${chainId}`);
}
}
return this.indexerClients.get(network.chainId);
}
async getIndexerGateway(tryAuth = true) {
if (!this.indexerGateway) {
const jwtAuth = (await this.getJWT(tryAuth)).token;
this.indexerGateway = new SequenceIndexerGateway(this.settings.sequenceIndexerGatewayUrl, undefined, jwtAuth);
}
return this.indexerGateway;
}
getProofString(key) {
// check if we already have or are waiting for a proof string
if (this.proofStrings.has(key)) {
const _proofString = this.proofStrings.get(key);
if (this.now() < _proofString.expiration) {
return _proofString;
}
// proof string expired, delete it and make a new one
this.proofStrings.delete(key);
}
const proof = new Proof({
address: this.account.address
});
proof.claims.app = this.settings.metadata.name;
if (typeof window === 'object') {
proof.claims.ogn = window.location.origin;
}
proof.setExpiryIn(this.expiration);
const ethAuth = new ETHAuth();
const chainId = BigInt(this.settings.sequenceApiChainId);
const found = findNetworkConfig(this.account.networks, chainId);
if (!found) {
throw Error('No network found');
}
ethAuth.chainId = Number(chainId);
const network = new ethers.Network(found.name, chainId);
// TODO: Modify ETHAuth so it can take a provider instead of a url
// -----
// Can't pass jwt here since this is used for getting the jwt
ethAuth.provider = new ethers.JsonRpcProvider(getFetchRequest(found.rpcUrl, this.projectAccessKey), network, {
staticNetwork: network
});
const expiration = this.now() + this.expiration - EXPIRATION_JWT_MARGIN;
const proofString = {
proofString: Promise.resolve(
// NOTICE: TODO: Here we ask the account to sign the message
// using whatever configuration we have ON-CHAIN, this means
// that the account will still use the v1 wallet, even if the migration
// was signed.
//
// This works for Sequence webapp v1 -> v2 because all v1 configurations share the same formula
// (torus + guard), but if we ever decide to allow cross-device login, then it will not work, because
// those other signers may not be part of the configuration.
//
this.account.signDigest(proof.messageDigest(), this.settings.sequenceApiChainId, true, 'eip6492')).then(s => {
proof.signature = s;
return ethAuth.encodeProof(proof, true);
}).catch(reason => {
this.proofStrings.delete(key);
throw reason;
}),
expiration
};
this.proofStrings.set(key, proofString);
return proofString;
}
}
// signAuthorization will perform an EIP712 typed-data message signing of ETHAuth domain via the provided
// Signer and authorization options.
const signAuthorization = async (signer, chainId, options) => {
const address = ethers.getAddress(await signer.getAddress());
if (!address || address === '' || address === '0x') {
throw ErrAccountIsRequired;
}
const proof = new Proof();
proof.address = address;
if (!options || !options.app || options.app === '') {
throw new AuthError('authorization options requires app to be set');
}
proof.claims.app = options.app;
proof.claims.ogn = options.origin;
proof.claims.n = options.nonce;
proof.setExpiryIn(options.expiry ? Math.max(options.expiry, 200) : DEFAULT_SESSION_EXPIRATION);
const typedData = proof.messageTypedData();
const chainIdNumber = toChainIdNumber(chainId);
proof.signature = await (signer instanceof Account ?
// Account can sign EIP-6492 signatures, so it doesn't require deploying the wallet
signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber, 'eip6492') : signer.signTypedData(typedData.domain, typedData.types, typedData.message, chainIdNumber));
const ethAuth = new ETHAuth();
const proofString = await ethAuth.encodeProof(proof, true);
return {
typedData,
proofString
};
};
// TODO: review......
class AuthError extends Error {
constructor(message) {
super(message);
this.name = 'AuthError';
}
}
const ErrAccountIsRequired = new AuthError('auth error: account address is empty');
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function isSessionDumpV1(obj) {
return obj.config && obj.metadata && obj.version === undefined;
}
function isSessionDumpV2(obj) {
return obj.version === 2 && obj.address;
}
// These chains are always validated for migrations
// if they are not available, the login will fail
const CRITICAL_CHAINS = [1, 137];
const SessionSettingsDefault = {
contexts: commons.context.defaultContexts,
networks: allNetworks,
tracker: new trackers.remote.RemoteConfigTracker('https://sessions.sequence.app')
};
class Session {
constructor(networks, contexts, account, services) {
this.networks = networks;
this.contexts = contexts;
this.account = account;
this.services = services;
}
async dump() {
const base = {
version: 2,
address: this.account.address
};
if (this.services) {
return _extends({}, base, await this.services.dump());
}
return base;
}
static async singleSigner(args) {
let {
signer
} = args;
if (typeof signer === 'string') {
signer = new ethers.Wallet(signer);
}
const orchestrator = new Orchestrator([signer]);
const referenceSigner = await signer.getAddress();
const threshold = 1;
const addSigners = [{
weight: 1,
address: referenceSigner
}];
const selectWallet = args.selectWallet || async function (wallets) {
var _args$settings$tracke, _args$settings;
if (wallets.length === 0) return undefined;
// Find a wallet that was originally created
// as a 1/1 of the reference signer
const tracker = (_args$settings$tracke = (_args$settings = args.settings) == null ? void 0 : _args$settings.tracker) != null ? _args$settings$tracke : SessionSettingsDefault.tracker;
const configs = await Promise.all(wallets.map(async function (wallet) {
const imageHash = await tracker.imageHashOfCounterfactualWallet({
wallet
});
return {
wallet,
config: imageHash && (await tracker.configOfImageHash({
imageHash: imageHash.imageHash
}))
};
}));
for (const config of configs) {
if (!config.config) {
continue;
}
const coder = universal.genericCoderFor(config.config.version);
const signers = coder.config.signersOf(config.config);
if (signers.length === 1 && signers[0].address === referenceSigner) {
return config.wallet;
}
}
return undefined;
};
return Session.open(_extends({}, args, {
orchestrator,
referenceSigner,
threshold,
addSigners,
selectWallet
}));
}
static async open(args) {
var _findNetworkConfig$ch, _findNetworkConfig, _settings$services$se, _settings$services, _networks$;
const {
referenceSigner,
threshold,
addSigners,
selectWallet,
onAccountAddress,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
// The reference network is mainnet, if mainnet is not available, we use the first network
const referenceChainId = (_findNetworkConfig$ch = (_findNetworkConfig = findNetworkConfig(networks, (_settings$services$se = settings == null || (_settings$services = settings.services) == null ? void 0 : _settings$services.sequenceApiChainId) != null ? _settings$services$se : ChainId.MAINNET)) == null ? void 0 : _findNetworkConfig.chainId) != null ? _findNetworkConfig$ch : (_networks$ = networks[0]) == null ? void 0 : _networks$.chainId;
if (!referenceChainId) throw Error('No reference chain found');
const foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner
});
const selectedWallet = await selectWallet(foundWallets.map(w => w.wallet));
let account;
if (selectedWallet) {
onAccountAddress == null || onAccountAddress(selectedWallet);
// existing account, lets update it
account = new Account({
address: selectedWallet,
tracker,
networks,
contexts,
orchestrator,
projectAccessKey
});
// Get the latest configuration of the wallet (on the reference chain)
// now this configuration should be of the latest version, so we can start
// manipulating it.
// NOTICE: We are performing the wallet update on a single chain, assuming that
// all other networks have the same configuration. This is not always true.
if (addSigners && addSigners.length > 0) {
// New wallets never need migrations
// (because we create them on the latest version)
let status = await account.status(referenceChainId);
// If the wallet was created originally on v2, then we can skip
// the migration checks all together.
if (status.original.version !== status.version || account.version !== status.version) {
// Account may not have been migrated yet, so we need to check
// if it has been migrated and if not, migrate it (in all chains)
const {
migratedAllChains: isFullyMigrated,
failedChains
} = await account.isMigratedAllChains();
// Failed chains must not contain mainnet or polygon, otherwise we cannot proceed.
if (failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to fetch account status on ${failedChains.join(', ')}`);
}
if (!isFullyMigrated) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account))) {
throw Error('Migration cancelled, cannot open session');
}
const {
failedChains: _failedChains
} = await account.signAllMigrations(editConfigOnMigration || (c => c));
if (_failedChains.some(c => CRITICAL_CHAINS.includes(c))) {
throw Error(`Failed to sign migrations on ${_failedChains.join(', ')}`);
}
// If we are using a dedupped tracker we need to invalidate the cache
// otherwise we run the risk of not seeing the signed migrations reflected.
if (trackers.isDedupedTracker(tracker)) {
tracker.invalidateCache();
}
let isFullyMigrated2;
[isFullyMigrated2, status] = await Promise.all([account.isMigratedAllChains().then(r => r.migratedAllChains), account.status(referenceChainId)]);
if (!isFullyMigrated2) throw Error('Failed to migrate account');
}
}
// NOTICE: We only need to do this because the API will not be able to
// validate the v2 signature (if the account has an onchain version of 1)
// we could speed this up by sending the migration alongside the jwt request
// and letting the API validate it offchain.
if (status.onChain.version !== status.version) {
await account.doBootstrap(referenceChainId, undefined, status);
}
const prevConfig = status.config;
const nextConfig = account.coders.config.editConfig(prevConfig, {
add: addSigners,
threshold
});
// Only update the onchain config if the imageHash has changed
if (account.coders.config.imageHashOf(prevConfig) !== account.coders.config.imageHashOf(nextConfig)) {
const newConfig = account.coders.config.editConfig(nextConfig, {
checkpoint: account.coders.config.checkpointOf(prevConfig) + 1n
});
await account.updateConfig(newConfig);
}
}
} else {
if (!addSigners || addSigners.length === 0) {
throw Error('Cannot create new account without signers');
}
if (!threshold) {
throw Error('Cannot create new account without threshold');
}
// fresh account
account = await Account.new({
config: {
threshold,
checkpoint: 0,
signers: addSigners
},
tracker,
contexts,
orchestrator,
networks,
projectAccessKey
});
onAccountAddress == null || onAccountAddress(account.address);
// sign a digest and send it to the tracker
// otherwise the tracker will not know about this account
await account.publishWitness();
// safety check, the remove tracker should be able to find
// this account for the reference signer
const _foundWallets = await tracker.walletsOfSigner({
signer: referenceSigner,
noCache: true
});
if (!_foundWallets.some(w => w.wallet === account.address)) {
throw Error('Account not found on tracker');
}
}
let servicesObj;
if (services) {
servicesObj = new Services(account, services);
servicesObj.auth(); // fire and forget
servicesObj.onAuth(result => {
if (result.status === 'fulfilled') {
account.setJwt(result.value);
}
});
}
return new Session(networks, contexts, account, servicesObj);
}
static async load(args) {
const {
dump,
settings,
editConfigOnMigration,
onMigration,
orchestrator,
projectAccessKey
} = args;
const {
contexts,
networks,
tracker,
services
} = _extends({}, SessionSettingsDefault, settings);
let account;
if (isSessionDumpV1(dump)) {
var _dump$jwt$expiration, _dump$jwt, _dump$jwt2;
// Old configuration format used to also contain an "address" field
// but if it doesn't, it means that it was a "counterfactual" account
// not yet updated, so we need to compute the address
const oldAddress = dump.config.address || commons.context.addressOf(contexts[1], v1.config.ConfigCoder.imageHashOf(_extends({}, dump.config, {
version: 1
})));
const jwtExpired = ((_dump$jwt$expiration = (_dump$jwt = dump.jwt) == null ? void 0 : _dump$jwt.expiration) != null ? _dump$jwt$expiration : 0) < Math.floor(Date.now() / 1000);
account = new Account({
address: oldAddress,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt2 = dump.jwt) == null ? void 0 : _dump$jwt2.token,
projectAccessKey
});
// TODO: This property may not hold if the user adds a new network
if (!(await account.isMigratedAllChains().then(r => r.migratedAllChains))) {
// This is an oportunity for whoever is opening the session to
// feed the orchestrator with more signers, so that the migration
// can be completed.
if (onMigration && !(await onMigration(account))) {
throw Error('Migration cancelled, cannot open session');
}
console.log('Migrating account...');
await account.signAllMigrations(editConfigOnMigration);
if (!(await account.isMigratedAllChains().then(r => r.migratedAllChains))) throw Error('Failed to migrate account');
}
// We may need to update the JWT if the account has been migrated
} else if (isSessionDumpV2(dump)) {
var _dump$jwt$expiration2, _dump$jwt3, _dump$jwt4;
const jwtExpired = ((_dump$jwt$expiration2 = (_dump$jwt3 = dump.jwt) == null ? void 0 : _dump$jwt3.expiration) != null ? _dump$jwt$expiration2 : 0) < Math.floor(Date.now() / 1000);
account = new Account({
address: dump.address,
tracker,
networks,
contexts,
orchestrator,
jwt: jwtExpired ? undefined : (_dump$jwt4 = dump.jwt) == null ? void 0 : _dump$jwt4.token,
projectAccessKey
});
} else {
throw Error('Invalid dump format');
}
let servicesObj;
if (services) {
var _dump$jwt$expiration3;
servicesObj = new Services(account, services, dump.jwt && {
jwt: {
token: Promise.resolve(dump.jwt.token),
expiration: (_dump$jwt$expiration3 = dump.jwt.expiration) != null ? _dump$jwt$expiration3 : jwtDecodeClaims(dump.jwt.token).exp
},
metadata: dump.metadata
});
}
return new Session(networks, contexts, account, servicesObj);
}
}
const ValidateSequenceWalletProof = (readerFor, tracker, context) => {
return async (_provider, chainId, proof) => {
const digest = proof.messageDigest();
const isValid = await readerFor(chainId).isValidSignature(proof.address, digest, proof.signature);
return {
isValid
};
};
};
export { AuthError, CRITICAL_CHAINS, ErrAccountIsRequired, Session, SessionSettingsDefault, ValidateSequenceWalletProof, isSessionDumpV1, isSessionDumpV2, signAuthorization };
import { ChainIdLike } from '@0xsequence/network';
import { TypedData } from '@0xsequence/utils';
import { Signer } from '@0xsequence/wallet';
import { Account } from '@0xsequence/account';
export interface AuthorizationOptions {
app?: string;
origin?: string;
expiry?: number;
nonce?: number;
}
export interface ETHAuthProof {
typedData: TypedData;
proofString: string;
}
export declare const signAuthorization: (signer: Signer | Account, chainId: ChainIdLike, options: AuthorizationOptions) => Promise<ETHAuthProof>;
export declare class AuthError extends Error {
constructor(message?: string);
}
export declare const ErrAccountIsRequired: AuthError;
export * from "./authorization.js";
export * from "./session.js";
export * from "./proof.js";
import { commons } from '@0xsequence/core';
import { ValidatorFunc } from '@0xsequence/ethauth';
import { tracker } from '@0xsequence/sessions';
export declare const ValidateSequenceWalletProof: (readerFor: (chainId: number) => commons.reader.Reader, tracker: tracker.ConfigTracker, context: commons.context.WalletContext) => ValidatorFunc;
import { Account } from '@0xsequence/account';
import { SequenceAPIClient } from '@0xsequence/api';
import { Indexer, SequenceIndexerGateway } from '@0xsequence/indexer';
import { SequenceMetadata } from '@0xsequence/metadata';
import { ChainIdLike } from '@0xsequence/network';
import { ethers } from 'ethers';
export type SessionMeta = {
name: string;
expiration?: number;
};
export type ServicesSettings = {
metadata: SessionMeta;
sequenceApiUrl: string;
sequenceApiChainId: ethers.BigNumberish;
sequenceMetadataUrl: string;
sequenceIndexerGatewayUrl: string;
};
export type SessionJWT = {
token: string;
expiration: number;
};
export type SessionJWTPromise = {
token: Promise<string>;
expiration: number;
};
export type ProofStringPromise = {
proofString: Promise<string>;
expiration: number;
};
export declare const DEFAULT_SESSION_EXPIRATION: number;
export declare const LONG_SESSION_EXPIRATION = 30000000;
export declare class Services {
readonly account: Account;
readonly settings: ServicesSettings;
readonly status: {
jwt?: SessionJWTPromise;
metadata?: SessionMeta;
};
_initialAuthRequest: Promise<SequenceAPIClient>;
private readonly proofStrings;
private onAuthCallbacks;
private apiClient;
private metadataClient;
private indexerClients;
private indexerGateway;
private projectAccessKey?;
constructor(account: Account, settings: ServicesSettings, status?: {
jwt?: SessionJWTPromise;
metadata?: SessionMeta;
}, projectAccessKey?: string);
private now;
get expiration(): number;
onAuth(cb: (result: PromiseSettledResult<string>) => void): () => ((result: PromiseSettledResult<string>) => void)[];
dump(): Promise<{
jwt?: SessionJWT;
metadata?: SessionMeta;
}>;
auth(maxTries?: number): Promise<SequenceAPIClient>;
private getJWT;
private getProofStringKey;
private isProofStringValid;
getAPIClient(tryAuth?: boolean): Promise<SequenceAPIClient>;
getMetadataClient(tryAuth?: boolean): Promise<SequenceMetadata>;
getIndexerClient(chainId: ChainIdLike, tryAuth?: boolean): Promise<Indexer>;
getIndexerGateway(tryAuth?: boolean): Promise<SequenceIndexerGateway>;
private getProofString;
}
import { NetworkConfig } from '@0xsequence/network';
import { Account } from '@0xsequence/account';
import { ethers } from 'ethers';
import { tracker } from '@0xsequence/sessions';
import { SignatureOrchestrator, signers } from '@0xsequence/signhub';
import { migrator } from '@0xsequence/migration';
import { commons, v1 } from '@0xsequence/core';
import { Services, ServicesSettings, SessionJWT, SessionMeta } from "./services.js";
export interface SessionDumpV1 {
config: Omit<v1.config.WalletConfig, 'version'> & {
address?: string;
};
jwt?: SessionJWT;
metadata: SessionMeta;
}
export interface SessionDumpV2 {
version: 2;
address: string;
jwt?: SessionJWT;
metadata?: SessionMeta;
}
export declare function isSessionDumpV1(obj: any): obj is SessionDumpV1;
export declare function isSessionDumpV2(obj: any): obj is SessionDumpV2;
export declare const CRITICAL_CHAINS: number[];
export type SessionSettings = {
services?: ServicesSettings;
contexts: commons.context.VersionedContext;
networks: NetworkConfig[];
tracker: tracker.ConfigTracker & migrator.PresignedMigrationTracker;
};
export declare const SessionSettingsDefault: SessionSettings;
export declare class Session {
networks: NetworkConfig[];
contexts: commons.context.VersionedContext;
account: Account;
services?: Services | undefined;
constructor(networks: NetworkConfig[], contexts: commons.context.VersionedContext, account: Account, services?: Services | undefined);
dump(): Promise<SessionDumpV2>;
static singleSigner(args: {
settings?: Partial<SessionSettings>;
signer: ethers.Signer | signers.SapientSigner | string;
selectWallet?: (wallets: string[]) => Promise<string | undefined>;
onAccountAddress?: (address: string) => void;
onMigration?: (account: Account) => Promise<boolean>;
editConfigOnMigration?: (config: commons.config.Config) => commons.config.Config;
projectAccessKey: string;
}): Promise<Session>;
static open(args: {
settings?: Partial<SessionSettings>;
orchestrator: SignatureOrchestrator;
addSigners?: commons.config.SimpleSigner[];
referenceSigner: string;
threshold?: ethers.BigNumberish;
selectWallet: (wallets: string[]) => Promise<string | undefined>;
onAccountAddress?: (address: string) => void;
editConfigOnMigration?: (config: commons.config.Config) => commons.config.Config;
onMigration?: (account: Account) => Promise<boolean>;
projectAccessKey?: string;
}): Promise<Session>;
static load(args: {
settings?: Partial<SessionSettings>;
orchestrator: SignatureOrchestrator;
dump: SessionDumpV1 | SessionDumpV2;
editConfigOnMigration: (config: commons.config.Config) => commons.config.Config;
onMigration?: (account: Account) => Promise<boolean>;
projectAccessKey?: string;
}): Promise<Session>;
}
+2
-2

@@ -1,2 +0,2 @@

export * from "../src/index.js";
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMHhzZXF1ZW5jZS1hdXRoLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=
export * from "./declarations/src/index.js";
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMHhzZXF1ZW5jZS1hdXRoLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi9kZWNsYXJhdGlvbnMvc3JjL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ==

@@ -1,16 +0,7 @@

"use strict";
// this file might look strange and you might be wondering what it's for
// it's lets you import your source files by importing this entrypoint
// as you would import it if it was built with preconstruct build
// this file is slightly different to some others though
// it has a require hook which compiles your code with Babel
// this means that you don't have to set up @babel/register or anything like that
// but you can still require this module and it'll be compiled
'use strict';
// this bit of code imports the require hook and registers it
let unregister = require("../../../node_modules/.pnpm/@preconstruct+hook@0.4.0/node_modules/@preconstruct/hook").___internalHook(typeof __dirname === 'undefined' ? undefined : __dirname, "../../..", "..");
// this re-exports the source file
module.exports = require("../src/index.ts");
unregister();
if (process.env.NODE_ENV === "production") {
module.exports = require("./0xsequence-auth.cjs.prod.js");
} else {
module.exports = require("./0xsequence-auth.cjs.dev.js");
}
{
"name": "@0xsequence/auth",
"version": "2.3.19",
"version": "2.3.20",
"description": "auth sub-package for Sequence",

@@ -13,14 +13,14 @@ "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/auth",

"@0xsequence/ethauth": "^1.0.0",
"@0xsequence/abi": "2.3.19",
"@0xsequence/account": "2.3.19",
"@0xsequence/indexer": "2.3.19",
"@0xsequence/metadata": "2.3.19",
"@0xsequence/migration": "2.3.19",
"@0xsequence/sessions": "2.3.19",
"@0xsequence/core": "2.3.19",
"@0xsequence/api": "2.3.19",
"@0xsequence/signhub": "2.3.19",
"@0xsequence/wallet": "2.3.19",
"@0xsequence/utils": "2.3.19",
"@0xsequence/network": "2.3.19"
"@0xsequence/account": "2.3.20",
"@0xsequence/api": "2.3.20",
"@0xsequence/indexer": "2.3.20",
"@0xsequence/metadata": "2.3.20",
"@0xsequence/migration": "2.3.20",
"@0xsequence/network": "2.3.20",
"@0xsequence/sessions": "2.3.20",
"@0xsequence/signhub": "2.3.20",
"@0xsequence/utils": "2.3.20",
"@0xsequence/core": "2.3.20",
"@0xsequence/wallet": "2.3.20",
"@0xsequence/abi": "2.3.20"
},

@@ -36,3 +36,3 @@ "peerDependencies": {

"mockttp": "^3.6.0",
"@0xsequence/tests": "2.3.19"
"@0xsequence/tests": "2.3.20"
},

@@ -39,0 +39,0 @@ "files": [