decane-connect-kit 2.2.7 → 2.3.0

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/dist/index.cjs CHANGED
@@ -207,9 +207,11 @@ __export(index_exports, {
207
207
  WrongPasswordError: () => WrongPasswordError,
208
208
  createDecane: () => createDecane,
209
209
  createDecaneConnect: () => createDecaneConnect,
210
+ createEthereumProvider: () => createEthereumProvider,
210
211
  startEmailAuth: () => startEmailAuth,
211
212
  useChainSwitch: () => useChainSwitch,
212
213
  useDecane: () => useDecane,
214
+ useEthereumProvider: () => useEthereumProvider,
213
215
  useSendTransaction: () => useSendTransaction,
214
216
  useSignMessage: () => useSignMessage,
215
217
  useSignTypedData: () => useSignTypedData,
@@ -2293,6 +2295,22 @@ var TeeClient = class {
2293
2295
  sessionExpiresIn() {
2294
2296
  return this.sessionExpiresInSec;
2295
2297
  }
2298
+ /** The opaque session handle, so a live session can be persisted and resumed after a reload. */
2299
+ getHandle() {
2300
+ return this.handle;
2301
+ }
2302
+ /**
2303
+ * Adopt a handle from a persisted session (page reload) instead of opening a
2304
+ * new one. The session was already attested when first opened, and the handle
2305
+ * is bound to that CVM session — a swapped CVM would simply not recognise it —
2306
+ * so re-attestation is skipped. If the handle has expired server-side, the
2307
+ * first sign() 401s and clears it, falling back to a normal reconnect.
2308
+ */
2309
+ adoptHandle(handle, assertionPerSignature) {
2310
+ this.handle = handle;
2311
+ this.assertionPerSignature = assertionPerSignature;
2312
+ this.attested = true;
2313
+ }
2296
2314
  async ensureAttested() {
2297
2315
  if (this.attested) return;
2298
2316
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2473,9 +2491,33 @@ var SessionManager = class {
2473
2491
  this.solanaAddress = null;
2474
2492
  this.expiresAt = null;
2475
2493
  this.timeoutId = null;
2476
- if (typeof window !== "undefined") {
2477
- window.addEventListener("pagehide", () => this.end());
2478
- }
2494
+ }
2495
+ setExpiryTimer(effectiveMs) {
2496
+ if (this.timeoutId) clearTimeout(this.timeoutId);
2497
+ this.timeoutId = setTimeout(() => {
2498
+ this.end();
2499
+ this.events.emit("session-expired");
2500
+ }, Math.max(0, effectiveMs));
2501
+ }
2502
+ /** The live session handle, for persisting a resumable session across reloads. */
2503
+ getHandle() {
2504
+ return this.tee?.getHandle() ?? null;
2505
+ }
2506
+ /**
2507
+ * Resume a persisted session without re-authenticating: adopt the handle,
2508
+ * addresses, and expiry from storage. No key material or prompt involved — the
2509
+ * TEE still holds the shares behind this handle. If the handle turns out to be
2510
+ * dead (server restarted, TTL passed), the next sign() fails and the caller
2511
+ * reconnects; nothing here can sign on its own.
2512
+ */
2513
+ resume(handle, addresses, expiresAtMs) {
2514
+ this.tee = new TeeClient(this.teeUrl);
2515
+ this.tee.adoptHandle(handle, this.requireAssertionPerSignature);
2516
+ this.evmAddress = addresses.evm;
2517
+ this.solanaAddress = addresses.solana;
2518
+ this.expiresAt = expiresAtMs;
2519
+ this.setExpiryTimer(expiresAtMs - Date.now());
2520
+ this.events.emit("session-started");
2479
2521
  }
2480
2522
  /**
2481
2523
  * Opens the TEE session and resolves which addresses this wallet really has.
@@ -2736,6 +2778,17 @@ async function signEvmTransaction(tee, chain, tx, jwt) {
2736
2778
  async function signEvmMessage(tee, chain, message, jwt) {
2737
2779
  return tee.sign({ chain, kind: "message", payload: message }, jwt);
2738
2780
  }
2781
+ async function signEvmAuthorization(tee, chainId, address, nonce, jwt) {
2782
+ const serialized = await tee.sign(
2783
+ { chain: `evm:${chainId}`, kind: "authorization", payload: JSON.stringify({ chainId, address, nonce }) },
2784
+ jwt
2785
+ );
2786
+ const hex = serialized.startsWith("0x") ? serialized.slice(2) : serialized;
2787
+ const r = "0x" + hex.slice(0, 64);
2788
+ const s = "0x" + hex.slice(64, 128);
2789
+ const v = parseInt(hex.slice(128, 130), 16);
2790
+ return { yParity: v >= 27 ? v - 27 : v, r, s };
2791
+ }
2739
2792
  async function signEvmTypedData(tee, chain, typedData, jwt) {
2740
2793
  const payload = JSON.stringify(
2741
2794
  typedData,
@@ -2744,6 +2797,194 @@ async function signEvmTypedData(tee, chain, typedData, jwt) {
2744
2797
  return tee.sign({ chain, kind: "typedData", payload }, jwt);
2745
2798
  }
2746
2799
 
2800
+ // src/social/balance.ts
2801
+ var DEFAULT_RPCS = {
2802
+ "evm:1": "https://ethereum.publicnode.com",
2803
+ "evm:137": "https://polygon.publicnode.com",
2804
+ "evm:8453": "https://base.publicnode.com",
2805
+ "evm:10": "https://optimism.publicnode.com",
2806
+ "evm:42161": "https://arbitrum-one.publicnode.com",
2807
+ "evm:56": "https://bsc.publicnode.com",
2808
+ "evm:11155111": "https://ethereum-sepolia.publicnode.com",
2809
+ "evm:84532": "https://base-sepolia.publicnode.com",
2810
+ "evm:31337": "http://localhost:8545"
2811
+ };
2812
+ var EVM_SYMBOLS = {
2813
+ "evm:137": "MATIC",
2814
+ "evm:56": "BNB"
2815
+ };
2816
+ function solanaEndpoint(chain, rpcUrls) {
2817
+ if (rpcUrls?.[chain]) return rpcUrls[chain];
2818
+ const cluster = chain.slice("solana:".length);
2819
+ return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
2820
+ }
2821
+ async function fetchBalance(opts) {
2822
+ const { kind, address, chain, rpcUrls } = opts;
2823
+ if (kind === "evm") {
2824
+ const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
2825
+ if (!rpcUrl) {
2826
+ throw new Error(
2827
+ `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
2828
+ );
2829
+ }
2830
+ const { JsonRpcProvider, formatEther } = await import("ethers");
2831
+ const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
2832
+ return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
2833
+ }
2834
+ const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
2835
+ const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
2836
+ new PublicKey(address)
2837
+ );
2838
+ return {
2839
+ raw: BigInt(lamports),
2840
+ formatted: String(lamports / LAMPORTS_PER_SOL),
2841
+ symbol: "SOL",
2842
+ decimals: 9
2843
+ };
2844
+ }
2845
+ function formatBalanceDisplay(value) {
2846
+ if (value == null) return "\u2014";
2847
+ const n = Number(value);
2848
+ if (!Number.isFinite(n)) return value;
2849
+ return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
2850
+ }
2851
+
2852
+ // src/social/eip1193.ts
2853
+ function toHex(n) {
2854
+ return "0x" + n.toString(16);
2855
+ }
2856
+ function hexToBytes2(hex) {
2857
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2858
+ const bytes = new Uint8Array(clean.length / 2);
2859
+ for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(clean.substr(i * 2, 2), 16);
2860
+ return bytes;
2861
+ }
2862
+ function asBigInt(v) {
2863
+ if (v == null) return void 0;
2864
+ if (typeof v === "bigint") return v;
2865
+ if (typeof v === "number") return BigInt(v);
2866
+ if (typeof v === "string") return BigInt(v);
2867
+ return void 0;
2868
+ }
2869
+ function createEthereumProvider(client, opts = {}) {
2870
+ let chainId = opts.chainId ?? 1;
2871
+ const listeners = /* @__PURE__ */ new Map();
2872
+ function emit(event, ...args) {
2873
+ listeners.get(event)?.forEach((h) => {
2874
+ try {
2875
+ h(...args);
2876
+ } catch {
2877
+ }
2878
+ });
2879
+ }
2880
+ client.on("connected", () => {
2881
+ const evm = client.getAddresses()?.evm;
2882
+ emit("connect", { chainId: toHex(chainId) });
2883
+ if (evm) emit("accountsChanged", [evm]);
2884
+ });
2885
+ client.on("disconnected", () => {
2886
+ emit("accountsChanged", []);
2887
+ emit("disconnect", { code: 4900, message: "Wallet disconnected" });
2888
+ });
2889
+ const chainStr = () => `evm:${chainId}`;
2890
+ async function rpc(method, params) {
2891
+ const url = opts.rpcUrls?.[`evm:${chainId}`] ?? DEFAULT_RPCS[`evm:${chainId}`];
2892
+ if (!url) {
2893
+ throw Object.assign(new Error(`No RPC for chain ${chainId}. Pass rpcUrls.`), { code: 4900 });
2894
+ }
2895
+ let res;
2896
+ try {
2897
+ res = await fetch(url, {
2898
+ method: "POST",
2899
+ headers: { "Content-Type": "application/json" },
2900
+ body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params })
2901
+ });
2902
+ } catch (e) {
2903
+ throw Object.assign(new Error(`RPC request failed: ${e.message}`), { code: 4900 });
2904
+ }
2905
+ const json = await res.json();
2906
+ if (json.error) throw Object.assign(new Error(json.error.message), { code: json.error.code });
2907
+ return json.result;
2908
+ }
2909
+ async function request(args) {
2910
+ const params = Array.isArray(args.params) ? args.params : args.params ? [args.params] : [];
2911
+ const addr = () => client.getAddresses()?.evm ?? null;
2912
+ switch (args.method) {
2913
+ case "eth_requestAccounts":
2914
+ case "eth_accounts": {
2915
+ const a = addr();
2916
+ return a ? [a] : [];
2917
+ }
2918
+ case "eth_chainId":
2919
+ return toHex(chainId);
2920
+ case "net_version":
2921
+ return String(chainId);
2922
+ case "personal_sign": {
2923
+ const message = params[0];
2924
+ const bytes = typeof message === "string" && message.startsWith("0x") ? hexToBytes2(message) : message;
2925
+ return client.signMessage({ chain: chainStr(), message: bytes });
2926
+ }
2927
+ case "eth_signTypedData_v4":
2928
+ case "eth_signTypedData": {
2929
+ const raw = params[1];
2930
+ const td = typeof raw === "string" ? JSON.parse(raw) : raw;
2931
+ return client.signTypedData({
2932
+ chain: chainStr(),
2933
+ domain: td.domain,
2934
+ types: td.types,
2935
+ message: td.message,
2936
+ primaryType: td.primaryType
2937
+ });
2938
+ }
2939
+ case "eth_sendTransaction": {
2940
+ const tx = params[0];
2941
+ return client.sendTransaction({
2942
+ chain: chainStr(),
2943
+ to: tx.to,
2944
+ value: asBigInt(tx.value),
2945
+ data: tx.data ?? tx.input,
2946
+ gasLimit: asBigInt(tx.gas ?? tx.gasLimit)
2947
+ });
2948
+ }
2949
+ case "eth_signAuthorization":
2950
+ case "wallet_sign7702Authorization": {
2951
+ if (!client.signAuthorization) {
2952
+ throw Object.assign(new Error("EIP-7702 is not supported by the connected signer."), { code: 4200 });
2953
+ }
2954
+ const p = params[0];
2955
+ const cid = p.chainId != null ? Number(asBigInt(p.chainId)) : chainId;
2956
+ const address = p.address ?? p.contractAddress;
2957
+ const nonce = p.nonce != null ? Number(asBigInt(p.nonce)) : 0;
2958
+ return client.signAuthorization({ chainId: cid, address, nonce });
2959
+ }
2960
+ case "wallet_switchEthereumChain": {
2961
+ const p = params[0];
2962
+ chainId = Number(asBigInt(p.chainId));
2963
+ emit("chainChanged", toHex(chainId));
2964
+ return null;
2965
+ }
2966
+ case "wallet_addEthereumChain":
2967
+ return null;
2968
+ default:
2969
+ return rpc(args.method, params);
2970
+ }
2971
+ }
2972
+ const provider = {
2973
+ request,
2974
+ on(event, handler) {
2975
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
2976
+ listeners.get(event).add(handler);
2977
+ return this;
2978
+ },
2979
+ removeListener(event, handler) {
2980
+ listeners.get(event)?.delete(handler);
2981
+ return this;
2982
+ },
2983
+ isDecane: true
2984
+ };
2985
+ return provider;
2986
+ }
2987
+
2747
2988
  // src/social/signers/solana.ts
2748
2989
  var import_web3 = require("@solana/web3.js");
2749
2990
  function b64(bytes) {
@@ -2981,58 +3222,6 @@ async function buildPortableBackup(deviceShare, recoveryShare, password, address
2981
3222
  };
2982
3223
  }
2983
3224
 
2984
- // src/social/balance.ts
2985
- var DEFAULT_RPCS = {
2986
- "evm:1": "https://ethereum.publicnode.com",
2987
- "evm:137": "https://polygon.publicnode.com",
2988
- "evm:8453": "https://base.publicnode.com",
2989
- "evm:10": "https://optimism.publicnode.com",
2990
- "evm:42161": "https://arbitrum-one.publicnode.com",
2991
- "evm:56": "https://bsc.publicnode.com",
2992
- "evm:11155111": "https://ethereum-sepolia.publicnode.com",
2993
- "evm:84532": "https://base-sepolia.publicnode.com",
2994
- "evm:31337": "http://localhost:8545"
2995
- };
2996
- var EVM_SYMBOLS = {
2997
- "evm:137": "MATIC",
2998
- "evm:56": "BNB"
2999
- };
3000
- function solanaEndpoint(chain, rpcUrls) {
3001
- if (rpcUrls?.[chain]) return rpcUrls[chain];
3002
- const cluster = chain.slice("solana:".length);
3003
- return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
3004
- }
3005
- async function fetchBalance(opts) {
3006
- const { kind, address, chain, rpcUrls } = opts;
3007
- if (kind === "evm") {
3008
- const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
3009
- if (!rpcUrl) {
3010
- throw new Error(
3011
- `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
3012
- );
3013
- }
3014
- const { JsonRpcProvider, formatEther } = await import("ethers");
3015
- const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
3016
- return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
3017
- }
3018
- const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
3019
- const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
3020
- new PublicKey(address)
3021
- );
3022
- return {
3023
- raw: BigInt(lamports),
3024
- formatted: String(lamports / LAMPORTS_PER_SOL),
3025
- symbol: "SOL",
3026
- decimals: 9
3027
- };
3028
- }
3029
- function formatBalanceDisplay(value) {
3030
- if (value == null) return "\u2014";
3031
- const n = Number(value);
3032
- if (!Number.isFinite(n)) return value;
3033
- return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
3034
- }
3035
-
3036
3225
  // src/social/client.ts
3037
3226
  var DEFAULT_API_BASE = "https://backend.decane.app";
3038
3227
  var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
@@ -3060,6 +3249,29 @@ function clearPersisted(key) {
3060
3249
  } catch {
3061
3250
  }
3062
3251
  }
3252
+ function sessionPersistKey(apiKey) {
3253
+ return `decane:sess:${apiKey.slice(0, 20)}`;
3254
+ }
3255
+ function loadSession(key) {
3256
+ try {
3257
+ const raw = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null;
3258
+ return raw ? JSON.parse(raw) : null;
3259
+ } catch {
3260
+ return null;
3261
+ }
3262
+ }
3263
+ function saveSession(key, s) {
3264
+ try {
3265
+ sessionStorage.setItem(key, JSON.stringify(s));
3266
+ } catch {
3267
+ }
3268
+ }
3269
+ function clearSessionPersist(key) {
3270
+ try {
3271
+ sessionStorage.removeItem(key);
3272
+ } catch {
3273
+ }
3274
+ }
3063
3275
  async function createDecaneConnect(config) {
3064
3276
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
3065
3277
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
@@ -3082,6 +3294,40 @@ async function createDecaneConnect(config) {
3082
3294
  );
3083
3295
  let auth = null;
3084
3296
  let persisted = loadPersisted(PERSIST_KEY);
3297
+ let ethProvider = null;
3298
+ const persistSession = config.persistSession !== false;
3299
+ const SESSION_KEY = sessionPersistKey(config.apiKey);
3300
+ function persistLiveSession() {
3301
+ if (!persistSession) return;
3302
+ const handle = session.getHandle();
3303
+ const expiresAt = session.expiresAtTimestamp();
3304
+ if (!handle || !auth || !expiresAt) return;
3305
+ saveSession(SESSION_KEY, {
3306
+ handle,
3307
+ jwt: auth.jwt,
3308
+ userId: auth.userId,
3309
+ addresses: auth.addresses,
3310
+ expiresAt
3311
+ });
3312
+ }
3313
+ function dropLiveSession() {
3314
+ clearSessionPersist(SESSION_KEY);
3315
+ }
3316
+ if (persistSession) {
3317
+ const saved = loadSession(SESSION_KEY);
3318
+ if (saved?.handle && saved.expiresAt > Date.now() + 5e3) {
3319
+ try {
3320
+ session.resume(saved.handle, saved.addresses, saved.expiresAt);
3321
+ auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
3322
+ persisted = { userId: saved.userId, addresses: saved.addresses };
3323
+ } catch {
3324
+ dropLiveSession();
3325
+ }
3326
+ } else if (saved) {
3327
+ dropLiveSession();
3328
+ }
3329
+ }
3330
+ events.on("session-expired", () => dropLiveSession());
3085
3331
  async function pinSetup() {
3086
3332
  if (!config.promptPin) throw new PromptPinNotConfiguredError();
3087
3333
  const pin = await config.promptPin();
@@ -3195,7 +3441,7 @@ async function createDecaneConnect(config) {
3195
3441
  let evmAddress;
3196
3442
  let solanaAddress;
3197
3443
  try {
3198
- evmAddress = new import_ethers2.Wallet(toHex(master)).address;
3444
+ evmAddress = new import_ethers2.Wallet(toHex2(master)).address;
3199
3445
  solanaAddress = import_web32.Keypair.fromSeed(master).publicKey.toBase58();
3200
3446
  shareSet = await splitMaster(master, 1);
3201
3447
  } finally {
@@ -3365,6 +3611,7 @@ async function createDecaneConnect(config) {
3365
3611
  persisted = { userId, addresses };
3366
3612
  savePersisted(PERSIST_KEY, persisted);
3367
3613
  events.emit("connected");
3614
+ persistLiveSession();
3368
3615
  return { addresses, isNewUser: false };
3369
3616
  }
3370
3617
  async function handleAuthResult(result) {
@@ -3380,6 +3627,7 @@ async function createDecaneConnect(config) {
3380
3627
  persisted = { userId: result.userId, addresses };
3381
3628
  savePersisted(PERSIST_KEY, persisted);
3382
3629
  events.emit("connected");
3630
+ persistLiveSession();
3383
3631
  return { addresses, isNewUser: result.isNewUser };
3384
3632
  }
3385
3633
  const client = {
@@ -3554,6 +3802,21 @@ async function createDecaneConnect(config) {
3554
3802
  auth?.jwt
3555
3803
  );
3556
3804
  },
3805
+ async signAuthorization(req) {
3806
+ const tee = session.requireActive();
3807
+ return signEvmAuthorization(tee, req.chainId, req.address, req.nonce, auth?.jwt);
3808
+ },
3809
+ getEthereumProvider(opts) {
3810
+ if (!ethProvider) {
3811
+ const firstEvm = config.chains.find((c) => c.startsWith("evm:"));
3812
+ const defaultChainId = firstEvm ? parseInt(firstEvm.slice(4), 10) : 1;
3813
+ ethProvider = createEthereumProvider(client, {
3814
+ chainId: opts?.chainId ?? defaultChainId,
3815
+ rpcUrls: config.rpcUrls
3816
+ });
3817
+ }
3818
+ return ethProvider;
3819
+ },
3557
3820
  async signSolanaTransaction(serializedTx) {
3558
3821
  const tee = session.requireActive();
3559
3822
  let tx;
@@ -3574,6 +3837,7 @@ async function createDecaneConnect(config) {
3574
3837
  auth = null;
3575
3838
  persisted = null;
3576
3839
  clearPersisted(PERSIST_KEY);
3840
+ dropLiveSession();
3577
3841
  events.emit("disconnected");
3578
3842
  },
3579
3843
  getAddresses() {
@@ -3716,14 +3980,14 @@ function b64uToBuffer(b64u) {
3716
3980
  const b644 = b64u.replace(/-/g, "+").replace(/_/g, "/");
3717
3981
  return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
3718
3982
  }
3719
- function toHex(bytes) {
3983
+ function toHex2(bytes) {
3720
3984
  return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3721
3985
  }
3722
3986
  async function deriveAddressesFromShares(a, b) {
3723
3987
  const master = await combineShares(a, b);
3724
3988
  try {
3725
3989
  return {
3726
- evm: new import_ethers2.Wallet(toHex(master)).address,
3990
+ evm: new import_ethers2.Wallet(toHex2(master)).address,
3727
3991
  solana: import_web32.Keypair.fromSeed(master).publicKey.toBase58()
3728
3992
  };
3729
3993
  } finally {
@@ -5610,7 +5874,15 @@ function DecaneKit({ config, children }) {
5610
5874
  sdk.on("wallet-unlocking", () => setSocialAuth((p) => ({ ...p, phase: "unlocking" })));
5611
5875
  sdk.on("connected", () => setSocialAuth((p) => ({ ...p, phase: null })));
5612
5876
  const saved = sdk.getAddresses();
5613
- if (saved) {
5877
+ if (sdk.isUnlocked()) {
5878
+ setSocialAuth((p) => ({
5879
+ ...p,
5880
+ addresses: saved,
5881
+ isUnlocked: true,
5882
+ needsReconnect: false,
5883
+ sessionExpiresAt: sdk.sessionExpiresAt()
5884
+ }));
5885
+ } else if (saved) {
5614
5886
  setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
5615
5887
  }
5616
5888
  sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
@@ -5741,6 +6013,19 @@ function DecaneKit({ config, children }) {
5741
6013
  if (!sdk) throw new Error("Social wallet not connected");
5742
6014
  return sdk.getBalance(chain);
5743
6015
  }, []);
6016
+ const socialGetEthereumProvider = (0, import_react5.useCallback)((opts) => {
6017
+ const sdk = socialRef.current;
6018
+ if (!sdk) throw new Error("Social wallet not connected");
6019
+ return sdk.getEthereumProvider(opts);
6020
+ }, []);
6021
+ const socialSignAuthorization = (0, import_react5.useCallback)(
6022
+ async (req) => {
6023
+ const sdk = socialRef.current;
6024
+ if (!sdk) throw new Error("Social wallet not connected");
6025
+ return sdk.signAuthorization(req);
6026
+ },
6027
+ []
6028
+ );
5744
6029
  const socialGetBalances = (0, import_react5.useCallback)(async () => {
5745
6030
  const sdk = socialRef.current;
5746
6031
  if (!sdk) throw new Error("Social wallet not connected");
@@ -5803,6 +6088,8 @@ function DecaneKit({ config, children }) {
5803
6088
  socialSendTransaction,
5804
6089
  socialGetBalance,
5805
6090
  socialGetBalances,
6091
+ socialGetEthereumProvider,
6092
+ socialSignAuthorization,
5806
6093
  socialSignSolanaTransaction,
5807
6094
  socialSignTypedData,
5808
6095
  socialRotateShares,
@@ -6025,6 +6312,8 @@ function useSocialWallet() {
6025
6312
  socialSignTypedData,
6026
6313
  socialGetBalance,
6027
6314
  socialGetBalances,
6315
+ socialGetEthereumProvider,
6316
+ socialSignAuthorization,
6028
6317
  socialRotateShares,
6029
6318
  socialExportPortableBackup,
6030
6319
  socialHasRecoveryShare,
@@ -6069,6 +6358,10 @@ function useSocialWallet() {
6069
6358
  (chain) => run(() => socialGetBalance(chain)),
6070
6359
  [socialGetBalance]
6071
6360
  );
6361
+ const signAuthorization = (0, import_react6.useCallback)(
6362
+ (req) => run(() => socialSignAuthorization(req)),
6363
+ [socialSignAuthorization]
6364
+ );
6072
6365
  const getBalances = (0, import_react6.useCallback)(
6073
6366
  () => run(() => socialGetBalances()),
6074
6367
  [socialGetBalances]
@@ -6142,6 +6435,10 @@ function useSocialWallet() {
6142
6435
  // Balances (native token; read-only, works while locked)
6143
6436
  getBalance,
6144
6437
  getBalances,
6438
+ // EVM interop
6439
+ signAuthorization,
6440
+ // An EIP-1193 provider for viem/wagmi/ethers (memoized).
6441
+ getEthereumProvider: socialGetEthereumProvider,
6145
6442
  // Recovery (2-of-3 Shamir share set)
6146
6443
  rotateShares,
6147
6444
  exportPortableBackup,
@@ -6199,6 +6496,18 @@ function useSocialBalance(chain) {
6199
6496
  }, [addrKey, chain]);
6200
6497
  return { balance, loading, error, refetch };
6201
6498
  }
6499
+ function useEthereumProvider(opts) {
6500
+ const { socialAuth, socialGetEthereumProvider } = useDecaneContext();
6501
+ const chainId = opts?.chainId;
6502
+ return (0, import_react6.useMemo)(() => {
6503
+ if (!socialAuth.addresses?.evm) return null;
6504
+ try {
6505
+ return socialGetEthereumProvider(chainId ? { chainId } : void 0);
6506
+ } catch {
6507
+ return null;
6508
+ }
6509
+ }, [socialAuth.addresses?.evm, chainId]);
6510
+ }
6202
6511
  function toDecaneError(err) {
6203
6512
  if (err instanceof DecaneError) return err;
6204
6513
  const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
@@ -6456,9 +6765,11 @@ function useSignTypedData() {
6456
6765
  WrongPasswordError,
6457
6766
  createDecane,
6458
6767
  createDecaneConnect,
6768
+ createEthereumProvider,
6459
6769
  startEmailAuth,
6460
6770
  useChainSwitch,
6461
6771
  useDecane,
6772
+ useEthereumProvider,
6462
6773
  useSendTransaction,
6463
6774
  useSignMessage,
6464
6775
  useSignTypedData,