decane-connect-kit 2.2.1 → 2.2.3

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
@@ -1916,7 +1916,11 @@ var EventEmitter = class {
1916
1916
  init_errors();
1917
1917
  var ERROR_MAP = {
1918
1918
  INVALID_API_KEY: InvalidApiKeyError,
1919
- ORIGIN_NOT_ALLOWED: OriginNotAllowedError
1919
+ ORIGIN_NOT_ALLOWED: OriginNotAllowedError,
1920
+ // The backend's authMiddleware code for an expired/revoked JWT. Surfacing it
1921
+ // as ReconnectRequiredError routes callers to the sign-in UI instead of a
1922
+ // generic failure message.
1923
+ UNAUTHORIZED: ReconnectRequiredError
1920
1924
  };
1921
1925
  var ApiClient = class {
1922
1926
  constructor(baseUrl, apiKey, appId) {
@@ -2202,11 +2206,22 @@ var TeeClient = class {
2202
2206
  this.attested = false;
2203
2207
  /** Mirrors what the signer confirmed for this session, not what we asked for. */
2204
2208
  this.assertionPerSignature = false;
2209
+ /** The signer's authoritative remaining lifetime (seconds), null until a session opens. */
2210
+ this.sessionExpiresInSec = null;
2205
2211
  }
2206
2212
  /** Whether this session actually got per-signature assertions. */
2207
2213
  requiresAssertionPerSignature() {
2208
2214
  return this.assertionPerSignature;
2209
2215
  }
2216
+ /**
2217
+ * The lifetime the signer granted this session, in seconds — which can be
2218
+ * shorter than the client's configured duration when the JWT expires first.
2219
+ * Null if the signer didn't report one (older build). Callers clamp their own
2220
+ * countdown to this so it never promises time the handle doesn't have.
2221
+ */
2222
+ sessionExpiresIn() {
2223
+ return this.sessionExpiresInSec;
2224
+ }
2210
2225
  async ensureAttested() {
2211
2226
  if (this.attested) return;
2212
2227
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2235,6 +2250,7 @@ var TeeClient = class {
2235
2250
  const body = await res.json();
2236
2251
  this.handle = body.handle;
2237
2252
  this.assertionPerSignature = body.requireAssertionPerSignature === true;
2253
+ this.sessionExpiresInSec = typeof body.expiresIn === "number" ? body.expiresIn : null;
2238
2254
  return body.addresses ?? null;
2239
2255
  }
2240
2256
  /**
@@ -2277,6 +2293,7 @@ var TeeClient = class {
2277
2293
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2278
2294
  body: JSON.stringify({ rpId: window.location.hostname })
2279
2295
  });
2296
+ if (res.status === 401) throw new ReconnectRequiredError();
2280
2297
  if (!res.ok) throw new ExportNotAuthorizedError();
2281
2298
  const { challengeId, options } = await res.json();
2282
2299
  let assertion;
@@ -2328,8 +2345,9 @@ var TeeClient = class {
2328
2345
  /**
2329
2346
  * `jwt` is only needed when this session requires per-signature assertions —
2330
2347
  * it authenticates the challenge request. Passed per call rather than cached
2331
- * on the instance deliberately: the backend's JWTs are shorter-lived than the
2332
- * TEE session TTL, so a stored copy would go stale mid-session.
2348
+ * on the instance deliberately: JWT lifetime is configured independently of
2349
+ * the TEE session TTL (they are aligned at 30min by default, but an operator
2350
+ * can shorten JWT_EXPIRY_SECONDS), so a stored copy could go stale mid-session.
2333
2351
  */
2334
2352
  async sign(req, jwt) {
2335
2353
  if (!this.handle) throw new Error("no session");
@@ -2419,12 +2437,14 @@ var SessionManager = class {
2419
2437
  }
2420
2438
  this.evmAddress = addresses.evm;
2421
2439
  this.solanaAddress = addresses.solana;
2422
- this.expiresAt = Date.now() + this.durationMs;
2440
+ const serverTtlMs = this.tee.sessionExpiresIn();
2441
+ const effectiveMs = serverTtlMs != null ? Math.min(this.durationMs, serverTtlMs * 1e3) : this.durationMs;
2442
+ this.expiresAt = Date.now() + effectiveMs;
2423
2443
  if (this.timeoutId) clearTimeout(this.timeoutId);
2424
2444
  this.timeoutId = setTimeout(() => {
2425
2445
  this.end();
2426
2446
  this.events.emit("session-expired");
2427
- }, this.durationMs);
2447
+ }, effectiveMs);
2428
2448
  this.events.emit("session-started");
2429
2449
  return addresses;
2430
2450
  }
@@ -2648,6 +2668,13 @@ async function signEvmTransaction(tee, chain, tx, jwt) {
2648
2668
  async function signEvmMessage(tee, chain, message, jwt) {
2649
2669
  return tee.sign({ chain, kind: "message", payload: message }, jwt);
2650
2670
  }
2671
+ async function signEvmTypedData(tee, chain, typedData, jwt) {
2672
+ const payload = JSON.stringify(
2673
+ typedData,
2674
+ (_k, v) => typeof v === "bigint" ? v.toString() : v
2675
+ );
2676
+ return tee.sign({ chain, kind: "typedData", payload }, jwt);
2677
+ }
2651
2678
 
2652
2679
  // src/social/signers/solana.ts
2653
2680
  import { VersionedTransaction as VTx } from "@solana/web3.js";
@@ -2886,10 +2913,7 @@ async function buildPortableBackup(deviceShare, recoveryShare, password, address
2886
2913
  };
2887
2914
  }
2888
2915
 
2889
- // src/social/client.ts
2890
- var DEFAULT_API_BASE = "https://backend.decane.app";
2891
- var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
2892
- var DEFAULT_SESSION_MINUTES = 30;
2916
+ // src/social/balance.ts
2893
2917
  var DEFAULT_RPCS = {
2894
2918
  "evm:1": "https://ethereum.publicnode.com",
2895
2919
  "evm:137": "https://polygon.publicnode.com",
@@ -2901,6 +2925,50 @@ var DEFAULT_RPCS = {
2901
2925
  "evm:84532": "https://base-sepolia.publicnode.com",
2902
2926
  "evm:31337": "http://localhost:8545"
2903
2927
  };
2928
+ var EVM_SYMBOLS = {
2929
+ "evm:137": "MATIC",
2930
+ "evm:56": "BNB"
2931
+ };
2932
+ function solanaEndpoint(chain, rpcUrls) {
2933
+ if (rpcUrls?.[chain]) return rpcUrls[chain];
2934
+ const cluster = chain.slice("solana:".length);
2935
+ return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
2936
+ }
2937
+ async function fetchBalance(opts) {
2938
+ const { kind, address, chain, rpcUrls } = opts;
2939
+ if (kind === "evm") {
2940
+ const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
2941
+ if (!rpcUrl) {
2942
+ throw new Error(
2943
+ `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
2944
+ );
2945
+ }
2946
+ const { JsonRpcProvider, formatEther } = await import("ethers");
2947
+ const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
2948
+ return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
2949
+ }
2950
+ const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
2951
+ const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
2952
+ new PublicKey(address)
2953
+ );
2954
+ return {
2955
+ raw: BigInt(lamports),
2956
+ formatted: String(lamports / LAMPORTS_PER_SOL),
2957
+ symbol: "SOL",
2958
+ decimals: 9
2959
+ };
2960
+ }
2961
+ function formatBalanceDisplay(value) {
2962
+ if (value == null) return "\u2014";
2963
+ const n = Number(value);
2964
+ if (!Number.isFinite(n)) return value;
2965
+ return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
2966
+ }
2967
+
2968
+ // src/social/client.ts
2969
+ var DEFAULT_API_BASE = "https://backend.decane.app";
2970
+ var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
2971
+ var DEFAULT_SESSION_MINUTES = 30;
2904
2972
  function makePersistKey(apiKey) {
2905
2973
  return `decane:social:${apiKey.slice(0, 20)}`;
2906
2974
  }
@@ -3398,6 +3466,23 @@ async function createDecaneConnect(config) {
3398
3466
  const txResponse = await provider.broadcastTransaction(signedTx);
3399
3467
  return txResponse.hash;
3400
3468
  },
3469
+ async signTypedData(req) {
3470
+ const tee = session.requireActive();
3471
+ if (!req.chain.startsWith("evm:")) {
3472
+ throw new CallSequenceError("signTypedData is EVM-only (EIP-712).");
3473
+ }
3474
+ return signEvmTypedData(
3475
+ tee,
3476
+ req.chain,
3477
+ {
3478
+ domain: req.domain,
3479
+ types: req.types,
3480
+ message: req.message,
3481
+ primaryType: req.primaryType
3482
+ },
3483
+ auth?.jwt
3484
+ );
3485
+ },
3401
3486
  async signSolanaTransaction(serializedTx) {
3402
3487
  const tee = session.requireActive();
3403
3488
  let tx;
@@ -3423,6 +3508,42 @@ async function createDecaneConnect(config) {
3423
3508
  getAddresses() {
3424
3509
  return auth?.addresses ?? persisted?.addresses ?? null;
3425
3510
  },
3511
+ // Native-token balance. Read-only public chain data, so it works whenever an
3512
+ // address is known — including while the wallet is locked (no TEE session
3513
+ // needed). `chain` defaults to the first configured EVM chain, else the
3514
+ // first Solana chain.
3515
+ async getBalance(chain) {
3516
+ const addresses = auth?.addresses ?? persisted?.addresses;
3517
+ if (!addresses) throw new CallSequenceError("Not connected \u2014 no wallet address known");
3518
+ const resolvedChain = chain ?? config.chains.find((c) => c.startsWith("evm:")) ?? config.chains.find((c) => c.startsWith("solana:"));
3519
+ if (!resolvedChain) throw new CallSequenceError("No chain configured");
3520
+ const kind = resolvedChain.startsWith("solana:") ? "solana" : "evm";
3521
+ const address = kind === "evm" ? addresses.evm : addresses.solana;
3522
+ if (!address) throw new CallSequenceError(`This wallet has no ${kind} address`);
3523
+ return fetchBalance({ kind, address, chain: resolvedChain, rpcUrls: config.rpcUrls });
3524
+ },
3525
+ // Balances across every configured chain the wallet has an address for.
3526
+ // Per-chain failures resolve to null rather than rejecting the whole call,
3527
+ // so one dead RPC doesn't blank the entire list.
3528
+ async getBalances() {
3529
+ const addresses = auth?.addresses ?? persisted?.addresses;
3530
+ if (!addresses) throw new CallSequenceError("Not connected \u2014 no wallet address known");
3531
+ const targets = config.chains.filter((c) => {
3532
+ const kind = c.startsWith("solana:") ? "solana" : "evm";
3533
+ return kind === "evm" ? !!addresses.evm : !!addresses.solana;
3534
+ });
3535
+ return Promise.all(
3536
+ targets.map(async (chain) => {
3537
+ const kind = chain.startsWith("solana:") ? "solana" : "evm";
3538
+ const address = kind === "evm" ? addresses.evm : addresses.solana;
3539
+ try {
3540
+ return { chain, balance: await fetchBalance({ kind, address, chain, rpcUrls: config.rpcUrls }) };
3541
+ } catch {
3542
+ return { chain, balance: null };
3543
+ }
3544
+ })
3545
+ );
3546
+ },
3426
3547
  async rotateShares(opts) {
3427
3548
  if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
3428
3549
  const tee = session.requireActive();
@@ -3669,6 +3790,31 @@ var CSS_VARS = {
3669
3790
 
3670
3791
  // src/ui/icons.tsx
3671
3792
  import React from "react";
3793
+ var DECAGON_OUTER = "12,2 17.88,3.91 21.51,8.91 21.51,15.09 17.88,20.09 12,22 6.12,20.09 2.49,15.09 2.49,8.91 6.12,3.91";
3794
+ var DECAGON_INNER = "12,7.4 14.7,8.28 16.37,10.58 16.37,13.42 14.7,15.72 12,16.6 9.3,15.72 7.63,13.42 7.63,10.58 9.3,8.28";
3795
+ function DecaneMark({ size = 24, muted = false }) {
3796
+ const color = muted ? "var(--dc-text-muted)" : "var(--dc-accent)";
3797
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, style: { flexShrink: 0, display: "block" } }, /* @__PURE__ */ React.createElement("polygon", { points: DECAGON_OUTER, fill: color, fillOpacity: muted ? 0.08 : 0.1, stroke: color, strokeWidth: "1.4", strokeLinejoin: "round" }), /* @__PURE__ */ React.createElement("polygon", { points: DECAGON_INNER, fill: color, fillOpacity: muted ? 0.55 : 0.95 }));
3798
+ }
3799
+ function DecaneWordmark({ size = 15, muted = false }) {
3800
+ return /* @__PURE__ */ React.createElement(
3801
+ "span",
3802
+ {
3803
+ style: {
3804
+ fontFamily: "var(--dc-font-sans, system-ui, sans-serif)",
3805
+ fontWeight: 800,
3806
+ fontSize: size,
3807
+ letterSpacing: "-0.03em",
3808
+ color: muted ? "var(--dc-text-muted)" : "var(--dc-text)",
3809
+ lineHeight: 1
3810
+ }
3811
+ },
3812
+ "decane"
3813
+ );
3814
+ }
3815
+ function DecaneLogo({ size = 20, muted = false }) {
3816
+ return /* @__PURE__ */ React.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: size * 0.34 } }, /* @__PURE__ */ React.createElement(DecaneMark, { size, muted }), /* @__PURE__ */ React.createElement(DecaneWordmark, { size: size * 0.78, muted }));
3817
+ }
3672
3818
  function WalletIcon({ src, name, size = 36 }) {
3673
3819
  if (src) {
3674
3820
  return /* @__PURE__ */ React.createElement(
@@ -4622,40 +4768,17 @@ function useBalance(kind, address, chain, rpcUrls) {
4622
4768
  if (!address || !chain) return;
4623
4769
  let cancelled = false;
4624
4770
  setState((s) => ({ ...s, loading: true }));
4625
- (async () => {
4626
- try {
4627
- if (kind === "evm") {
4628
- const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
4629
- if (!rpcUrl) {
4630
- if (!cancelled) setState({ loading: false, value: null, symbol: "ETH" });
4631
- return;
4632
- }
4633
- const { JsonRpcProvider, formatEther } = await import("ethers");
4634
- const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
4635
- if (!cancelled) setState({ loading: false, value: formatEther(wei), symbol: "ETH" });
4636
- } else {
4637
- const cluster = chain.slice("solana:".length);
4638
- const endpoint = rpcUrls?.[chain] ?? (cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com");
4639
- const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
4640
- const lamports = await new Connection(endpoint).getBalance(new PublicKey(address));
4641
- if (!cancelled) setState({ loading: false, value: String(lamports / LAMPORTS_PER_SOL), symbol: "SOL" });
4642
- }
4643
- } catch {
4644
- if (!cancelled) setState({ loading: false, value: null, symbol: kind === "evm" ? "ETH" : "SOL" });
4645
- }
4646
- })();
4771
+ fetchBalance({ kind, address, chain, rpcUrls }).then((b) => {
4772
+ if (!cancelled) setState({ loading: false, value: b.formatted, symbol: b.symbol });
4773
+ }).catch(() => {
4774
+ if (!cancelled) setState({ loading: false, value: null, symbol: kind === "evm" ? "ETH" : "SOL" });
4775
+ });
4647
4776
  return () => {
4648
4777
  cancelled = true;
4649
4778
  };
4650
4779
  }, [kind, address, chain, rpcUrls]);
4651
4780
  return state;
4652
4781
  }
4653
- function formatBalance(value) {
4654
- if (value == null) return "\u2014";
4655
- const n = Number(value);
4656
- if (!Number.isFinite(n)) return value;
4657
- return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
4658
- }
4659
4782
  function AddressRow({
4660
4783
  label,
4661
4784
  address,
@@ -4671,7 +4794,7 @@ function AddressRow({
4671
4794
  borderRadius: 10,
4672
4795
  padding: "10px 14px",
4673
4796
  marginBottom: 8
4674
- } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 5 } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "0.06em", textTransform: "uppercase" } }, label), /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 11, fontFamily: "monospace", color: "var(--dc-text)" } }, balance.loading ? "\u2026" : `${formatBalance(balance.value)} ${balance.symbol}`)), /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 13, fontFamily: "monospace", color: "var(--dc-text)", letterSpacing: "0.02em" } }, short), /* @__PURE__ */ React3.createElement(CopyButton, { value: address })));
4797
+ } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 5 } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "0.06em", textTransform: "uppercase" } }, label), /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 11, fontFamily: "monospace", color: "var(--dc-text)" } }, balance.loading ? "\u2026" : `${formatBalanceDisplay(balance.value)} ${balance.symbol}`)), /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 13, fontFamily: "monospace", color: "var(--dc-text)", letterSpacing: "0.02em" } }, short), /* @__PURE__ */ React3.createElement(CopyButton, { value: address })));
4675
4798
  }
4676
4799
  function SignInView({
4677
4800
  onGoogleSignIn,
@@ -5048,34 +5171,17 @@ function SocialWalletModal({
5048
5171
  top: 0,
5049
5172
  zIndex: 1,
5050
5173
  background: "linear-gradient(90deg, transparent, var(--dc-accent) 30%, var(--dc-accent) 70%, transparent)"
5051
- } }), /* @__PURE__ */ React3.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React3.createElement("div", { style: {
5052
- width: 34,
5053
- height: 34,
5054
- borderRadius: 10,
5174
+ } }), /* @__PURE__ */ React3.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 11 } }, /* @__PURE__ */ React3.createElement("div", { style: {
5175
+ width: 38,
5176
+ height: 38,
5177
+ borderRadius: 11,
5055
5178
  background: "var(--dc-surface)",
5056
5179
  border: "1px solid var(--dc-border)",
5057
5180
  display: "flex",
5058
5181
  alignItems: "center",
5059
5182
  justifyContent: "center",
5060
5183
  flexShrink: 0
5061
- } }, /* @__PURE__ */ React3.createElement("svg", { width: "16", height: "18", viewBox: "0 0 16 18", fill: "none" }, /* @__PURE__ */ React3.createElement(
5062
- "path",
5063
- {
5064
- d: "M8 1L1.5 3.5V8.5C1.5 12.09 4.35 15.4 8 16.5C11.65 15.4 14.5 12.09 14.5 8.5V3.5L8 1Z",
5065
- stroke: "var(--dc-accent)",
5066
- strokeWidth: "1.4",
5067
- strokeLinejoin: "round"
5068
- }
5069
- ), /* @__PURE__ */ React3.createElement(
5070
- "path",
5071
- {
5072
- d: "M5.5 9L7 10.5L10.5 7",
5073
- stroke: "var(--dc-accent)",
5074
- strokeWidth: "1.4",
5075
- strokeLinecap: "round",
5076
- strokeLinejoin: "round"
5077
- }
5078
- ))), /* @__PURE__ */ React3.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 15, fontWeight: 800, color: "var(--dc-text)", letterSpacing: "-0.02em" } }, "Social Wallet"), /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 1 } }, "Decane non-custodial \xB7 key split three ways")), /* @__PURE__ */ React3.createElement(
5184
+ } }, /* @__PURE__ */ React3.createElement(DecaneMark, { size: 22 })), /* @__PURE__ */ React3.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 6 } }, /* @__PURE__ */ React3.createElement(DecaneWordmark, { size: 15 }), /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 13, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "-0.01em" } }, "wallet")), /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 2 } }, "Non-custodial \xB7 key split three ways")), /* @__PURE__ */ React3.createElement(
5079
5185
  "button",
5080
5186
  {
5081
5187
  onClick: onClose,
@@ -5178,7 +5284,14 @@ function SocialWalletModal({
5178
5284
  }
5179
5285
  },
5180
5286
  disconnecting ? "Signing out\u2026" : "Sign out"
5181
- ))))
5287
+ ))), /* @__PURE__ */ React3.createElement("div", { style: {
5288
+ padding: "11px 16px",
5289
+ borderTop: "1px solid var(--dc-border)",
5290
+ display: "flex",
5291
+ alignItems: "center",
5292
+ justifyContent: "center",
5293
+ gap: 6
5294
+ } }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: 10.5, color: "var(--dc-text-muted)", letterSpacing: "0.02em" } }, "Secured by"), /* @__PURE__ */ React3.createElement(DecaneMark, { size: 13, muted: true }), /* @__PURE__ */ React3.createElement(DecaneWordmark, { size: 11, muted: true })))
5182
5295
  );
5183
5296
  return createPortal2(modal, document.body);
5184
5297
  }
@@ -5371,6 +5484,21 @@ function DecaneKit({ config, children }) {
5371
5484
  if (!sdk) throw new Error("Social wallet not connected");
5372
5485
  return sdk.signSolanaTransaction(tx);
5373
5486
  }, []);
5487
+ const socialSignTypedData = useCallback3(async (req) => {
5488
+ const sdk = socialRef.current;
5489
+ if (!sdk) throw new Error("Social wallet not connected");
5490
+ return sdk.signTypedData({ ...req, chain: req.chain });
5491
+ }, []);
5492
+ const socialGetBalance = useCallback3(async (chain) => {
5493
+ const sdk = socialRef.current;
5494
+ if (!sdk) throw new Error("Social wallet not connected");
5495
+ return sdk.getBalance(chain);
5496
+ }, []);
5497
+ const socialGetBalances = useCallback3(async () => {
5498
+ const sdk = socialRef.current;
5499
+ if (!sdk) throw new Error("Social wallet not connected");
5500
+ return sdk.getBalances();
5501
+ }, []);
5374
5502
  const socialRotateShares = useCallback3(async (opts) => {
5375
5503
  const sdk = socialRef.current;
5376
5504
  if (!sdk) throw new Error("Social wallet not connected");
@@ -5426,7 +5554,10 @@ function DecaneKit({ config, children }) {
5426
5554
  socialDisconnect,
5427
5555
  socialSignMessage,
5428
5556
  socialSendTransaction,
5557
+ socialGetBalance,
5558
+ socialGetBalances,
5429
5559
  socialSignSolanaTransaction,
5560
+ socialSignTypedData,
5430
5561
  socialRotateShares,
5431
5562
  socialExportPortableBackup,
5432
5563
  socialHasRecoveryShare,
@@ -5624,6 +5755,9 @@ function useSocialWallet() {
5624
5755
  socialSignMessage,
5625
5756
  socialSendTransaction,
5626
5757
  socialSignSolanaTransaction,
5758
+ socialSignTypedData,
5759
+ socialGetBalance,
5760
+ socialGetBalances,
5627
5761
  socialRotateShares,
5628
5762
  socialExportPortableBackup,
5629
5763
  socialHasRecoveryShare,
@@ -5660,6 +5794,18 @@ function useSocialWallet() {
5660
5794
  (tx) => run(() => socialSignSolanaTransaction(tx)),
5661
5795
  [socialSignSolanaTransaction]
5662
5796
  );
5797
+ const signTypedData = useCallback4(
5798
+ (req) => run(() => socialSignTypedData(req)),
5799
+ [socialSignTypedData]
5800
+ );
5801
+ const getBalance = useCallback4(
5802
+ (chain) => run(() => socialGetBalance(chain)),
5803
+ [socialGetBalance]
5804
+ );
5805
+ const getBalances = useCallback4(
5806
+ () => run(() => socialGetBalances()),
5807
+ [socialGetBalances]
5808
+ );
5663
5809
  const unlock = useCallback4(async () => {
5664
5810
  setError(null);
5665
5811
  try {
@@ -5721,6 +5867,10 @@ function useSocialWallet() {
5721
5867
  signMessage,
5722
5868
  sendTransaction,
5723
5869
  signSolanaTransaction: signSolanaTransaction2,
5870
+ signTypedData,
5871
+ // Balances (native token; read-only, works while locked)
5872
+ getBalance,
5873
+ getBalances,
5724
5874
  // Recovery (2-of-3 Shamir share set)
5725
5875
  rotateShares,
5726
5876
  exportPortableBackup,
@@ -5732,6 +5882,52 @@ function useSocialWallet() {
5732
5882
  clearError: () => setError(null)
5733
5883
  };
5734
5884
  }
5885
+ function useSocialBalance(chain) {
5886
+ const { socialAuth, socialGetBalance } = useDecaneContext();
5887
+ const [balance, setBalance] = useState4(null);
5888
+ const [loading, setLoading] = useState4(false);
5889
+ const [error, setError] = useState4(null);
5890
+ const addrKey = `${socialAuth.addresses?.evm ?? ""}|${socialAuth.addresses?.solana ?? ""}`;
5891
+ const refetch = useCallback4(async () => {
5892
+ if (!socialAuth.addresses) {
5893
+ setBalance(null);
5894
+ return;
5895
+ }
5896
+ setLoading(true);
5897
+ setError(null);
5898
+ try {
5899
+ setBalance(await socialGetBalance(chain));
5900
+ } catch (e) {
5901
+ setError(e.message);
5902
+ setBalance(null);
5903
+ } finally {
5904
+ setLoading(false);
5905
+ }
5906
+ }, [socialGetBalance, chain, socialAuth.addresses]);
5907
+ useEffect4(() => {
5908
+ let cancelled = false;
5909
+ if (!socialAuth.addresses) {
5910
+ setBalance(null);
5911
+ return;
5912
+ }
5913
+ setLoading(true);
5914
+ setError(null);
5915
+ socialGetBalance(chain).then((b) => {
5916
+ if (!cancelled) setBalance(b);
5917
+ }).catch((e) => {
5918
+ if (!cancelled) {
5919
+ setError(e.message);
5920
+ setBalance(null);
5921
+ }
5922
+ }).finally(() => {
5923
+ if (!cancelled) setLoading(false);
5924
+ });
5925
+ return () => {
5926
+ cancelled = true;
5927
+ };
5928
+ }, [addrKey, chain]);
5929
+ return { balance, loading, error, refetch };
5930
+ }
5735
5931
  function toDecaneError(err) {
5736
5932
  if (err instanceof DecaneError) return err;
5737
5933
  const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
@@ -5964,6 +6160,9 @@ export {
5964
6160
  DecaneError,
5965
6161
  DecaneErrorCode,
5966
6162
  DecaneKit,
6163
+ DecaneLogo,
6164
+ DecaneMark,
6165
+ DecaneWordmark,
5967
6166
  ExportNotAuthorizedError,
5968
6167
  InvalidApiKeyError,
5969
6168
  InvalidPinError,
@@ -5992,6 +6191,7 @@ export {
5992
6191
  useSignMessage,
5993
6192
  useSignTypedData,
5994
6193
  useSocialAuth,
6194
+ useSocialBalance,
5995
6195
  useSocialWallet,
5996
6196
  useWalletSelector,
5997
6197
  verifyEmailCode