@bounded-sh/client 0.0.42 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/REACT_NATIVE.md +12 -14
- package/dist/auth/crossmint-signer-bridge.d.ts +8 -0
- package/dist/auth/providers/email-auth-provider.d.ts +1 -1
- package/dist/auth/providers/injected-evm-wallet-provider.d.ts +37 -0
- package/dist/auth/providers/injected-evm-wallet-types.d.ts +65 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +604 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +604 -32
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +596 -30
- package/dist/index.native.js.map +1 -1
- package/dist/index.native.mjs +597 -31
- package/dist/index.native.mjs.map +1 -1
- package/package.json +19 -8
package/dist/index.native.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getActiveSessionManager, genAuthNonce, genSolanaMessage, createSessionWithSignature, getConfig, convertRemainingAccounts, buildSetDocumentsTransaction, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, reconnectWithNewAuth, deriveUserIdentityFromIdToken, getIdToken as getIdToken$1 } from '@bounded-sh/core';
|
|
1
|
+
import { getActiveSessionManager, genAuthNonce, genSolanaMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, getConfig, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genSiweMessage, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, reconnectWithNewAuth, deriveUserIdentityFromIdToken, getIdToken as getIdToken$1 } from '@bounded-sh/core';
|
|
2
2
|
export * from '@bounded-sh/core';
|
|
3
3
|
export { BoundedDeclineError, DECLINE_EVENT_NAME, aggregate, count, deriveDecline, dispatchDeclineEvent, docId, get, getConfig, getFiles, getMany, queryAggregate, runExpression, runExpressionMany, runQuery, runQueryMany, search, set, setFile, setMany, signAndSubmitTransaction, signMessage, signTransaction, subscribe } from '@bounded-sh/core';
|
|
4
4
|
import { Connection, PublicKey, VersionedTransaction, Transaction } from '@solana/web3.js';
|
|
@@ -3635,7 +3635,7 @@ function decodeJwt$2(token) {
|
|
|
3635
3635
|
// Pull the embedded-wallet identity out of the active session's id token.
|
|
3636
3636
|
// Returns null when this session has no wallet (so callers can throw the
|
|
3637
3637
|
// informative NO_EMBEDDED_WALLET error).
|
|
3638
|
-
function walletClaimsFromSession() {
|
|
3638
|
+
function walletClaimsFromSession(chain = 'solana') {
|
|
3639
3639
|
let idToken = null;
|
|
3640
3640
|
try {
|
|
3641
3641
|
idToken = getActiveSessionManager().getIdToken();
|
|
@@ -3647,14 +3647,17 @@ function walletClaimsFromSession() {
|
|
|
3647
3647
|
return null;
|
|
3648
3648
|
const c = decodeJwt$2(idToken);
|
|
3649
3649
|
const address = c['custom:walletAddress'];
|
|
3650
|
+
const evmAddress = c['custom:evmWalletAddress'];
|
|
3650
3651
|
const email = c['email'];
|
|
3651
|
-
if (typeof address !== 'string' || !address)
|
|
3652
|
-
return null;
|
|
3653
3652
|
if (typeof email !== 'string' || !email)
|
|
3654
3653
|
return null;
|
|
3654
|
+
if (chain === 'solana' && (typeof address !== 'string' || !address))
|
|
3655
|
+
return null;
|
|
3656
|
+
if (chain === 'evm' && (typeof evmAddress !== 'string' || !/^0x[0-9a-fA-F]{40}$/.test(evmAddress)))
|
|
3657
|
+
return null;
|
|
3655
3658
|
const envClaim = c['custom:walletEnvironment'];
|
|
3656
3659
|
const environment = envClaim === 'staging' || envClaim === 'production' ? envClaim : undefined;
|
|
3657
|
-
return { email, address, environment };
|
|
3660
|
+
return Object.assign(Object.assign(Object.assign({ email }, (typeof address === 'string' && address ? { address } : {})), (typeof evmAddress === 'string' ? { evmAddress: evmAddress.toLowerCase() } : {})), { environment });
|
|
3658
3661
|
}
|
|
3659
3662
|
async function signerPageUrl() {
|
|
3660
3663
|
const cfg = await getConfig();
|
|
@@ -3675,7 +3678,8 @@ async function signViaSignerPopup(action) {
|
|
|
3675
3678
|
if (!hasDOM$1()) {
|
|
3676
3679
|
throw new Error('Embedded-wallet signing is only available in the browser (popup-based).');
|
|
3677
3680
|
}
|
|
3678
|
-
const
|
|
3681
|
+
const isEvm = action.kind === 'evmTransaction';
|
|
3682
|
+
const claims = walletClaimsFromSession(isEvm ? 'evm' : 'solana');
|
|
3679
3683
|
if (!claims)
|
|
3680
3684
|
throw new Error(NO_EMBEDDED_WALLET);
|
|
3681
3685
|
const { url, origin: signerOrigin } = await signerPageUrl();
|
|
@@ -3693,7 +3697,7 @@ async function signViaSignerPopup(action) {
|
|
|
3693
3697
|
requestId,
|
|
3694
3698
|
environment: claims.environment, // may be undefined → page auto-detects
|
|
3695
3699
|
email: claims.email,
|
|
3696
|
-
address: claims.address,
|
|
3700
|
+
address: isEvm ? claims.evmAddress : claims.address,
|
|
3697
3701
|
action,
|
|
3698
3702
|
};
|
|
3699
3703
|
return await new Promise((resolve, reject) => {
|
|
@@ -3718,6 +3722,8 @@ async function signViaSignerPopup(action) {
|
|
|
3718
3722
|
const onMsg = (ev) => {
|
|
3719
3723
|
if (ev.origin !== signerOrigin)
|
|
3720
3724
|
return; // only trust the signer page origin
|
|
3725
|
+
if (ev.source !== popup)
|
|
3726
|
+
return; // only trust this signer popup window
|
|
3721
3727
|
const d = ev.data;
|
|
3722
3728
|
if (!d || typeof d !== 'object')
|
|
3723
3729
|
return;
|
|
@@ -3789,6 +3795,10 @@ function serializeTransaction(transaction) {
|
|
|
3789
3795
|
function hasEmbeddedWallet() {
|
|
3790
3796
|
return walletClaimsFromSession() != null;
|
|
3791
3797
|
}
|
|
3798
|
+
/** Whether the active email/social session carries an embedded Crossmint EVM wallet. */
|
|
3799
|
+
function hasEmbeddedEvmWallet() {
|
|
3800
|
+
return walletClaimsFromSession('evm') != null;
|
|
3801
|
+
}
|
|
3792
3802
|
/** signAndSubmitTransaction for embedded wallets: serialize + route through the
|
|
3793
3803
|
* signer popup, resolve with the on-chain transaction signature/hash. */
|
|
3794
3804
|
async function embeddedSignAndSubmit(transaction) {
|
|
@@ -3799,6 +3809,36 @@ async function embeddedSignAndSubmit(transaction) {
|
|
|
3799
3809
|
throw new Error('Signer returned no transaction hash.');
|
|
3800
3810
|
return hash;
|
|
3801
3811
|
}
|
|
3812
|
+
/** Submit a validated BoundedRuntime EVM write through the user's email signer. */
|
|
3813
|
+
async function embeddedRunEvmTransaction(transaction) {
|
|
3814
|
+
var _a, _b;
|
|
3815
|
+
if (!transaction.to || !transaction.data || !transaction.chainId)
|
|
3816
|
+
throw new Error('Incomplete embedded EVM transaction');
|
|
3817
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(transaction.to))
|
|
3818
|
+
throw new Error('Invalid embedded EVM runtime address');
|
|
3819
|
+
assertCanonicalBoundedRuntimeWriteCalldata(transaction.data);
|
|
3820
|
+
if (!Number.isSafeInteger(transaction.chainId) || transaction.chainId <= 0)
|
|
3821
|
+
throw new Error('Invalid embedded EVM chain id');
|
|
3822
|
+
if (transaction.nonce !== undefined)
|
|
3823
|
+
throw new Error('Server-supplied EVM nonces are not accepted');
|
|
3824
|
+
try {
|
|
3825
|
+
if (BigInt((_a = transaction.value) !== null && _a !== void 0 ? _a : '0') !== BigInt(0))
|
|
3826
|
+
throw new Error('Bounded EVM writes cannot transfer native value');
|
|
3827
|
+
}
|
|
3828
|
+
catch (error) {
|
|
3829
|
+
if (error instanceof Error && error.message.includes('cannot transfer'))
|
|
3830
|
+
throw error;
|
|
3831
|
+
throw new Error('Invalid embedded EVM transaction value');
|
|
3832
|
+
}
|
|
3833
|
+
const result = await signViaSignerPopup({
|
|
3834
|
+
kind: 'evmTransaction',
|
|
3835
|
+
transaction: { to: transaction.to, data: transaction.data, value: (_b = transaction.value) !== null && _b !== void 0 ? _b : '0', chainId: transaction.chainId },
|
|
3836
|
+
});
|
|
3837
|
+
const hash = result.hash || result.transactionId;
|
|
3838
|
+
if (!hash)
|
|
3839
|
+
throw new Error('Signer returned no EVM transaction hash.');
|
|
3840
|
+
return { transactionSignature: hash, blockNumber: 0, gasUsed: '0', data: result };
|
|
3841
|
+
}
|
|
3802
3842
|
|
|
3803
3843
|
// Web uses localStorage (WebSessionManager); React Native uses the configured
|
|
3804
3844
|
// ReactNativeSessionManager. getActiveSessionManager() picks the right one by
|
|
@@ -3833,7 +3873,10 @@ function userFromIdToken$1(idToken, fallbackEmail) {
|
|
|
3833
3873
|
const id = c['custom:userId'] || c.sub || c['custom:walletAddress'] || '';
|
|
3834
3874
|
const email = (c.email || fallbackEmail || null);
|
|
3835
3875
|
// Email logins have no wallet — address is null (the User type allows it).
|
|
3836
|
-
|
|
3876
|
+
const evmAddress = typeof c['custom:evmWalletAddress'] === 'string' && /^0x[0-9a-fA-F]{40}$/.test(c['custom:evmWalletAddress'])
|
|
3877
|
+
? c['custom:evmWalletAddress'].toLowerCase()
|
|
3878
|
+
: null;
|
|
3879
|
+
return { id, address: (c['custom:walletAddress'] || null), evmAddress, email, provider: emailProviderSingleton };
|
|
3837
3880
|
}
|
|
3838
3881
|
// ---------------------------------------------------------------------------
|
|
3839
3882
|
// provider
|
|
@@ -3867,7 +3910,17 @@ class EmailAuthProvider {
|
|
|
3867
3910
|
async signMessage(_message) {
|
|
3868
3911
|
throw new Error(hasEmbeddedWallet() ? EMBEDDED_WALLET_UNSUPPORTED : NO_WALLET);
|
|
3869
3912
|
}
|
|
3870
|
-
async runTransaction(
|
|
3913
|
+
async runTransaction(evm, sol, options) {
|
|
3914
|
+
if (evm && sol)
|
|
3915
|
+
throw new Error('A transaction cannot target EVM and Solana at the same time.');
|
|
3916
|
+
if (!evm)
|
|
3917
|
+
throw new Error(NO_WALLET);
|
|
3918
|
+
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false)
|
|
3919
|
+
throw new Error('Embedded EVM wallets sign and submit atomically; shouldSubmitTx:false is unsupported.');
|
|
3920
|
+
if (!hasEmbeddedEvmWallet())
|
|
3921
|
+
throw new Error('This login has no embedded EVM wallet. Enable auth.wallets on an app with an EVM collection and sign in again.');
|
|
3922
|
+
return embeddedRunEvmTransaction(evm);
|
|
3923
|
+
}
|
|
3871
3924
|
async signTransaction(_t) {
|
|
3872
3925
|
throw new Error(hasEmbeddedWallet() ? EMBEDDED_WALLET_UNSUPPORTED : NO_WALLET);
|
|
3873
3926
|
}
|
|
@@ -3973,31 +4026,45 @@ function defaultInjectedProvider() {
|
|
|
3973
4026
|
return null;
|
|
3974
4027
|
}
|
|
3975
4028
|
|
|
3976
|
-
const STORED_AUTH_METHOD_KEY$
|
|
4029
|
+
const STORED_AUTH_METHOD_KEY$3 = 'bounded_last_auth_method';
|
|
3977
4030
|
const INJECTED_AUTH_METHOD = 'phantom';
|
|
3978
|
-
const NO_WALLET_ERR = 'No injected Solana wallet found. Install a wallet like Phantom (phantom.app) and try again, ' +
|
|
4031
|
+
const NO_WALLET_ERR$1 = 'No injected Solana wallet found. Install a wallet like Phantom (phantom.app) and try again, ' +
|
|
3979
4032
|
'or pass config.injectedWalletConfig.getProvider to point at your wallet.';
|
|
3980
4033
|
class InjectedWalletProvider {
|
|
3981
4034
|
constructor(config = {}, networkUrl = null, configuredNetwork = null) {
|
|
3982
|
-
var _a
|
|
4035
|
+
var _a;
|
|
3983
4036
|
if (typeof window === 'undefined') {
|
|
3984
4037
|
throw new Error('InjectedWalletProvider can only be instantiated in a browser environment');
|
|
3985
4038
|
}
|
|
4039
|
+
// `config.rpcUrl` is the legacy wallet-login-local override. Normalize
|
|
4040
|
+
// it before precedence so an empty legacy value cannot shadow the
|
|
4041
|
+
// current top-level config.rpcUrl. Re-initialization must also replace
|
|
4042
|
+
// every singleton field; otherwise a previous app's provider/RPC can
|
|
4043
|
+
// survive getAuthProvider()'s config-key rotation.
|
|
4044
|
+
const resolvedNetworkUrl = (_a = normalizeSolanaRpcUrl(config.rpcUrl)) !== null && _a !== void 0 ? _a : normalizeSolanaRpcUrl(networkUrl);
|
|
4045
|
+
const localNetwork = typeof config.network === 'string' && config.network.trim() !== ''
|
|
4046
|
+
? config.network.trim()
|
|
4047
|
+
: null;
|
|
4048
|
+
const resolvedConfiguredNetwork = localNetwork !== null && localNetwork !== void 0 ? localNetwork : (typeof configuredNetwork === 'string' && configuredNetwork.trim() !== ''
|
|
4049
|
+
? configuredNetwork.trim()
|
|
4050
|
+
: null);
|
|
3986
4051
|
// Singleton so the SDK reuses one instance (mirrors the other providers).
|
|
3987
4052
|
if (InjectedWalletProvider.instance) {
|
|
3988
|
-
InjectedWalletProvider.instance.config = Object.assign(
|
|
4053
|
+
InjectedWalletProvider.instance.config = Object.assign({}, config);
|
|
4054
|
+
InjectedWalletProvider.instance.networkUrl = resolvedNetworkUrl;
|
|
4055
|
+
InjectedWalletProvider.instance.configuredNetwork = resolvedConfiguredNetwork;
|
|
3989
4056
|
return InjectedWalletProvider.instance;
|
|
3990
4057
|
}
|
|
3991
|
-
this.config = config;
|
|
3992
|
-
this.networkUrl =
|
|
3993
|
-
this.configuredNetwork =
|
|
4058
|
+
this.config = Object.assign({}, config);
|
|
4059
|
+
this.networkUrl = resolvedNetworkUrl;
|
|
4060
|
+
this.configuredNetwork = resolvedConfiguredNetwork;
|
|
3994
4061
|
InjectedWalletProvider.instance = this;
|
|
3995
4062
|
}
|
|
3996
4063
|
resolveProvider() {
|
|
3997
4064
|
var _a;
|
|
3998
4065
|
const provider = (_a = (this.config.getProvider ? this.config.getProvider() : null)) !== null && _a !== void 0 ? _a : defaultInjectedProvider();
|
|
3999
4066
|
if (!provider)
|
|
4000
|
-
throw new Error(NO_WALLET_ERR);
|
|
4067
|
+
throw new Error(NO_WALLET_ERR$1);
|
|
4001
4068
|
return provider;
|
|
4002
4069
|
}
|
|
4003
4070
|
/** Connect the injected wallet (if needed) and return its Solana address. */
|
|
@@ -4051,7 +4118,7 @@ class InjectedWalletProvider {
|
|
|
4051
4118
|
// Mark the auth method so clearIncompatibleSession() doesn't wipe this
|
|
4052
4119
|
// wallet session on the next init().
|
|
4053
4120
|
try {
|
|
4054
|
-
getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$
|
|
4121
|
+
getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$3, INJECTED_AUTH_METHOD);
|
|
4055
4122
|
}
|
|
4056
4123
|
catch ( /* no storage */_a) { /* no storage */ }
|
|
4057
4124
|
}
|
|
@@ -4206,6 +4273,406 @@ class InjectedWalletProvider {
|
|
|
4206
4273
|
}
|
|
4207
4274
|
InjectedWalletProvider.instance = null;
|
|
4208
4275
|
|
|
4276
|
+
// injected-evm-wallet-types.ts — tiny, dependency-free surface for the EVM
|
|
4277
|
+
// WALLET LOGIN path (BYO wallet via SIWE). The EVM twin of
|
|
4278
|
+
// injected-wallet-types.ts: kept separate from injected-evm-wallet-provider.ts
|
|
4279
|
+
// so the SDK entry can eagerly export the type surface + the provider-discovery
|
|
4280
|
+
// helper WITHOUT dragging the provider class into the default bundle. The
|
|
4281
|
+
// provider is lazy-loaded by getAuthProvider() when an app opts into
|
|
4282
|
+
// authMethod:'evm-wallet'.
|
|
4283
|
+
//
|
|
4284
|
+
// It rides the standard EIP-1193 injected provider a browser wallet exposes
|
|
4285
|
+
// (MetaMask / Rabby / Coinbase Wallet / any window.ethereum), discovered via
|
|
4286
|
+
// EIP-6963 (multi-wallet, collision-free) with a window.ethereum fallback. It
|
|
4287
|
+
// needs NO viem/ethers and NO React — EIP-1193 request() + hex strings suffice.
|
|
4288
|
+
// EIP-6963 is announcement-based: wallets add an `eip6963:requestProvider`
|
|
4289
|
+
// listener and synchronously dispatch `eip6963:announceProvider` in response. We
|
|
4290
|
+
// keep a persistent collector so announcements (including late ones) are
|
|
4291
|
+
// captured, and re-request on demand.
|
|
4292
|
+
const announcedProviders = [];
|
|
4293
|
+
let announceListenerAttached = false;
|
|
4294
|
+
function attachAnnounceListener() {
|
|
4295
|
+
if (announceListenerAttached || typeof window === 'undefined')
|
|
4296
|
+
return;
|
|
4297
|
+
announceListenerAttached = true;
|
|
4298
|
+
window.addEventListener('eip6963:announceProvider', (event) => {
|
|
4299
|
+
const detail = event === null || event === void 0 ? void 0 : event.detail;
|
|
4300
|
+
if (detail &&
|
|
4301
|
+
detail.provider &&
|
|
4302
|
+
typeof detail.provider.request === 'function' &&
|
|
4303
|
+
!announcedProviders.some((d) => { var _a, _b; return ((_a = d.info) === null || _a === void 0 ? void 0 : _a.uuid) === ((_b = detail.info) === null || _b === void 0 ? void 0 : _b.uuid); })) {
|
|
4304
|
+
announcedProviders.push(detail);
|
|
4305
|
+
}
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
/**
|
|
4309
|
+
* Request + collect EIP-6963 providers. Dispatches `eip6963:requestProvider`
|
|
4310
|
+
* (synchronous — already-loaded wallets announce in the same tick) and returns
|
|
4311
|
+
* every wallet announced so far. Kept synchronous so it works inside a user
|
|
4312
|
+
* gesture and can be eagerly exported from the SDK entry.
|
|
4313
|
+
*/
|
|
4314
|
+
function requestEip6963Providers() {
|
|
4315
|
+
if (typeof window === 'undefined')
|
|
4316
|
+
return [];
|
|
4317
|
+
attachAnnounceListener();
|
|
4318
|
+
try {
|
|
4319
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
4320
|
+
}
|
|
4321
|
+
catch (_a) {
|
|
4322
|
+
/* older browsers without Event constructor — fall through to window.ethereum */
|
|
4323
|
+
}
|
|
4324
|
+
return announcedProviders.slice();
|
|
4325
|
+
}
|
|
4326
|
+
/**
|
|
4327
|
+
* Default EVM provider discovery: EIP-6963 first (multi-wallet, collision-free),
|
|
4328
|
+
* then legacy `window.ethereum`. Returns null when no injected wallet is present.
|
|
4329
|
+
*/
|
|
4330
|
+
function defaultInjectedEvmProvider() {
|
|
4331
|
+
if (typeof window === 'undefined')
|
|
4332
|
+
return null;
|
|
4333
|
+
const details = requestEip6963Providers();
|
|
4334
|
+
if (details.length > 0)
|
|
4335
|
+
return details[0].provider;
|
|
4336
|
+
const eth = window.ethereum;
|
|
4337
|
+
if (eth && typeof eth.request === 'function')
|
|
4338
|
+
return eth;
|
|
4339
|
+
return null;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
const STORED_AUTH_METHOD_KEY$2 = 'bounded_last_auth_method';
|
|
4343
|
+
// Must equal the authMethod the registry (auth/index.ts) sets as currentAuthMethod,
|
|
4344
|
+
// so clearIncompatibleSession() keeps (not wipes) this session on the next init().
|
|
4345
|
+
const INJECTED_EVM_AUTH_METHOD = 'evm-wallet';
|
|
4346
|
+
// The /session issuer's discriminator for the SIWE (EIP-4361 secp256k1) exchange.
|
|
4347
|
+
const SIWE_SESSION_METHOD = 'siwe';
|
|
4348
|
+
const NO_WALLET_ERR = 'No injected EVM wallet found. Install a wallet like MetaMask (metamask.io) or Rabby and try again, ' +
|
|
4349
|
+
'or pass config.evmWalletConfig.getProvider to point at your wallet.';
|
|
4350
|
+
const NO_SOL_TX_ERR = 'EVM wallet login signs+submits transactions in one step (eth_sendTransaction) and has no Solana ' +
|
|
4351
|
+
'transaction surface. Use an onchain WRITE (setMany) — the worker returns a pre-built EVM tx that ' +
|
|
4352
|
+
'runTransaction submits — instead of signTransaction/signAndSubmitTransaction.';
|
|
4353
|
+
// Bounded polling for the tx receipt: ~60s at 2s cadence (L2 blocks are seconds).
|
|
4354
|
+
const RECEIPT_POLL_ATTEMPTS = 30;
|
|
4355
|
+
const RECEIPT_POLL_INTERVAL_MS = 2000;
|
|
4356
|
+
function normalizeConfig(config) {
|
|
4357
|
+
var _a;
|
|
4358
|
+
if (config.chainId !== undefined && (!Number.isSafeInteger(config.chainId) || config.chainId <= 0)) {
|
|
4359
|
+
throw new Error('EVM wallet chainId must be a positive safe integer');
|
|
4360
|
+
}
|
|
4361
|
+
let runtimeAddress;
|
|
4362
|
+
if (config.runtimeAddress !== undefined) {
|
|
4363
|
+
runtimeAddress = (_a = normalizeEvmAddress(config.runtimeAddress)) !== null && _a !== void 0 ? _a : undefined;
|
|
4364
|
+
if (!runtimeAddress)
|
|
4365
|
+
throw new Error('EVM wallet runtimeAddress is invalid');
|
|
4366
|
+
if (config.chainId === undefined)
|
|
4367
|
+
throw new Error('EVM wallet runtimeAddress requires an explicit chainId');
|
|
4368
|
+
}
|
|
4369
|
+
let runtimeAddresses;
|
|
4370
|
+
if (config.runtimeAddresses !== undefined) {
|
|
4371
|
+
runtimeAddresses = {};
|
|
4372
|
+
for (const [rawChainId, rawAddress] of Object.entries(config.runtimeAddresses)) {
|
|
4373
|
+
const chainId = Number(rawChainId);
|
|
4374
|
+
const address = normalizeEvmAddress(rawAddress);
|
|
4375
|
+
if (!Number.isSafeInteger(chainId) || chainId <= 0 || String(chainId) !== rawChainId || !address) {
|
|
4376
|
+
throw new Error(`Invalid EVM runtime allowlist entry for chain ${rawChainId}`);
|
|
4377
|
+
}
|
|
4378
|
+
runtimeAddresses[rawChainId] = address;
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
return Object.assign(Object.assign({}, config), { runtimeAddress, runtimeAddresses });
|
|
4382
|
+
}
|
|
4383
|
+
class InjectedEvmWalletProvider {
|
|
4384
|
+
constructor(config = {}) {
|
|
4385
|
+
this.authenticatedAddress = null;
|
|
4386
|
+
if (typeof window === 'undefined') {
|
|
4387
|
+
throw new Error('InjectedEvmWalletProvider can only be instantiated in a browser environment');
|
|
4388
|
+
}
|
|
4389
|
+
const normalized = normalizeConfig(config);
|
|
4390
|
+
// Singleton so the SDK reuses one instance (mirrors the other providers).
|
|
4391
|
+
if (InjectedEvmWalletProvider.instance) {
|
|
4392
|
+
InjectedEvmWalletProvider.instance.config = normalized;
|
|
4393
|
+
InjectedEvmWalletProvider.instance.authenticatedAddress = null;
|
|
4394
|
+
return InjectedEvmWalletProvider.instance;
|
|
4395
|
+
}
|
|
4396
|
+
this.config = normalized;
|
|
4397
|
+
InjectedEvmWalletProvider.instance = this;
|
|
4398
|
+
}
|
|
4399
|
+
// @user.address (the Solana wallet) is null for an EVM-wallet login. User.address
|
|
4400
|
+
// is typed `string` for legacy reasons — email logins cast the same null the same
|
|
4401
|
+
// way (email-auth-provider.ts) — so the null is cast here in one place.
|
|
4402
|
+
evmUser(address) {
|
|
4403
|
+
return { id: address, address: null, evmAddress: address, provider: this };
|
|
4404
|
+
}
|
|
4405
|
+
resolveProvider() {
|
|
4406
|
+
var _a;
|
|
4407
|
+
const provider = (_a = (this.config.getProvider ? this.config.getProvider() : null)) !== null && _a !== void 0 ? _a : defaultInjectedEvmProvider();
|
|
4408
|
+
if (!provider)
|
|
4409
|
+
throw new Error(NO_WALLET_ERR);
|
|
4410
|
+
return provider;
|
|
4411
|
+
}
|
|
4412
|
+
/** Connect the injected wallet and return its canonical lowercase EVM address. */
|
|
4413
|
+
async connectAddress(provider) {
|
|
4414
|
+
const accounts = await provider.request({ method: 'eth_requestAccounts' });
|
|
4415
|
+
const raw = Array.isArray(accounts) ? accounts[0] : undefined;
|
|
4416
|
+
const address = normalizeEvmAddress(raw);
|
|
4417
|
+
if (!address)
|
|
4418
|
+
throw new Error('Wallet did not return a valid EVM address on connect');
|
|
4419
|
+
return address;
|
|
4420
|
+
}
|
|
4421
|
+
/** Current chain id the wallet is connected to (EIP-155 number), or null. */
|
|
4422
|
+
async currentChainId(provider) {
|
|
4423
|
+
try {
|
|
4424
|
+
const hex = await provider.request({ method: 'eth_chainId' });
|
|
4425
|
+
if (typeof hex !== 'string' || !/^0x[0-9a-fA-F]+$/.test(hex))
|
|
4426
|
+
return null;
|
|
4427
|
+
const n = Number(BigInt(hex));
|
|
4428
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
4429
|
+
}
|
|
4430
|
+
catch (_a) {
|
|
4431
|
+
return null;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
async login() {
|
|
4435
|
+
var _a;
|
|
4436
|
+
setAuthLoading(true);
|
|
4437
|
+
try {
|
|
4438
|
+
const provider = this.resolveProvider();
|
|
4439
|
+
const address = await this.connectAddress(provider);
|
|
4440
|
+
// Reuse a still-valid session for THIS wallet.
|
|
4441
|
+
const existing = await getActiveSessionManager().getSession();
|
|
4442
|
+
if (existing && existing.address === address) {
|
|
4443
|
+
const user = this.evmUser(address);
|
|
4444
|
+
this.authenticatedAddress = address;
|
|
4445
|
+
setCurrentUser(user);
|
|
4446
|
+
this.markAuthMethod();
|
|
4447
|
+
return user;
|
|
4448
|
+
}
|
|
4449
|
+
// SIWE: nonce -> canonical EIP-4361 prompt -> the wallet personal_sign's it.
|
|
4450
|
+
const nonce = await genAuthNonce();
|
|
4451
|
+
const appId = (await getConfig()).appId;
|
|
4452
|
+
const chainId = (_a = this.config.chainId) !== null && _a !== void 0 ? _a : await this.currentChainId(provider);
|
|
4453
|
+
if (chainId === null)
|
|
4454
|
+
throw new Error('Could not verify the EVM wallet chain for SIWE login');
|
|
4455
|
+
const message = genSiweMessage(address, nonce, appId, this.resolveDomain(), this.resolveUri(), { chainId });
|
|
4456
|
+
// personal_sign(message, address) -> 0x r‖s‖v hex (the shape the
|
|
4457
|
+
// bounded-auth verifier's recoverSiweSigner consumes). The raw UTF-8
|
|
4458
|
+
// message is signed under the EIP-191 envelope the verifier hashes.
|
|
4459
|
+
const signature = await provider.request({
|
|
4460
|
+
method: 'personal_sign',
|
|
4461
|
+
params: [message, address],
|
|
4462
|
+
});
|
|
4463
|
+
const result = await createSessionWithSignature(address, message, signature, SIWE_SESSION_METHOD);
|
|
4464
|
+
await getActiveSessionManager().storeSession(address, result.accessToken, result.idToken, result.refreshToken);
|
|
4465
|
+
this.markAuthMethod();
|
|
4466
|
+
const user = this.evmUser(address);
|
|
4467
|
+
this.authenticatedAddress = address;
|
|
4468
|
+
setCurrentUser(user);
|
|
4469
|
+
return user;
|
|
4470
|
+
}
|
|
4471
|
+
finally {
|
|
4472
|
+
setAuthLoading(false);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
resolveDomain() {
|
|
4476
|
+
const actual = window.location.host.toLowerCase();
|
|
4477
|
+
if (this.config.domain && this.config.domain.toLowerCase() !== actual) {
|
|
4478
|
+
throw new Error('SIWE domain override must match the current browser origin');
|
|
4479
|
+
}
|
|
4480
|
+
return actual;
|
|
4481
|
+
}
|
|
4482
|
+
resolveUri() {
|
|
4483
|
+
const actual = window.location.origin.toLowerCase();
|
|
4484
|
+
if (this.config.uri) {
|
|
4485
|
+
let configured;
|
|
4486
|
+
try {
|
|
4487
|
+
configured = new URL(this.config.uri).origin.toLowerCase();
|
|
4488
|
+
}
|
|
4489
|
+
catch (_a) {
|
|
4490
|
+
throw new Error('SIWE URI override must be an absolute URL');
|
|
4491
|
+
}
|
|
4492
|
+
if (configured !== actual)
|
|
4493
|
+
throw new Error('SIWE URI override must match the current browser origin');
|
|
4494
|
+
}
|
|
4495
|
+
return actual;
|
|
4496
|
+
}
|
|
4497
|
+
markAuthMethod() {
|
|
4498
|
+
try {
|
|
4499
|
+
getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$2, INJECTED_EVM_AUTH_METHOD);
|
|
4500
|
+
}
|
|
4501
|
+
catch ( /* no storage */_a) { /* no storage */ }
|
|
4502
|
+
}
|
|
4503
|
+
async restoreSession() {
|
|
4504
|
+
const session = await getActiveSessionManager().getSession();
|
|
4505
|
+
if (!session)
|
|
4506
|
+
return null;
|
|
4507
|
+
// An EVM-wallet session stores the EVM address as its address; @user.address
|
|
4508
|
+
// (the Solana wallet) is null for this login.
|
|
4509
|
+
const address = normalizeEvmAddress(session.address);
|
|
4510
|
+
if (!address) {
|
|
4511
|
+
getActiveSessionManager().clearSession();
|
|
4512
|
+
return null;
|
|
4513
|
+
}
|
|
4514
|
+
this.authenticatedAddress = address;
|
|
4515
|
+
const user = this.evmUser(address);
|
|
4516
|
+
setCurrentUser(user);
|
|
4517
|
+
return user;
|
|
4518
|
+
}
|
|
4519
|
+
async logout() {
|
|
4520
|
+
// EIP-1193 has no standard disconnect; clearing the Bounded session is the
|
|
4521
|
+
// logout. (wallet_revokePermissions is non-standard/opt-in — skip it.)
|
|
4522
|
+
getActiveSessionManager().clearSession();
|
|
4523
|
+
this.authenticatedAddress = null;
|
|
4524
|
+
setCurrentUser(null);
|
|
4525
|
+
}
|
|
4526
|
+
// --- Signing surface -------------------------------------------------------
|
|
4527
|
+
/** App-facing message signing: EIP-191 personal_sign, returns 0x r‖s‖v hex. */
|
|
4528
|
+
async signMessage(message) {
|
|
4529
|
+
const provider = this.resolveProvider();
|
|
4530
|
+
const address = await this.connectAddress(provider);
|
|
4531
|
+
if (!this.authenticatedAddress || address !== this.authenticatedAddress) {
|
|
4532
|
+
throw new Error('The connected EVM wallet account changed after login. Sign in again before signing.');
|
|
4533
|
+
}
|
|
4534
|
+
return provider.request({ method: 'personal_sign', params: [message, address] });
|
|
4535
|
+
}
|
|
4536
|
+
// Solana-typed methods are structurally unsupported on an EVM wallet — the
|
|
4537
|
+
// EVM onchain write goes through runTransaction (eth_sendTransaction).
|
|
4538
|
+
async signTransaction(_transaction) {
|
|
4539
|
+
throw new Error(NO_SOL_TX_ERR);
|
|
4540
|
+
}
|
|
4541
|
+
async signAndSubmitTransaction(_transaction, _feePayer) {
|
|
4542
|
+
throw new Error(NO_SOL_TX_ERR);
|
|
4543
|
+
}
|
|
4544
|
+
/**
|
|
4545
|
+
* Onchain write path: submit the worker's PRE-BUILT EIP-1559 tx via
|
|
4546
|
+
* eth_sendTransaction (the injected wallet signs + submits in one step), then
|
|
4547
|
+
* poll for the receipt. The EVM branch of operations.ts's 202 dispatch calls
|
|
4548
|
+
* this with the pre-built payload in the EVM slot and the Solana slot undefined.
|
|
4549
|
+
*/
|
|
4550
|
+
async runTransaction(evmTransactionData, solTransactionData, options) {
|
|
4551
|
+
var _a, _b, _c;
|
|
4552
|
+
const evmTx = evmTransactionData;
|
|
4553
|
+
if (evmTx && solTransactionData) {
|
|
4554
|
+
throw new Error('A transaction cannot target EVM and Solana at the same time.');
|
|
4555
|
+
}
|
|
4556
|
+
if (!evmTx || !normalizeEvmAddress(evmTx.to) || typeof evmTx.data !== 'string' || !/^0x(?:[0-9a-fA-F]{2})+$/.test(evmTx.data)) {
|
|
4557
|
+
throw new Error('EVM transaction data (pre-built { to, data, chainId }) is required for an EVM onchain write');
|
|
4558
|
+
}
|
|
4559
|
+
// Exact decode, rather than a four-byte prefix check: permits the
|
|
4560
|
+
// ordinary and attested document-write entrypoints while rejecting
|
|
4561
|
+
// malformed/trailing calldata before the wallet sees a signing prompt.
|
|
4562
|
+
assertCanonicalBoundedRuntimeWriteCalldata(evmTx.data);
|
|
4563
|
+
if (!Number.isSafeInteger(evmTx.chainId) || evmTx.chainId <= 0) {
|
|
4564
|
+
throw new Error('EVM transaction chainId is required and must be a positive safe integer');
|
|
4565
|
+
}
|
|
4566
|
+
const chainId = evmTx.chainId;
|
|
4567
|
+
const trustedRuntime = (_b = (_a = this.config.runtimeAddresses) === null || _a === void 0 ? void 0 : _a[String(chainId)]) !== null && _b !== void 0 ? _b : (this.config.chainId === chainId ? this.config.runtimeAddress : undefined);
|
|
4568
|
+
if (!trustedRuntime)
|
|
4569
|
+
throw new Error(`No trusted BoundedRuntime address configured for EVM chain ${chainId}`);
|
|
4570
|
+
if (normalizeEvmAddress(evmTx.to) !== trustedRuntime) {
|
|
4571
|
+
throw new Error('EVM transaction target is not the configured BoundedRuntime');
|
|
4572
|
+
}
|
|
4573
|
+
if (evmTx.nonce !== undefined) {
|
|
4574
|
+
throw new Error('Server-supplied EVM nonces are not accepted');
|
|
4575
|
+
}
|
|
4576
|
+
try {
|
|
4577
|
+
if (BigInt((_c = evmTx.value) !== null && _c !== void 0 ? _c : '0') !== BigInt(0)) {
|
|
4578
|
+
throw new Error('BoundedRuntime.setDocuments cannot transfer native value');
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
catch (error) {
|
|
4582
|
+
if (error instanceof Error && error.message.includes('cannot transfer'))
|
|
4583
|
+
throw error;
|
|
4584
|
+
throw new Error('Invalid EVM transaction value');
|
|
4585
|
+
}
|
|
4586
|
+
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false) {
|
|
4587
|
+
// Injected EVM wallets sign+submit atomically; eth_signTransaction is not
|
|
4588
|
+
// broadly supported (MetaMask removed it), so we cannot return a signed-
|
|
4589
|
+
// but-unsubmitted tx here. Fail clearly instead of silently submitting.
|
|
4590
|
+
throw new Error('shouldSubmitTx:false is unsupported for injected EVM wallets (they sign and submit atomically).');
|
|
4591
|
+
}
|
|
4592
|
+
const provider = this.resolveProvider();
|
|
4593
|
+
const from = await this.connectAddress(provider);
|
|
4594
|
+
if (!this.authenticatedAddress || from !== this.authenticatedAddress) {
|
|
4595
|
+
throw new Error('The connected EVM wallet account changed after login. Sign in again before submitting this write.');
|
|
4596
|
+
}
|
|
4597
|
+
// Chain-switch guard: the pre-built tx names the chain it MUST execute on.
|
|
4598
|
+
if (typeof evmTx.chainId === 'number') {
|
|
4599
|
+
const current = await this.currentChainId(provider);
|
|
4600
|
+
if (current === null)
|
|
4601
|
+
throw new Error('Could not verify the EVM wallet chain; refusing to submit the transaction.');
|
|
4602
|
+
if (current !== evmTx.chainId) {
|
|
4603
|
+
const targetHex = '0x' + evmTx.chainId.toString(16);
|
|
4604
|
+
try {
|
|
4605
|
+
await provider.request({
|
|
4606
|
+
method: 'wallet_switchEthereumChain',
|
|
4607
|
+
params: [{ chainId: targetHex }],
|
|
4608
|
+
});
|
|
4609
|
+
}
|
|
4610
|
+
catch (err) {
|
|
4611
|
+
throw new Error(`EVM wallet is on chain ${current} but this write targets chain ${evmTx.chainId}, ` +
|
|
4612
|
+
`and the chain switch was rejected or failed${(err === null || err === void 0 ? void 0 : err.message) ? `: ${err.message}` : ''}. ` +
|
|
4613
|
+
`Switch your wallet to chain ${evmTx.chainId} and retry.`);
|
|
4614
|
+
}
|
|
4615
|
+
const switched = await this.currentChainId(provider);
|
|
4616
|
+
if (switched !== evmTx.chainId) {
|
|
4617
|
+
throw new Error(`EVM wallet did not switch to required chain ${evmTx.chainId}; refusing to submit.`);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
const txParams = { from, to: normalizeEvmAddress(evmTx.to), data: evmTx.data.toLowerCase() };
|
|
4622
|
+
txParams.value = '0x0';
|
|
4623
|
+
// Gas, fee and nonce hints come from the server and are not part of the
|
|
4624
|
+
// governed action. Deliberately omit them: the wallet estimates locally,
|
|
4625
|
+
// preventing a compromised response from forcing an abusive fee/nonce.
|
|
4626
|
+
const txHash = await provider.request({
|
|
4627
|
+
method: 'eth_sendTransaction',
|
|
4628
|
+
params: [txParams],
|
|
4629
|
+
});
|
|
4630
|
+
if (typeof txHash !== 'string' || !/^0x[0-9a-fA-F]{64}$/.test(txHash)) {
|
|
4631
|
+
throw new Error('EVM wallet returned an invalid transaction hash');
|
|
4632
|
+
}
|
|
4633
|
+
const receipt = await this.waitForReceipt(provider, txHash);
|
|
4634
|
+
if (!receipt || typeof receipt.status !== 'string' || !/^0x[0-9a-fA-F]+$/.test(receipt.status)) {
|
|
4635
|
+
throw new Error(`EVM transaction ${txHash} returned an invalid receipt status`);
|
|
4636
|
+
}
|
|
4637
|
+
if (BigInt(receipt.status) !== BigInt(1)) {
|
|
4638
|
+
throw new Error(`EVM transaction ${txHash} reverted on-chain`);
|
|
4639
|
+
}
|
|
4640
|
+
if (typeof receipt.blockNumber !== 'string' || !/^0x[0-9a-fA-F]+$/.test(receipt.blockNumber)) {
|
|
4641
|
+
throw new Error(`EVM transaction ${txHash} returned an invalid block number`);
|
|
4642
|
+
}
|
|
4643
|
+
const blockNumber = BigInt(receipt.blockNumber);
|
|
4644
|
+
if (blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
|
|
4645
|
+
throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
|
|
4646
|
+
if (typeof receipt.gasUsed !== 'string' || !/^0x[0-9a-fA-F]+$/.test(receipt.gasUsed)) {
|
|
4647
|
+
throw new Error(`EVM transaction ${txHash} returned invalid gas usage`);
|
|
4648
|
+
}
|
|
4649
|
+
return {
|
|
4650
|
+
transactionSignature: txHash,
|
|
4651
|
+
blockNumber: Number(blockNumber),
|
|
4652
|
+
gasUsed: BigInt(receipt.gasUsed).toString(),
|
|
4653
|
+
data: receipt,
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4656
|
+
/** Poll eth_getTransactionReceipt until it lands (bounded retries) or throw. */
|
|
4657
|
+
async waitForReceipt(provider, txHash) {
|
|
4658
|
+
for (let attempt = 0; attempt < RECEIPT_POLL_ATTEMPTS; attempt++) {
|
|
4659
|
+
const receipt = await provider.request({
|
|
4660
|
+
method: 'eth_getTransactionReceipt',
|
|
4661
|
+
params: [txHash],
|
|
4662
|
+
});
|
|
4663
|
+
if (receipt)
|
|
4664
|
+
return receipt;
|
|
4665
|
+
await new Promise((r) => setTimeout(r, RECEIPT_POLL_INTERVAL_MS));
|
|
4666
|
+
}
|
|
4667
|
+
throw new Error(`EVM transaction ${txHash} was submitted but no receipt confirmed within ` +
|
|
4668
|
+
`${(RECEIPT_POLL_ATTEMPTS * RECEIPT_POLL_INTERVAL_MS) / 1000}s. It may still land; check the explorer.`);
|
|
4669
|
+
}
|
|
4670
|
+
async getNativeMethods() {
|
|
4671
|
+
return this.resolveProvider();
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
InjectedEvmWalletProvider.instance = null;
|
|
4675
|
+
|
|
4209
4676
|
// Same message the email provider uses when a hosted (OIDC) login has no
|
|
4210
4677
|
// embedded wallet — enable `auth.wallets` in policy to get one.
|
|
4211
4678
|
const OIDC_NO_WALLET = 'This login has no embedded wallet. Enable `"auth": { "wallets": true }` in ' +
|
|
@@ -4255,6 +4722,17 @@ const oidcProviderSingleton = {
|
|
|
4255
4722
|
async signTransaction(_t) {
|
|
4256
4723
|
throw new Error(hasEmbeddedWallet() ? EMBEDDED_WALLET_UNSUPPORTED : OIDC_NO_WALLET);
|
|
4257
4724
|
},
|
|
4725
|
+
async runTransaction(evm, sol, options) {
|
|
4726
|
+
if (evm && sol)
|
|
4727
|
+
throw new Error('A transaction cannot target EVM and Solana at the same time.');
|
|
4728
|
+
if (!evm)
|
|
4729
|
+
throw new Error(OIDC_NO_WALLET);
|
|
4730
|
+
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false)
|
|
4731
|
+
throw new Error('Embedded EVM wallets sign and submit atomically; shouldSubmitTx:false is unsupported.');
|
|
4732
|
+
if (!hasEmbeddedEvmWallet())
|
|
4733
|
+
throw new Error('This login has no embedded EVM wallet. Enable auth.wallets on an app with an EVM collection and sign in again.');
|
|
4734
|
+
return embeddedRunEvmTransaction(evm);
|
|
4735
|
+
},
|
|
4258
4736
|
async signAndSubmitTransaction(transaction, _feePayer) {
|
|
4259
4737
|
if (!hasEmbeddedWallet())
|
|
4260
4738
|
throw new Error(OIDC_NO_WALLET);
|
|
@@ -4266,6 +4744,9 @@ function userFromIdToken(idToken) {
|
|
|
4266
4744
|
const id = c['custom:userId'] || c.sub || c['custom:walletAddress'] || '';
|
|
4267
4745
|
return {
|
|
4268
4746
|
id, address: (c['custom:walletAddress'] || null),
|
|
4747
|
+
evmAddress: typeof c['custom:evmWalletAddress'] === 'string' && /^0x[0-9a-fA-F]{40}$/.test(c['custom:evmWalletAddress'])
|
|
4748
|
+
? c['custom:evmWalletAddress'].toLowerCase()
|
|
4749
|
+
: null,
|
|
4269
4750
|
email: (c.email || null), provider: oidcProviderSingleton,
|
|
4270
4751
|
};
|
|
4271
4752
|
}
|
|
@@ -4510,23 +4991,74 @@ async function completeLoginFromRedirect() {
|
|
|
4510
4991
|
async function loginWithPopup(opts) {
|
|
4511
4992
|
if (!hasDOM())
|
|
4512
4993
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
4513
|
-
|
|
4514
|
-
|
|
4994
|
+
// Open synchronously, before PKCE/config awaits consume the click's user-
|
|
4995
|
+
// activation window. Opening only after buildAuthorize() intermittently
|
|
4996
|
+
// left Chrome with a real named window stranded at about:blank (or blocked
|
|
4997
|
+
// it entirely), while callers waited forever for an OAuth message.
|
|
4515
4998
|
const w = opts.width || 480, h = opts.height || 720;
|
|
4516
4999
|
const left = window.screenX + (window.outerWidth - w) / 2;
|
|
4517
5000
|
const top = window.screenY + (window.outerHeight - h) / 2;
|
|
4518
|
-
const popup = window.open(
|
|
5001
|
+
const popup = window.open('', 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4519
5002
|
if (!popup)
|
|
4520
5003
|
throw new Error('Popup blocked — use loginWithRedirect() instead');
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
5004
|
+
let authorization;
|
|
5005
|
+
try {
|
|
5006
|
+
authorization = await buildAuthorize(opts);
|
|
5007
|
+
}
|
|
5008
|
+
catch (error) {
|
|
5009
|
+
try {
|
|
5010
|
+
popup.close();
|
|
5011
|
+
}
|
|
5012
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
5013
|
+
throw error;
|
|
5014
|
+
}
|
|
5015
|
+
const { url, pkce } = authorization;
|
|
5016
|
+
// Validate every remaining synchronous setup step before navigating. Both
|
|
5017
|
+
// URL parsing and sessionStorage can throw (malformed caller input, blocked
|
|
5018
|
+
// storage, quota/privacy mode); because the popup is already open to retain
|
|
5019
|
+
// user activation, close it on either failure instead of orphaning a blank
|
|
5020
|
+
// window with no listener or watchdog attached.
|
|
5021
|
+
let expectedOrigin;
|
|
5022
|
+
try {
|
|
5023
|
+
expectedOrigin = new URL(pkce.redirectUri).origin;
|
|
5024
|
+
sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
|
|
5025
|
+
}
|
|
5026
|
+
catch (error) {
|
|
5027
|
+
try {
|
|
5028
|
+
popup.close();
|
|
5029
|
+
}
|
|
5030
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
5031
|
+
throw error;
|
|
5032
|
+
}
|
|
5033
|
+
try {
|
|
5034
|
+
popup.location.replace(url);
|
|
5035
|
+
}
|
|
5036
|
+
catch (_c) {
|
|
5037
|
+
try {
|
|
5038
|
+
popup.location.href = url;
|
|
5039
|
+
}
|
|
5040
|
+
catch (_d) {
|
|
5041
|
+
try {
|
|
5042
|
+
popup.close();
|
|
5043
|
+
}
|
|
5044
|
+
catch ( /* ignore */_e) { /* ignore */ }
|
|
5045
|
+
throw new Error('Popup failed to navigate — use loginWithRedirect() instead');
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
// expectedOrigin came from the RESOLVED redirect_uri (buildAuthorize defaulted it to the
|
|
5049
|
+
// current page on web), not opts.redirectUri which may be undefined.
|
|
4524
5050
|
setAuthLoading(true); // reflect "signing in…" while the popup is open
|
|
4525
5051
|
try {
|
|
4526
5052
|
return await new Promise((resolve, reject) => {
|
|
4527
5053
|
let settled = false;
|
|
4528
5054
|
let timer;
|
|
4529
|
-
|
|
5055
|
+
let navigationTimer;
|
|
5056
|
+
const cleanup = () => {
|
|
5057
|
+
settled = true;
|
|
5058
|
+
clearInterval(timer);
|
|
5059
|
+
clearTimeout(navigationTimer);
|
|
5060
|
+
window.removeEventListener('message', onMsg);
|
|
5061
|
+
};
|
|
4530
5062
|
const onMsg = async (ev) => {
|
|
4531
5063
|
if (ev.origin !== expectedOrigin)
|
|
4532
5064
|
return; // only trust the redirect_uri origin
|
|
@@ -4554,6 +5086,27 @@ async function loginWithPopup(opts) {
|
|
|
4554
5086
|
}
|
|
4555
5087
|
};
|
|
4556
5088
|
window.addEventListener('message', onMsg);
|
|
5089
|
+
// Some browser/extension combinations can return a real popup handle but
|
|
5090
|
+
// strand the window at about:blank instead of navigating to the hosted
|
|
5091
|
+
// authorize URL. The old code then waited forever because the popup was
|
|
5092
|
+
// open and no message could ever arrive. Once the popup becomes
|
|
5093
|
+
// cross-origin, reading location throws — that is positive evidence that
|
|
5094
|
+
// navigation succeeded, so only a still-readable about:blank is rejected.
|
|
5095
|
+
navigationTimer = setTimeout(() => {
|
|
5096
|
+
if (settled || popup.closed)
|
|
5097
|
+
return;
|
|
5098
|
+
try {
|
|
5099
|
+
if (popup.location.href === 'about:blank') {
|
|
5100
|
+
cleanup();
|
|
5101
|
+
try {
|
|
5102
|
+
popup.close();
|
|
5103
|
+
}
|
|
5104
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
5105
|
+
reject(new Error('Popup stalled at about:blank — use loginWithRedirect() instead'));
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
catch ( /* cross-origin hosted auth loaded normally */_b) { /* cross-origin hosted auth loaded normally */ }
|
|
5109
|
+
}, 4000);
|
|
4557
5110
|
// The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
|
|
4558
5111
|
// races the just-posted message and would report a spurious "Login window closed" on a
|
|
4559
5112
|
// SUCCESSFUL login. When we see it closed, wait a beat for that pending message to settle
|
|
@@ -4697,7 +5250,7 @@ function stableConfigValue(value, seen = new WeakSet()) {
|
|
|
4697
5250
|
return String(value);
|
|
4698
5251
|
}
|
|
4699
5252
|
function authProviderConfigKey(config) {
|
|
4700
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
5253
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
4701
5254
|
return JSON.stringify({
|
|
4702
5255
|
authMethod: (_a = config.authMethod) !== null && _a !== void 0 ? _a : null,
|
|
4703
5256
|
chain: (_b = config.chain) !== null && _b !== void 0 ? _b : null,
|
|
@@ -4707,9 +5260,10 @@ function authProviderConfigKey(config) {
|
|
|
4707
5260
|
phantomConfig: stableConfigValue((_e = config.phantomConfig) !== null && _e !== void 0 ? _e : null),
|
|
4708
5261
|
walletLogin: stableConfigValue((_f = config.walletLogin) !== null && _f !== void 0 ? _f : null),
|
|
4709
5262
|
injectedWalletConfig: stableConfigValue((_g = config.injectedWalletConfig) !== null && _g !== void 0 ? _g : null),
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
5263
|
+
evmWalletConfig: stableConfigValue((_h = config.evmWalletConfig) !== null && _h !== void 0 ? _h : null),
|
|
5264
|
+
privyConfig: stableConfigValue((_j = config.privyConfig) !== null && _j !== void 0 ? _j : null),
|
|
5265
|
+
mobileWalletConfig: stableConfigValue((_k = config.mobileWalletConfig) !== null && _k !== void 0 ? _k : null),
|
|
5266
|
+
privyExpoProvider: objectIdentityKey((_l = config.privyExpoProvider) !== null && _l !== void 0 ? _l : null),
|
|
4713
5267
|
});
|
|
4714
5268
|
}
|
|
4715
5269
|
/**
|
|
@@ -4790,7 +5344,7 @@ function optionalProviderError(method) {
|
|
|
4790
5344
|
`or install/import an explicit wallet-provider entry once your app opts into "${method}".`);
|
|
4791
5345
|
}
|
|
4792
5346
|
async function getAuthProvider(config) {
|
|
4793
|
-
var _a;
|
|
5347
|
+
var _a, _b;
|
|
4794
5348
|
const nextProviderConfigKey = config ? authProviderConfigKey(config) : null;
|
|
4795
5349
|
if (currentAuthProvider && (!config || currentAuthProviderConfigKey === nextProviderConfigKey)) {
|
|
4796
5350
|
return currentAuthProvider;
|
|
@@ -4814,7 +5368,7 @@ async function getAuthProvider(config) {
|
|
|
4814
5368
|
// browser environment" deep in init(). The server build (`bounded-sh/server`)
|
|
4815
5369
|
// never resolves these methods (it signs with a keypair), so this can't fire for
|
|
4816
5370
|
// it — it only converts the cryptic browser-in-Node crash into a useful message.
|
|
4817
|
-
const browserWalletMethods = ['phantom', 'wallet', 'rainbowkit', 'coinbase-smart-wallet', 'onboard', 'mobile-wallet-adapter', 'privy'];
|
|
5371
|
+
const browserWalletMethods = ['phantom', 'wallet', 'evm-wallet', 'rainbowkit', 'coinbase-smart-wallet', 'onboard', 'mobile-wallet-adapter', 'privy'];
|
|
4818
5372
|
if (typeof window === 'undefined' && browserWalletMethods.includes(authMethod)) {
|
|
4819
5373
|
throw new Error(`bounded-sh: '${authMethod}' auth requires a browser, but no \`window\` was found ` +
|
|
4820
5374
|
`(you're likely running in Node). In server/Node code, import from 'bounded-sh/server' ` +
|
|
@@ -4849,6 +5403,18 @@ async function getAuthProvider(config) {
|
|
|
4849
5403
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
4850
5404
|
break;
|
|
4851
5405
|
}
|
|
5406
|
+
case "evm-wallet": {
|
|
5407
|
+
// First-class EVM WALLET LOGIN (BYO Ethereum wallet via SIWE). Slim
|
|
5408
|
+
// path: rides the EIP-1193 injected provider a browser wallet exposes
|
|
5409
|
+
// (MetaMask / Rabby / Coinbase Wallet / any window.ethereum), discovered
|
|
5410
|
+
// via EIP-6963. The user's REAL EVM wallet becomes @user.evmAddress;
|
|
5411
|
+
// login = SIWE (EIP-4361 personal_sign). No viem/ethers and no React.
|
|
5412
|
+
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
5413
|
+
// to point at a specific wallet or set chainId/domain.
|
|
5414
|
+
const evmWalletConfig = ((_b = config.evmWalletConfig) !== null && _b !== void 0 ? _b : {});
|
|
5415
|
+
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
5416
|
+
break;
|
|
5417
|
+
}
|
|
4852
5418
|
case "rainbowkit":
|
|
4853
5419
|
console.warn("Rainbow Kit auth is not yet supported.");
|
|
4854
5420
|
break;
|