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.js CHANGED
@@ -2222,6 +2222,22 @@ var TeeClient = class {
2222
2222
  sessionExpiresIn() {
2223
2223
  return this.sessionExpiresInSec;
2224
2224
  }
2225
+ /** The opaque session handle, so a live session can be persisted and resumed after a reload. */
2226
+ getHandle() {
2227
+ return this.handle;
2228
+ }
2229
+ /**
2230
+ * Adopt a handle from a persisted session (page reload) instead of opening a
2231
+ * new one. The session was already attested when first opened, and the handle
2232
+ * is bound to that CVM session — a swapped CVM would simply not recognise it —
2233
+ * so re-attestation is skipped. If the handle has expired server-side, the
2234
+ * first sign() 401s and clears it, falling back to a normal reconnect.
2235
+ */
2236
+ adoptHandle(handle, assertionPerSignature) {
2237
+ this.handle = handle;
2238
+ this.assertionPerSignature = assertionPerSignature;
2239
+ this.attested = true;
2240
+ }
2225
2241
  async ensureAttested() {
2226
2242
  if (this.attested) return;
2227
2243
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2402,9 +2418,33 @@ var SessionManager = class {
2402
2418
  this.solanaAddress = null;
2403
2419
  this.expiresAt = null;
2404
2420
  this.timeoutId = null;
2405
- if (typeof window !== "undefined") {
2406
- window.addEventListener("pagehide", () => this.end());
2407
- }
2421
+ }
2422
+ setExpiryTimer(effectiveMs) {
2423
+ if (this.timeoutId) clearTimeout(this.timeoutId);
2424
+ this.timeoutId = setTimeout(() => {
2425
+ this.end();
2426
+ this.events.emit("session-expired");
2427
+ }, Math.max(0, effectiveMs));
2428
+ }
2429
+ /** The live session handle, for persisting a resumable session across reloads. */
2430
+ getHandle() {
2431
+ return this.tee?.getHandle() ?? null;
2432
+ }
2433
+ /**
2434
+ * Resume a persisted session without re-authenticating: adopt the handle,
2435
+ * addresses, and expiry from storage. No key material or prompt involved — the
2436
+ * TEE still holds the shares behind this handle. If the handle turns out to be
2437
+ * dead (server restarted, TTL passed), the next sign() fails and the caller
2438
+ * reconnects; nothing here can sign on its own.
2439
+ */
2440
+ resume(handle, addresses, expiresAtMs) {
2441
+ this.tee = new TeeClient(this.teeUrl);
2442
+ this.tee.adoptHandle(handle, this.requireAssertionPerSignature);
2443
+ this.evmAddress = addresses.evm;
2444
+ this.solanaAddress = addresses.solana;
2445
+ this.expiresAt = expiresAtMs;
2446
+ this.setExpiryTimer(expiresAtMs - Date.now());
2447
+ this.events.emit("session-started");
2408
2448
  }
2409
2449
  /**
2410
2450
  * Opens the TEE session and resolves which addresses this wallet really has.
@@ -2665,6 +2705,17 @@ async function signEvmTransaction(tee, chain, tx, jwt) {
2665
2705
  async function signEvmMessage(tee, chain, message, jwt) {
2666
2706
  return tee.sign({ chain, kind: "message", payload: message }, jwt);
2667
2707
  }
2708
+ async function signEvmAuthorization(tee, chainId, address, nonce, jwt) {
2709
+ const serialized = await tee.sign(
2710
+ { chain: `evm:${chainId}`, kind: "authorization", payload: JSON.stringify({ chainId, address, nonce }) },
2711
+ jwt
2712
+ );
2713
+ const hex = serialized.startsWith("0x") ? serialized.slice(2) : serialized;
2714
+ const r = "0x" + hex.slice(0, 64);
2715
+ const s = "0x" + hex.slice(64, 128);
2716
+ const v = parseInt(hex.slice(128, 130), 16);
2717
+ return { yParity: v >= 27 ? v - 27 : v, r, s };
2718
+ }
2668
2719
  async function signEvmTypedData(tee, chain, typedData, jwt) {
2669
2720
  const payload = JSON.stringify(
2670
2721
  typedData,
@@ -2673,6 +2724,194 @@ async function signEvmTypedData(tee, chain, typedData, jwt) {
2673
2724
  return tee.sign({ chain, kind: "typedData", payload }, jwt);
2674
2725
  }
2675
2726
 
2727
+ // src/social/balance.ts
2728
+ var DEFAULT_RPCS = {
2729
+ "evm:1": "https://ethereum.publicnode.com",
2730
+ "evm:137": "https://polygon.publicnode.com",
2731
+ "evm:8453": "https://base.publicnode.com",
2732
+ "evm:10": "https://optimism.publicnode.com",
2733
+ "evm:42161": "https://arbitrum-one.publicnode.com",
2734
+ "evm:56": "https://bsc.publicnode.com",
2735
+ "evm:11155111": "https://ethereum-sepolia.publicnode.com",
2736
+ "evm:84532": "https://base-sepolia.publicnode.com",
2737
+ "evm:31337": "http://localhost:8545"
2738
+ };
2739
+ var EVM_SYMBOLS = {
2740
+ "evm:137": "MATIC",
2741
+ "evm:56": "BNB"
2742
+ };
2743
+ function solanaEndpoint(chain, rpcUrls) {
2744
+ if (rpcUrls?.[chain]) return rpcUrls[chain];
2745
+ const cluster = chain.slice("solana:".length);
2746
+ return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
2747
+ }
2748
+ async function fetchBalance(opts) {
2749
+ const { kind, address, chain, rpcUrls } = opts;
2750
+ if (kind === "evm") {
2751
+ const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
2752
+ if (!rpcUrl) {
2753
+ throw new Error(
2754
+ `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
2755
+ );
2756
+ }
2757
+ const { JsonRpcProvider, formatEther } = await import("ethers");
2758
+ const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
2759
+ return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
2760
+ }
2761
+ const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
2762
+ const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
2763
+ new PublicKey(address)
2764
+ );
2765
+ return {
2766
+ raw: BigInt(lamports),
2767
+ formatted: String(lamports / LAMPORTS_PER_SOL),
2768
+ symbol: "SOL",
2769
+ decimals: 9
2770
+ };
2771
+ }
2772
+ function formatBalanceDisplay(value) {
2773
+ if (value == null) return "\u2014";
2774
+ const n = Number(value);
2775
+ if (!Number.isFinite(n)) return value;
2776
+ return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
2777
+ }
2778
+
2779
+ // src/social/eip1193.ts
2780
+ function toHex(n) {
2781
+ return "0x" + n.toString(16);
2782
+ }
2783
+ function hexToBytes2(hex) {
2784
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2785
+ const bytes = new Uint8Array(clean.length / 2);
2786
+ for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(clean.substr(i * 2, 2), 16);
2787
+ return bytes;
2788
+ }
2789
+ function asBigInt(v) {
2790
+ if (v == null) return void 0;
2791
+ if (typeof v === "bigint") return v;
2792
+ if (typeof v === "number") return BigInt(v);
2793
+ if (typeof v === "string") return BigInt(v);
2794
+ return void 0;
2795
+ }
2796
+ function createEthereumProvider(client, opts = {}) {
2797
+ let chainId = opts.chainId ?? 1;
2798
+ const listeners = /* @__PURE__ */ new Map();
2799
+ function emit(event, ...args) {
2800
+ listeners.get(event)?.forEach((h) => {
2801
+ try {
2802
+ h(...args);
2803
+ } catch {
2804
+ }
2805
+ });
2806
+ }
2807
+ client.on("connected", () => {
2808
+ const evm = client.getAddresses()?.evm;
2809
+ emit("connect", { chainId: toHex(chainId) });
2810
+ if (evm) emit("accountsChanged", [evm]);
2811
+ });
2812
+ client.on("disconnected", () => {
2813
+ emit("accountsChanged", []);
2814
+ emit("disconnect", { code: 4900, message: "Wallet disconnected" });
2815
+ });
2816
+ const chainStr = () => `evm:${chainId}`;
2817
+ async function rpc(method, params) {
2818
+ const url = opts.rpcUrls?.[`evm:${chainId}`] ?? DEFAULT_RPCS[`evm:${chainId}`];
2819
+ if (!url) {
2820
+ throw Object.assign(new Error(`No RPC for chain ${chainId}. Pass rpcUrls.`), { code: 4900 });
2821
+ }
2822
+ let res;
2823
+ try {
2824
+ res = await fetch(url, {
2825
+ method: "POST",
2826
+ headers: { "Content-Type": "application/json" },
2827
+ body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params })
2828
+ });
2829
+ } catch (e) {
2830
+ throw Object.assign(new Error(`RPC request failed: ${e.message}`), { code: 4900 });
2831
+ }
2832
+ const json = await res.json();
2833
+ if (json.error) throw Object.assign(new Error(json.error.message), { code: json.error.code });
2834
+ return json.result;
2835
+ }
2836
+ async function request(args) {
2837
+ const params = Array.isArray(args.params) ? args.params : args.params ? [args.params] : [];
2838
+ const addr = () => client.getAddresses()?.evm ?? null;
2839
+ switch (args.method) {
2840
+ case "eth_requestAccounts":
2841
+ case "eth_accounts": {
2842
+ const a = addr();
2843
+ return a ? [a] : [];
2844
+ }
2845
+ case "eth_chainId":
2846
+ return toHex(chainId);
2847
+ case "net_version":
2848
+ return String(chainId);
2849
+ case "personal_sign": {
2850
+ const message = params[0];
2851
+ const bytes = typeof message === "string" && message.startsWith("0x") ? hexToBytes2(message) : message;
2852
+ return client.signMessage({ chain: chainStr(), message: bytes });
2853
+ }
2854
+ case "eth_signTypedData_v4":
2855
+ case "eth_signTypedData": {
2856
+ const raw = params[1];
2857
+ const td = typeof raw === "string" ? JSON.parse(raw) : raw;
2858
+ return client.signTypedData({
2859
+ chain: chainStr(),
2860
+ domain: td.domain,
2861
+ types: td.types,
2862
+ message: td.message,
2863
+ primaryType: td.primaryType
2864
+ });
2865
+ }
2866
+ case "eth_sendTransaction": {
2867
+ const tx = params[0];
2868
+ return client.sendTransaction({
2869
+ chain: chainStr(),
2870
+ to: tx.to,
2871
+ value: asBigInt(tx.value),
2872
+ data: tx.data ?? tx.input,
2873
+ gasLimit: asBigInt(tx.gas ?? tx.gasLimit)
2874
+ });
2875
+ }
2876
+ case "eth_signAuthorization":
2877
+ case "wallet_sign7702Authorization": {
2878
+ if (!client.signAuthorization) {
2879
+ throw Object.assign(new Error("EIP-7702 is not supported by the connected signer."), { code: 4200 });
2880
+ }
2881
+ const p = params[0];
2882
+ const cid = p.chainId != null ? Number(asBigInt(p.chainId)) : chainId;
2883
+ const address = p.address ?? p.contractAddress;
2884
+ const nonce = p.nonce != null ? Number(asBigInt(p.nonce)) : 0;
2885
+ return client.signAuthorization({ chainId: cid, address, nonce });
2886
+ }
2887
+ case "wallet_switchEthereumChain": {
2888
+ const p = params[0];
2889
+ chainId = Number(asBigInt(p.chainId));
2890
+ emit("chainChanged", toHex(chainId));
2891
+ return null;
2892
+ }
2893
+ case "wallet_addEthereumChain":
2894
+ return null;
2895
+ default:
2896
+ return rpc(args.method, params);
2897
+ }
2898
+ }
2899
+ const provider = {
2900
+ request,
2901
+ on(event, handler) {
2902
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
2903
+ listeners.get(event).add(handler);
2904
+ return this;
2905
+ },
2906
+ removeListener(event, handler) {
2907
+ listeners.get(event)?.delete(handler);
2908
+ return this;
2909
+ },
2910
+ isDecane: true
2911
+ };
2912
+ return provider;
2913
+ }
2914
+
2676
2915
  // src/social/signers/solana.ts
