@bounded-sh/client 0.0.43 → 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.
@@ -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 claims = walletClaimsFromSession();
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
- return { id, address: (c['custom:walletAddress'] || null), email, provider: emailProviderSingleton };
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(_evm, _sol, _options) { throw new Error(NO_WALLET); }
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$2 = 'bounded_last_auth_method';
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, _b;
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(Object.assign({}, InjectedWalletProvider.instance.config), config);
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 = (_a = config.rpcUrl) !== null && _a !== void 0 ? _a : networkUrl;
3993
- this.configuredNetwork = (_b = config.network) !== null && _b !== void 0 ? _b : 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$2, INJECTED_AUTH_METHOD);
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
  }
@@ -4769,7 +5250,7 @@ function stableConfigValue(value, seen = new WeakSet()) {
4769
5250
  return String(value);
4770
5251
  }
4771
5252
  function authProviderConfigKey(config) {
4772
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
5253
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
4773
5254
  return JSON.stringify({
4774
5255
  authMethod: (_a = config.authMethod) !== null && _a !== void 0 ? _a : null,
4775
5256
  chain: (_b = config.chain) !== null && _b !== void 0 ? _b : null,
@@ -4779,9 +5260,10 @@ function authProviderConfigKey(config) {
4779
5260
  phantomConfig: stableConfigValue((_e = config.phantomConfig) !== null && _e !== void 0 ? _e : null),
4780
5261
  walletLogin: stableConfigValue((_f = config.walletLogin) !== null && _f !== void 0 ? _f : null),
4781
5262
  injectedWalletConfig: stableConfigValue((_g = config.injectedWalletConfig) !== null && _g !== void 0 ? _g : null),
4782
- privyConfig: stableConfigValue((_h = config.privyConfig) !== null && _h !== void 0 ? _h : null),
4783
- mobileWalletConfig: stableConfigValue((_j = config.mobileWalletConfig) !== null && _j !== void 0 ? _j : null),
4784
- privyExpoProvider: objectIdentityKey((_k = config.privyExpoProvider) !== null && _k !== void 0 ? _k : null),
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),
4785
5267
  });
4786
5268
  }
4787
5269
  /**
@@ -4862,7 +5344,7 @@ function optionalProviderError(method) {
4862
5344
  `or install/import an explicit wallet-provider entry once your app opts into "${method}".`);
4863
5345
  }
4864
5346
  async function getAuthProvider(config) {
4865
- var _a;
5347
+ var _a, _b;
4866
5348
  const nextProviderConfigKey = config ? authProviderConfigKey(config) : null;
4867
5349
  if (currentAuthProvider && (!config || currentAuthProviderConfigKey === nextProviderConfigKey)) {
4868
5350
  return currentAuthProvider;
@@ -4886,7 +5368,7 @@ async function getAuthProvider(config) {
4886
5368
  // browser environment" deep in init(). The server build (`bounded-sh/server`)
4887
5369
  // never resolves these methods (it signs with a keypair), so this can't fire for
4888
5370
  // it — it only converts the cryptic browser-in-Node crash into a useful message.
4889
- 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'];
4890
5372
  if (typeof window === 'undefined' && browserWalletMethods.includes(authMethod)) {
4891
5373
  throw new Error(`bounded-sh: '${authMethod}' auth requires a browser, but no \`window\` was found ` +
4892
5374
  `(you're likely running in Node). In server/Node code, import from 'bounded-sh/server' ` +
@@ -4921,6 +5403,18 @@ async function getAuthProvider(config) {
4921
5403
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
4922
5404
  break;
4923
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
+ }
4924
5418
  case "rainbowkit":
4925
5419
  console.warn("Rainbow Kit auth is not yet supported.");
4926
5420
  break;