2677
2916
  import { VersionedTransaction as VTx } from "@solana/web3.js";
2678
2917
  function b64(bytes) {
@@ -2910,58 +3149,6 @@ async function buildPortableBackup(deviceShare, recoveryShare, password, address
2910
3149
  };
2911
3150
  }
2912
3151
 
2913
- // src/social/balance.ts
2914
- var DEFAULT_RPCS = {
2915
- "evm:1": "https://ethereum.publicnode.com",
2916
- "evm:137": "https://polygon.publicnode.com",
2917
- "evm:8453": "https://base.publicnode.com",
2918
- "evm:10": "https://optimism.publicnode.com",
2919
- "evm:42161": "https://arbitrum-one.publicnode.com",
2920
- "evm:56": "https://bsc.publicnode.com",
2921
- "evm:11155111": "https://ethereum-sepolia.publicnode.com",
2922
- "evm:84532": "https://base-sepolia.publicnode.com",
2923
- "evm:31337": "http://localhost:8545"
2924
- };
2925
- var EVM_SYMBOLS = {
2926
- "evm:137": "MATIC",
2927
- "evm:56": "BNB"
2928
- };
2929
- function solanaEndpoint(chain, rpcUrls) {
2930
- if (rpcUrls?.[chain]) return rpcUrls[chain];
2931
- const cluster = chain.slice("solana:".length);
2932
- return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
2933
- }
2934
- async function fetchBalance(opts) {
2935
- const { kind, address, chain, rpcUrls } = opts;
2936
- if (kind === "evm") {
2937
- const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
2938
- if (!rpcUrl) {
2939
- throw new Error(
2940
- `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
2941
- );
2942
- }
2943
- const { JsonRpcProvider, formatEther } = await import("ethers");
2944
- const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
2945
- return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
2946
- }
2947
- const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
2948
- const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
2949
- new PublicKey(address)
2950
- );
2951
- return {
2952
- raw: BigInt(lamports),
2953
- formatted: String(lamports / LAMPORTS_PER_SOL),
2954
- symbol: "SOL",
2955
- decimals: 9
2956
- };
2957
- }
2958
- function formatBalanceDisplay(value) {
2959
- if (value == null) return "\u2014";
2960
- const n = Number(value);
2961
- if (!Number.isFinite(n)) return value;
2962
- return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
2963
- }
2964
-
2965
3152
  // src/social/client.ts
2966
3153
  var DEFAULT_API_BASE = "https://backend.decane.app";
2967
3154
  var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
@@ -2989,6 +3176,29 @@ function clearPersisted(key) {
2989
3176
  } catch {
2990
3177
  }
2991
3178
  }
3179
+ function sessionPersistKey(apiKey) {
3180
+ return `decane:sess:${apiKey.slice(0, 20)}`;
3181
+ }
3182
+ function loadSession(key) {
3183
+ try {
3184
+ const raw = typeof sessionStorage !== "undefined" ? sessionStorage.getItem(key) : null;
3185
+ return raw ? JSON.parse(raw) : null;
3186
+ } catch {
3187
+ return null;
3188
+ }
3189
+ }
3190
+ function saveSession(key, s) {
3191
+ try {
3192
+ sessionStorage.setItem(key, JSON.stringify(s));
3193
+ } catch {
3194
+ }
3195
+ }
3196
+ function clearSessionPersist(key) {
3197
+ try {
3198
+ sessionStorage.removeItem(key);
3199
+ } catch {
3200
+ }
3201
+ }
2992
3202
  async function createDecaneConnect(config) {
2993
3203
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
2994
3204
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
@@ -3011,6 +3221,40 @@ async function createDecaneConnect(config) {
3011
3221
  );
3012
3222
  let auth = null;
3013
3223
  let persisted = loadPersisted(PERSIST_KEY);
3224
+ let ethProvider = null;
3225
+ const persistSession = config.persistSession !== false;
3226
+ const SESSION_KEY = sessionPersistKey(config.apiKey);
3227
+ function persistLiveSession() {
3228
+ if (!persistSession) return;
3229
+ const handle = session.getHandle();
3230
+ const expiresAt = session.expiresAtTimestamp();
3231
+ if (!handle || !auth || !expiresAt) return;
3232
+ saveSession(SESSION_KEY, {
3233
+ handle,
3234
+ jwt: auth.jwt,
3235
+ userId: auth.userId,
3236
+ addresses: auth.addresses,
3237
+ expiresAt
3238
+ });
3239
+ }
3240
+ function dropLiveSession() {
3241
+ clearSessionPersist(SESSION_KEY);
3242
+ }
3243
+ if (persistSession) {
3244
+ const saved = loadSession(SESSION_KEY);
3245
+ if (saved?.handle && saved.expiresAt > Date.now() + 5e3) {
3246
+ try {
3247
+ session.resume(saved.handle, saved.addresses, saved.expiresAt);
3248
+ auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
3249
+ persisted = { userId: saved.userId, addresses: saved.addresses };
3250
+ } catch {
3251
+ dropLiveSession();
3252
+ }
3253
+ } else if (saved) {
3254
+ dropLiveSession();
3255
+ }
3256
+ }
3257
+ events.on("session-expired", () => dropLiveSession());
3014
3258
  async function pinSetup() {
3015
3259
  if (!config.promptPin) throw new PromptPinNotConfiguredError();
3016
3260
  const pin = await config.promptPin();
@@ -3124,7 +3368,7 @@ async function createDecaneConnect(config) {
3124
3368
  let evmAddress;
3125
3369
  let solanaAddress;
3126
3370
  try {
3127
- evmAddress = new Wallet(toHex(master)).address;
3371
+ evmAddress = new Wallet(toHex2(master)).address;
3128
3372
  solanaAddress = Keypair.fromSeed(master).publicKey.toBase58();
3129
3373
  shareSet = await splitMaster(master, 1);
3130
3374
  } finally {
@@ -3294,6 +3538,7 @@ async function createDecaneConnect(config) {
3294
3538
  persisted = { userId, addresses };
3295
3539
  savePersisted(PERSIST_KEY, persisted);
3296
3540
  events.emit("connected");
3541
+ persistLiveSession();
3297
3542
  return { addresses, isNewUser: false };
3298
3543
  }
3299
3544
  async function handleAuthResult(result) {
@@ -3309,6 +3554,7 @@ async function createDecaneConnect(config) {
3309
3554
  persisted = { userId: result.userId, addresses };
3310
3555
  savePersisted(PERSIST_KEY, persisted);
3311
3556
  events.emit("connected");
3557
+ persistLiveSession();
3312
3558
  return { addresses, isNewUser: result.isNewUser };
3313
3559
  }
3314
3560
  const client = {
@@ -3483,6 +3729,21 @@ async function createDecaneConnect(config) {
3483
3729
  auth?.jwt
3484
3730
  );
3485
3731
  },
3732
+ async signAuthorization(req) {
3733
+ const tee = session.requireActive();
3734
+ return signEvmAuthorization(tee, req.chainId, req.address, req.nonce, auth?.jwt);
3735
+ },
3736
+ getEthereumProvider(opts) {
3737
+ if (!ethProvider) {
3738
+ const firstEvm = config.chains.find((c) => c.startsWith("evm:"));
3739
+ const defaultChainId = firstEvm ? parseInt(firstEvm.slice(4), 10) : 1;
3740
+ ethProvider = createEthereumProvider(client, {
3741
+ chainId: opts?.chainId ?? defaultChainId,
3742
+ rpcUrls: config.rpcUrls
3743
+ });
3744
+ }
3745
+ return ethProvider;
3746
+ },
3486
3747
  async signSolanaTransaction(serializedTx) {
3487
3748
  const tee = session.requireActive();
3488
3749
  let tx;
@@ -3503,6 +3764,7 @@ async function createDecaneConnect(config) {
3503
3764
  auth = null;
3504
3765
  persisted = null;
3505
3766
  clearPersisted(PERSIST_KEY);
3767
+ dropLiveSession();
3506
3768
  events.emit("disconnected");
3507
3769
  },
3508
3770
  getAddresses() {
@@ -3645,14 +3907,14 @@ function b64uToBuffer(b64u) {
3645
3907
  const b644 = b64u.replace(/-/g, "+").replace(/_/g, "/");
3646
3908
  return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
3647
3909
  }
3648
- function toHex(bytes) {
3910
+ function toHex2(bytes) {
3649
3911
  return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3650
3912
  }
3651
3913
  async function deriveAddressesFromShares(a, b) {
3652
3914
  const master = await combineShares(a, b);
3653
3915
  try {
3654
3916
  return {
3655
- evm: new Wallet(toHex(master)).address,
3917
+ evm: new Wallet(toHex2(master)).address,
3656
3918
  solana: Keypair.fromSeed(master).publicKey.toBase58()
3657
3919
  };
3658
3920
  } finally {
@@ -5546,7 +5808,15 @@ function DecaneKit({ config, children }) {
5546
5808
  sdk.on("wallet-unlocking", () => setSocialAuth((p) => ({ ...p, phase: "unlocking" })));
5547
5809
  sdk.on("connected", () => setSocialAuth((p) => ({ ...p, phase: null })));
5548
5810
  const saved = sdk.getAddresses();
5549
- if (saved) {
5811
+ if (sdk.isUnlocked()) {
5812
+ setSocialAuth((p) => ({
5813
+ ...p,
5814
+ addresses: saved,
5815
+ isUnlocked: true,
5816
+ needsReconnect: false,
5817
+ sessionExpiresAt: sdk.sessionExpiresAt()
5818
+ }));
5819
+ } else if (saved) {
5550
5820
  setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
5551
5821
  }
5552
5822
  sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
@@ -5677,6 +5947,19 @@ function DecaneKit({ config, children }) {
5677
5947
  if (!sdk) throw new Error("Social wallet not connected");
5678
5948
  return sdk.getBalance(chain);
5679
5949
  }, []);
5950
+ const socialGetEthereumProvider = useCallback3((opts) => {
5951
+ const sdk = socialRef.current;
5952
+ if (!sdk) throw new Error("Social wallet not connected");
5953
+ return sdk.getEthereumProvider(opts);
5954
+ }, []);
5955
+ const socialSignAuthorization = useCallback3(
5956
+ async (req) => {
5957
+ const sdk = socialRef.current;
5958
+ if (!sdk) throw new Error("Social wallet not connected");
5959
+ return sdk.signAuthorization(req);
5960
+ },
5961
+ []
5962
+ );
5680
5963
  const socialGetBalances = useCallback3(async () => {
5681
5964
  const sdk = socialRef.current;
5682
5965
  if (!sdk) throw new Error("Social wallet not connected");
@@ -5739,6 +6022,8 @@ function DecaneKit({ config, children }) {
5739
6022
  socialSendTransaction,
5740
6023
  socialGetBalance,
5741
6024
  socialGetBalances,
6025
+ socialGetEthereumProvider,
6026
+ socialSignAuthorization,
5742
6027
  socialSignSolanaTransaction,
5743
6028
  socialSignTypedData,
5744
6029
  socialRotateShares,
@@ -5811,7 +6096,7 @@ function useDecaneContext() {
5811
6096
  }
5812
6097
 
5813
6098
  // src/ui/hooks.ts
5814
- import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
6099
+ import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4, useMemo } from "react";
5815
6100
  function useSocialAuth() {
5816
6101
  const {
5817
6102
  socialAuth,
@@ -5961,6 +6246,8 @@ function useSocialWallet() {
5961
6246
  socialSignTypedData,
5962
6247
  socialGetBalance,
5963
6248
  socialGetBalances,
6249
+ socialGetEthereumProvider,
6250
+ socialSignAuthorization,
5964
6251
  socialRotateShares,
5965
6252
  socialExportPortableBackup,
5966
6253
  socialHasRecoveryShare,
@@ -6005,6 +6292,10 @@ function useSocialWallet() {
6005
6292
  (chain) => run(() => socialGetBalance(chain)),
6006
6293
  [socialGetBalance]
6007
6294
  );
6295
+ const signAuthorization = useCallback4(
6296
+ (req) => run(() => socialSignAuthorization(req)),
6297
+ [socialSignAuthorization]
6298
+ );
6008
6299
  const getBalances = useCallback4(
6009
6300
  () => run(() => socialGetBalances()),
6010
6301
  [socialGetBalances]
@@ -6078,6 +6369,10 @@ function useSocialWallet() {
6078
6369
  // Balances (native token; read-only, works while locked)
6079
6370
  getBalance,
6080
6371
  getBalances,
6372
+ // EVM interop
6373
+ signAuthorization,
6374
+ // An EIP-1193 provider for viem/wagmi/ethers (memoized).
6375
+ getEthereumProvider: socialGetEthereumProvider,
6081
6376
  // Recovery (2-of-3 Shamir share set)
6082
6377
  rotateShares,
6083
6378
  exportPortableBackup,
@@ -6135,6 +6430,18 @@ function useSocialBalance(chain) {
6135
6430
  }, [addrKey, chain]);
6136
6431
  return { balance, loading, error, refetch };
6137
6432
  }
6433
+ function useEthereumProvider(opts) {
6434
+ const { socialAuth, socialGetEthereumProvider } = useDecaneContext();
6435
+ const chainId = opts?.chainId;
6436
+ return useMemo(() => {
6437
+ if (!socialAuth.addresses?.evm) return null;
6438
+ try {
6439
+ return socialGetEthereumProvider(chainId ? { chainId } : void 0);
6440
+ } catch {
6441
+ return null;
6442
+ }
6443
+ }, [socialAuth.addresses?.evm, chainId]);
6444
+ }
6138
6445
  function toDecaneError(err) {
6139
6446
  if (err instanceof DecaneError) return err;
6140
6447
  const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
@@ -6391,9 +6698,11 @@ export {
6391
6698
  WrongPasswordError,
6392
6699
  createDecane,
6393
6700
  createDecaneConnect,
6701
+ createEthereumProvider,
6394
6702
  startEmailAuth,
6395
6703
  useChainSwitch,
6396
6704
  useDecane,
6705
+ useEthereumProvider,
6397
6706
  useSendTransaction,
6398
6707
  useSignMessage,
6399
6708
  useSignTypedData,