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.cjs CHANGED
@@ -183,6 +183,9 @@ __export(index_exports, {
183
183
  DecaneError: () => DecaneError,
184
184
  DecaneErrorCode: () => DecaneErrorCode,
185
185
  DecaneKit: () => DecaneKit,
186
+ DecaneLogo: () => DecaneLogo,
187
+ DecaneMark: () => DecaneMark,
188
+ DecaneWordmark: () => DecaneWordmark,
186
189
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
187
190
  InvalidApiKeyError: () => InvalidApiKeyError,
188
191
  InvalidPinError: () => InvalidPinError,
@@ -211,6 +214,7 @@ __export(index_exports, {
211
214
  useSignMessage: () => useSignMessage,
212
215
  useSignTypedData: () => useSignTypedData,
213
216
  useSocialAuth: () => useSocialAuth,
217
+ useSocialBalance: () => useSocialBalance,
214
218
  useSocialWallet: () => useSocialWallet,
215
219
  useWalletSelector: () => useWalletSelector,
216
220
  verifyEmailCode: () => verifyEmailCode
@@ -1983,7 +1987,11 @@ var EventEmitter = class {
1983
1987
  init_errors();
1984
1988
  var ERROR_MAP = {
1985
1989
  INVALID_API_KEY: InvalidApiKeyError,
1986
- ORIGIN_NOT_ALLOWED: OriginNotAllowedError
1990
+ ORIGIN_NOT_ALLOWED: OriginNotAllowedError,
1991
+ // The backend's authMiddleware code for an expired/revoked JWT. Surfacing it
1992
+ // as ReconnectRequiredError routes callers to the sign-in UI instead of a
1993
+ // generic failure message.
1994
+ UNAUTHORIZED: ReconnectRequiredError
1987
1995
  };
1988
1996
  var ApiClient = class {
1989
1997
  constructor(baseUrl, apiKey, appId) {
@@ -2269,11 +2277,22 @@ var TeeClient = class {
2269
2277
  this.attested = false;
2270
2278
  /** Mirrors what the signer confirmed for this session, not what we asked for. */
2271
2279
  this.assertionPerSignature = false;
2280
+ /** The signer's authoritative remaining lifetime (seconds), null until a session opens. */
2281
+ this.sessionExpiresInSec = null;
2272
2282
  }
2273
2283
  /** Whether this session actually got per-signature assertions. */
2274
2284
  requiresAssertionPerSignature() {
2275
2285
  return this.assertionPerSignature;
2276
2286
  }
2287
+ /**
2288
+ * The lifetime the signer granted this session, in seconds — which can be
2289
+ * shorter than the client's configured duration when the JWT expires first.
2290
+ * Null if the signer didn't report one (older build). Callers clamp their own
2291
+ * countdown to this so it never promises time the handle doesn't have.
2292
+ */
2293
+ sessionExpiresIn() {
2294
+ return this.sessionExpiresInSec;
2295
+ }
2277
2296
  async ensureAttested() {
2278
2297
  if (this.attested) return;
2279
2298
  const nonce = crypto.getRandomValues(new Uint8Array(16));
@@ -2302,6 +2321,7 @@ var TeeClient = class {
2302
2321
  const body = await res.json();
2303
2322
  this.handle = body.handle;
2304
2323
  this.assertionPerSignature = body.requireAssertionPerSignature === true;
2324
+ this.sessionExpiresInSec = typeof body.expiresIn === "number" ? body.expiresIn : null;
2305
2325
  return body.addresses ?? null;
2306
2326
  }
2307
2327
  /**
@@ -2344,6 +2364,7 @@ var TeeClient = class {
2344
2364
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
2345
2365
  body: JSON.stringify({ rpId: window.location.hostname })
2346
2366
  });
2367
+ if (res.status === 401) throw new ReconnectRequiredError();
2347
2368
  if (!res.ok) throw new ExportNotAuthorizedError();
2348
2369
  const { challengeId, options } = await res.json();
2349
2370
  let assertion;
@@ -2395,8 +2416,9 @@ var TeeClient = class {
2395
2416
  /**
2396
2417
  * `jwt` is only needed when this session requires per-signature assertions —
2397
2418
  * it authenticates the challenge request. Passed per call rather than cached
2398
- * on the instance deliberately: the backend's JWTs are shorter-lived than the
2399
- * TEE session TTL, so a stored copy would go stale mid-session.
2419
+ * on the instance deliberately: JWT lifetime is configured independently of
2420
+ * the TEE session TTL (they are aligned at 30min by default, but an operator
2421
+ * can shorten JWT_EXPIRY_SECONDS), so a stored copy could go stale mid-session.
2400
2422
  */
2401
2423
  async sign(req, jwt) {
2402
2424
  if (!this.handle) throw new Error("no session");
@@ -2486,12 +2508,14 @@ var SessionManager = class {
2486
2508
  }
2487
2509
  this.evmAddress = addresses.evm;
2488
2510
  this.solanaAddress = addresses.solana;
2489
- this.expiresAt = Date.now() + this.durationMs;
2511
+ const serverTtlMs = this.tee.sessionExpiresIn();
2512
+ const effectiveMs = serverTtlMs != null ? Math.min(this.durationMs, serverTtlMs * 1e3) : this.durationMs;
2513
+ this.expiresAt = Date.now() + effectiveMs;
2490
2514
  if (this.timeoutId) clearTimeout(this.timeoutId);
2491
2515
  this.timeoutId = setTimeout(() => {
2492
2516
  this.end();
2493
2517
  this.events.emit("session-expired");
2494
- }, this.durationMs);
2518
+ }, effectiveMs);
2495
2519
  this.events.emit("session-started");
2496
2520
  return addresses;
2497
2521
  }
@@ -2715,6 +2739,13 @@ async function signEvmTransaction(tee, chain, tx, jwt) {
2715
2739
  async function signEvmMessage(tee, chain, message, jwt) {
2716
2740
  return tee.sign({ chain, kind: "message", payload: message }, jwt);
2717
2741
  }
2742
+ async function signEvmTypedData(tee, chain, typedData, jwt) {
2743
+ const payload = JSON.stringify(
2744
+ typedData,
2745
+ (_k, v) => typeof v === "bigint" ? v.toString() : v
2746
+ );
2747
+ return tee.sign({ chain, kind: "typedData", payload }, jwt);
2748
+ }
2718
2749
 
2719
2750
  // src/social/signers/solana.ts
2720
2751
  var import_web3 = require("@solana/web3.js");
@@ -2953,10 +2984,7 @@ async function buildPortableBackup(deviceShare, recoveryShare, password, address
2953
2984
  };
2954
2985
  }
2955
2986
 
2956
- // src/social/client.ts
2957
- var DEFAULT_API_BASE = "https://backend.decane.app";
2958
- var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
2959
- var DEFAULT_SESSION_MINUTES = 30;
2987
+ // src/social/balance.ts
2960
2988
  var DEFAULT_RPCS = {
2961
2989
  "evm:1": "https://ethereum.publicnode.com",
2962
2990
  "evm:137": "https://polygon.publicnode.com",
@@ -2968,6 +2996,50 @@ var DEFAULT_RPCS = {
2968
2996
  "evm:84532": "https://base-sepolia.publicnode.com",
2969
2997
  "evm:31337": "http://localhost:8545"
2970
2998
  };
2999
+ var EVM_SYMBOLS = {
3000
+ "evm:137": "MATIC",
3001
+ "evm:56": "BNB"
3002
+ };
3003
+ function solanaEndpoint(chain, rpcUrls) {
3004
+ if (rpcUrls?.[chain]) return rpcUrls[chain];
3005
+ const cluster = chain.slice("solana:".length);
3006
+ return cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com";
3007
+ }
3008
+ async function fetchBalance(opts) {
3009
+ const { kind, address, chain, rpcUrls } = opts;
3010
+ if (kind === "evm") {
3011
+ const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
3012
+ if (!rpcUrl) {
3013
+ throw new Error(
3014
+ `No RPC URL for ${chain}. Pass rpcUrls: { '${chain}': 'https://...' } in social config.`
3015
+ );
3016
+ }
3017
+ const { JsonRpcProvider, formatEther } = await import("ethers");
3018
+ const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
3019
+ return { raw: wei, formatted: formatEther(wei), symbol: EVM_SYMBOLS[chain] ?? "ETH", decimals: 18 };
3020
+ }
3021
+ const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
3022
+ const lamports = await new Connection(solanaEndpoint(chain, rpcUrls)).getBalance(
3023
+ new PublicKey(address)
3024
+ );
3025
+ return {
3026
+ raw: BigInt(lamports),
3027
+ formatted: String(lamports / LAMPORTS_PER_SOL),
3028
+ symbol: "SOL",
3029
+ decimals: 9
3030
+ };
3031
+ }
3032
+ function formatBalanceDisplay(value) {
3033
+ if (value == null) return "\u2014";
3034
+ const n = Number(value);
3035
+ if (!Number.isFinite(n)) return value;
3036
+ return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
3037
+ }
3038
+
3039
+ // src/social/client.ts
3040
+ var DEFAULT_API_BASE = "https://backend.decane.app";
3041
+ var DEFAULT_TEE_URL = "https://4405742df08d19abc549a05ade2a7154d98bd87f-8443.dstack-pha-prod9.phala.network";
3042
+ var DEFAULT_SESSION_MINUTES = 30;
2971
3043
  function makePersistKey(apiKey) {
2972
3044
  return `decane:social:${apiKey.slice(0, 20)}`;
2973
3045
  }
@@ -3465,6 +3537,23 @@ async function createDecaneConnect(config) {
3465
3537
  const txResponse = await provider.broadcastTransaction(signedTx);
3466
3538
  return txResponse.hash;
3467
3539
  },
3540
+ async signTypedData(req) {
3541
+ const tee = session.requireActive();
3542
+ if (!req.chain.startsWith("evm:")) {
3543
+ throw new CallSequenceError("signTypedData is EVM-only (EIP-712).");
3544
+ }
3545
+ return signEvmTypedData(
3546
+ tee,
3547
+ req.chain,
3548
+ {
3549
+ domain: req.domain,
3550
+ types: req.types,
3551
+ message: req.message,
3552
+ primaryType: req.primaryType
3553
+ },
3554
+ auth?.jwt
3555
+ );
3556
+ },
3468
3557
  async signSolanaTransaction(serializedTx) {
3469
3558
  const tee = session.requireActive();
3470
3559
  let tx;
@@ -3490,6 +3579,42 @@ async function createDecaneConnect(config) {
3490
3579
  getAddresses() {
3491
3580
  return auth?.addresses ?? persisted?.addresses ?? null;
3492
3581
  },
3582
+ // Native-token balance. Read-only public chain data, so it works whenever an
3583
+ // address is known — including while the wallet is locked (no TEE session
3584
+ // needed). `chain` defaults to the first configured EVM chain, else the
3585
+ // first Solana chain.
3586
+ async getBalance(chain) {
3587
+ const addresses = auth?.addresses ?? persisted?.addresses;
3588
+ if (!addresses) throw new CallSequenceError("Not connected \u2014 no wallet address known");
3589
+ const resolvedChain = chain ?? config.chains.find((c) => c.startsWith("evm:")) ?? config.chains.find((c) => c.startsWith("solana:"));
3590
+ if (!resolvedChain) throw new CallSequenceError("No chain configured");
3591
+ const kind = resolvedChain.startsWith("solana:") ? "solana" : "evm";
3592
+ const address = kind === "evm" ? addresses.evm : addresses.solana;
3593
+ if (!address) throw new CallSequenceError(`This wallet has no ${kind} address`);
3594
+ return fetchBalance({ kind, address, chain: resolvedChain, rpcUrls: config.rpcUrls });
3595
+ },
3596
+ // Balances across every configured chain the wallet has an address for.
3597
+ // Per-chain failures resolve to null rather than rejecting the whole call,
3598
+ // so one dead RPC doesn't blank the entire list.
3599
+ async getBalances() {
3600
+ const addresses = auth?.addresses ?? persisted?.addresses;
3601
+ if (!addresses) throw new CallSequenceError("Not connected \u2014 no wallet address known");
3602
+ const targets = config.chains.filter((c) => {
3603
+ const kind = c.startsWith("solana:") ? "solana" : "evm";
3604
+ return kind === "evm" ? !!addresses.evm : !!addresses.solana;
3605
+ });
3606
+ return Promise.all(
3607
+ targets.map(async (chain) => {
3608
+ const kind = chain.startsWith("solana:") ? "solana" : "evm";
3609
+ const address = kind === "evm" ? addresses.evm : addresses.solana;
3610
+ try {
3611
+ return { chain, balance: await fetchBalance({ kind, address, chain, rpcUrls: config.rpcUrls }) };
3612
+ } catch {
3613
+ return { chain, balance: null };
3614
+ }
3615
+ })
3616
+ );
3617
+ },
3493
3618
  async rotateShares(opts) {
3494
3619
  if (!auth) throw new CallSequenceError("Not connected \u2014 call connect() first");
3495
3620
  const tee = session.requireActive();
@@ -3736,6 +3861,31 @@ var CSS_VARS = {
3736
3861
 
3737
3862
  // src/ui/icons.tsx
3738
3863
  var import_react = __toESM(require("react"), 1);
3864
+ 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";
3865
+ 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";
3866
+ function DecaneMark({ size = 24, muted = false }) {
3867
+ const color = muted ? "var(--dc-text-muted)" : "var(--dc-accent)";
3868
+ return /* @__PURE__ */ import_react.default.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, style: { flexShrink: 0, display: "block" } }, /* @__PURE__ */ import_react.default.createElement("polygon", { points: DECAGON_OUTER, fill: color, fillOpacity: muted ? 0.08 : 0.1, stroke: color, strokeWidth: "1.4", strokeLinejoin: "round" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: DECAGON_INNER, fill: color, fillOpacity: muted ? 0.55 : 0.95 }));
3869
+ }
3870
+ function DecaneWordmark({ size = 15, muted = false }) {
3871
+ return /* @__PURE__ */ import_react.default.createElement(
3872
+ "span",
3873
+ {
3874
+ style: {
3875
+ fontFamily: "var(--dc-font-sans, system-ui, sans-serif)",
3876
+ fontWeight: 800,
3877
+ fontSize: size,
3878
+ letterSpacing: "-0.03em",
3879
+ color: muted ? "var(--dc-text-muted)" : "var(--dc-text)",
3880
+ lineHeight: 1
3881
+ }
3882
+ },
3883
+ "decane"
3884
+ );
3885
+ }
3886
+ function DecaneLogo({ size = 20, muted = false }) {
3887
+ return /* @__PURE__ */ import_react.default.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: size * 0.34 } }, /* @__PURE__ */ import_react.default.createElement(DecaneMark, { size, muted }), /* @__PURE__ */ import_react.default.createElement(DecaneWordmark, { size: size * 0.78, muted }));
3888
+ }
3739
3889
  function WalletIcon({ src, name, size = 36 }) {
3740
3890
  if (src) {
3741
3891
  return /* @__PURE__ */ import_react.default.createElement(
@@ -4689,40 +4839,17 @@ function useBalance(kind, address, chain, rpcUrls) {
4689
4839
  if (!address || !chain) return;
4690
4840
  let cancelled = false;
4691
4841
  setState((s) => ({ ...s, loading: true }));
4692
- (async () => {
4693
- try {
4694
- if (kind === "evm") {
4695
- const rpcUrl = rpcUrls?.[chain] ?? DEFAULT_RPCS[chain];
4696
- if (!rpcUrl) {
4697
- if (!cancelled) setState({ loading: false, value: null, symbol: "ETH" });
4698
- return;
4699
- }
4700
- const { JsonRpcProvider, formatEther } = await import("ethers");
4701
- const wei = await new JsonRpcProvider(rpcUrl).getBalance(address);
4702
- if (!cancelled) setState({ loading: false, value: formatEther(wei), symbol: "ETH" });
4703
- } else {
4704
- const cluster = chain.slice("solana:".length);
4705
- const endpoint = rpcUrls?.[chain] ?? (cluster === "devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com");
4706
- const { Connection, PublicKey, LAMPORTS_PER_SOL } = await import("@solana/web3.js");
4707
- const lamports = await new Connection(endpoint).getBalance(new PublicKey(address));
4708
- if (!cancelled) setState({ loading: false, value: String(lamports / LAMPORTS_PER_SOL), symbol: "SOL" });
4709
- }
4710
- } catch {
4711
- if (!cancelled) setState({ loading: false, value: null, symbol: kind === "evm" ? "ETH" : "SOL" });
4712
- }
4713
- })();
4842
+ fetchBalance({ kind, address, chain, rpcUrls }).then((b) => {
4843
+ if (!cancelled) setState({ loading: false, value: b.formatted, symbol: b.symbol });
4844
+ }).catch(() => {
4845
+ if (!cancelled) setState({ loading: false, value: null, symbol: kind === "evm" ? "ETH" : "SOL" });
4846
+ });
4714
4847
  return () => {
4715
4848
  cancelled = true;
4716
4849
  };
4717
4850
  }, [kind, address, chain, rpcUrls]);
4718
4851
  return state;
4719
4852
  }
4720
- function formatBalance(value) {
4721
- if (value == null) return "\u2014";
4722
- const n = Number(value);
4723
- if (!Number.isFinite(n)) return value;
4724
- return n === 0 ? "0" : n < 1e-4 ? "<0.0001" : n.toFixed(4).replace(/\.?0+$/, "");
4725
- }
4726
4853
  function AddressRow({
4727
4854
  label,
4728
4855
  address,
@@ -4738,7 +4865,7 @@ function AddressRow({
4738
4865
  borderRadius: 10,
4739
4866
  padding: "10px 14px",
4740
4867
  marginBottom: 8
4741
- } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 5 } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "0.06em", textTransform: "uppercase" } }, label), /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 11, fontFamily: "monospace", color: "var(--dc-text)" } }, balance.loading ? "\u2026" : `${formatBalance(balance.value)} ${balance.symbol}`)), /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 13, fontFamily: "monospace", color: "var(--dc-text)", letterSpacing: "0.02em" } }, short), /* @__PURE__ */ import_react3.default.createElement(CopyButton, { value: address })));
4868
+ } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 5 } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 10, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "0.06em", textTransform: "uppercase" } }, label), /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 11, fontFamily: "monospace", color: "var(--dc-text)" } }, balance.loading ? "\u2026" : `${formatBalanceDisplay(balance.value)} ${balance.symbol}`)), /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 13, fontFamily: "monospace", color: "var(--dc-text)", letterSpacing: "0.02em" } }, short), /* @__PURE__ */ import_react3.default.createElement(CopyButton, { value: address })));
4742
4869
  }
4743
4870
  function SignInView({
4744
4871
  onGoogleSignIn,
@@ -5115,34 +5242,17 @@ function SocialWalletModal({
5115
5242
  top: 0,
5116
5243
  zIndex: 1,
5117
5244
  background: "linear-gradient(90deg, transparent, var(--dc-accent) 30%, var(--dc-accent) 70%, transparent)"
5118
- } }), /* @__PURE__ */ import_react3.default.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
5119
- width: 34,
5120
- height: 34,
5121
- borderRadius: 10,
5245
+ } }), /* @__PURE__ */ import_react3.default.createElement("div", { style: { padding: "18px 20px 16px", display: "flex", alignItems: "center", gap: 11 } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
5246
+ width: 38,
5247
+ height: 38,
5248
+ borderRadius: 11,
5122
5249
  background: "var(--dc-surface)",
5123
5250
  border: "1px solid var(--dc-border)",
5124
5251
  display: "flex",
5125
5252
  alignItems: "center",
5126
5253
  justifyContent: "center",
5127
5254
  flexShrink: 0
5128
- } }, /* @__PURE__ */ import_react3.default.createElement("svg", { width: "16", height: "18", viewBox: "0 0 16 18", fill: "none" }, /* @__PURE__ */ import_react3.default.createElement(
5129
- "path",
5130
- {
5131
- 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",
5132
- stroke: "var(--dc-accent)",
5133
- strokeWidth: "1.4",
5134
- strokeLinejoin: "round"
5135
- }
5136
- ), /* @__PURE__ */ import_react3.default.createElement(
5137
- "path",
5138
- {
5139
- d: "M5.5 9L7 10.5L10.5 7",
5140
- stroke: "var(--dc-accent)",
5141
- strokeWidth: "1.4",
5142
- strokeLinecap: "round",
5143
- strokeLinejoin: "round"
5144
- }
5145
- ))), /* @__PURE__ */ import_react3.default.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: { fontSize: 15, fontWeight: 800, color: "var(--dc-text)", letterSpacing: "-0.02em" } }, "Social Wallet"), /* @__PURE__ */ import_react3.default.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 1 } }, "Decane non-custodial \xB7 key split three ways")), /* @__PURE__ */ import_react3.default.createElement(
5255
+ } }, /* @__PURE__ */ import_react3.default.createElement(DecaneMark, { size: 22 })), /* @__PURE__ */ import_react3.default.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 6 } }, /* @__PURE__ */ import_react3.default.createElement(DecaneWordmark, { size: 15 }), /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 13, fontWeight: 700, color: "var(--dc-text-muted)", letterSpacing: "-0.01em" } }, "wallet")), /* @__PURE__ */ import_react3.default.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 2 } }, "Non-custodial \xB7 key split three ways")), /* @__PURE__ */ import_react3.default.createElement(
5146
5256
  "button",
5147
5257
  {
5148
5258
  onClick: onClose,
@@ -5245,7 +5355,14 @@ function SocialWalletModal({
5245
5355
  }
5246
5356
  },
5247
5357
  disconnecting ? "Signing out\u2026" : "Sign out"
5248
- ))))
5358
+ ))), /* @__PURE__ */ import_react3.default.createElement("div", { style: {
5359
+ padding: "11px 16px",
5360
+ borderTop: "1px solid var(--dc-border)",
5361
+ display: "flex",
5362
+ alignItems: "center",
5363
+ justifyContent: "center",
5364
+ gap: 6
5365
+ } }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { fontSize: 10.5, color: "var(--dc-text-muted)", letterSpacing: "0.02em" } }, "Secured by"), /* @__PURE__ */ import_react3.default.createElement(DecaneMark, { size: 13, muted: true }), /* @__PURE__ */ import_react3.default.createElement(DecaneWordmark, { size: 11, muted: true })))
5249
5366
  );
5250
5367
  return (0, import_react_dom2.createPortal)(modal, document.body);
5251
5368
  }
@@ -5431,6 +5548,21 @@ function DecaneKit({ config, children }) {
5431
5548
  if (!sdk) throw new Error("Social wallet not connected");
5432
5549
  return sdk.signSolanaTransaction(tx);
5433
5550
  }, []);
5551
+ const socialSignTypedData = (0, import_react4.useCallback)(async (req) => {
5552
+ const sdk = socialRef.current;
5553
+ if (!sdk) throw new Error("Social wallet not connected");
5554
+ return sdk.signTypedData({ ...req, chain: req.chain });
5555
+ }, []);
5556
+ const socialGetBalance = (0, import_react4.useCallback)(async (chain) => {
5557
+ const sdk = socialRef.current;
5558
+ if (!sdk) throw new Error("Social wallet not connected");
5559
+ return sdk.getBalance(chain);
5560
+ }, []);
5561
+ const socialGetBalances = (0, import_react4.useCallback)(async () => {
5562
+ const sdk = socialRef.current;
5563
+ if (!sdk) throw new Error("Social wallet not connected");
5564
+ return sdk.getBalances();
5565
+ }, []);
5434
5566
  const socialRotateShares = (0, import_react4.useCallback)(async (opts) => {
5435
5567
  const sdk = socialRef.current;
5436
5568
  if (!sdk) throw new Error("Social wallet not connected");
@@ -5486,7 +5618,10 @@ function DecaneKit({ config, children }) {
5486
5618
  socialDisconnect,
5487
5619
  socialSignMessage,
5488
5620
  socialSendTransaction,
5621
+ socialGetBalance,
5622
+ socialGetBalances,
5489
5623
  socialSignSolanaTransaction,
5624
+ socialSignTypedData,
5490
5625
  socialRotateShares,
5491
5626
  socialExportPortableBackup,
5492
5627
  socialHasRecoveryShare,
@@ -5684,6 +5819,9 @@ function useSocialWallet() {
5684
5819
  socialSignMessage,
5685
5820
  socialSendTransaction,
5686
5821
  socialSignSolanaTransaction,
5822
+ socialSignTypedData,
5823
+ socialGetBalance,
5824
+ socialGetBalances,
5687
5825
  socialRotateShares,
5688
5826
  socialExportPortableBackup,
5689
5827
  socialHasRecoveryShare,
@@ -5720,6 +5858,18 @@ function useSocialWallet() {
5720
5858
  (tx) => run(() => socialSignSolanaTransaction(tx)),
5721
5859
  [socialSignSolanaTransaction]
5722
5860
  );
5861
+ const signTypedData = (0, import_react5.useCallback)(
5862
+ (req) => run(() => socialSignTypedData(req)),
5863
+ [socialSignTypedData]
5864
+ );
5865
+ const getBalance = (0, import_react5.useCallback)(
5866
+ (chain) => run(() => socialGetBalance(chain)),
5867
+ [socialGetBalance]
5868
+ );
5869
+ const getBalances = (0, import_react5.useCallback)(
5870
+ () => run(() => socialGetBalances()),
5871
+ [socialGetBalances]
5872
+ );
5723
5873
  const unlock = (0, import_react5.useCallback)(async () => {
5724
5874
  setError(null);
5725
5875
  try {
@@ -5781,6 +5931,10 @@ function useSocialWallet() {
5781
5931
  signMessage,
5782
5932
  sendTransaction,
5783
5933
  signSolanaTransaction: signSolanaTransaction2,
5934
+ signTypedData,
5935
+ // Balances (native token; read-only, works while locked)
5936
+ getBalance,
5937
+ getBalances,
5784
5938
  // Recovery (2-of-3 Shamir share set)
5785
5939
  rotateShares,
5786
5940
  exportPortableBackup,
@@ -5792,6 +5946,52 @@ function useSocialWallet() {
5792
5946
  clearError: () => setError(null)
5793
5947
  };
5794
5948
  }
5949
+ function useSocialBalance(chain) {
5950
+ const { socialAuth, socialGetBalance } = useDecaneContext();
5951
+ const [balance, setBalance] = (0, import_react5.useState)(null);
5952
+ const [loading, setLoading] = (0, import_react5.useState)(false);
5953
+ const [error, setError] = (0, import_react5.useState)(null);
5954
+ const addrKey = `${socialAuth.addresses?.evm ?? ""}|${socialAuth.addresses?.solana ?? ""}`;
5955
+ const refetch = (0, import_react5.useCallback)(async () => {
5956
+ if (!socialAuth.addresses) {
5957
+ setBalance(null);
5958
+ return;
5959
+ }
5960
+ setLoading(true);
5961
+ setError(null);
5962
+ try {
5963
+ setBalance(await socialGetBalance(chain));
5964
+ } catch (e) {
5965
+ setError(e.message);
5966
+ setBalance(null);
5967
+ } finally {
5968
+ setLoading(false);
5969
+ }
5970
+ }, [socialGetBalance, chain, socialAuth.addresses]);
5971
+ (0, import_react5.useEffect)(() => {
5972
+ let cancelled = false;
5973
+ if (!socialAuth.addresses) {
5974
+ setBalance(null);
5975
+ return;
5976
+ }
5977
+ setLoading(true);
5978
+ setError(null);
5979
+ socialGetBalance(chain).then((b) => {
5980
+ if (!cancelled) setBalance(b);
5981
+ }).catch((e) => {
5982
+ if (!cancelled) {
5983
+ setError(e.message);
5984
+ setBalance(null);
5985
+ }
5986
+ }).finally(() => {
5987
+ if (!cancelled) setLoading(false);
5988
+ });
5989
+ return () => {
5990
+ cancelled = true;
5991
+ };
5992
+ }, [addrKey, chain]);
5993
+ return { balance, loading, error, refetch };
5994
+ }
5795
5995
  function toDecaneError(err) {
5796
5996
  if (err instanceof DecaneError) return err;
5797
5997
  const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
@@ -6025,6 +6225,9 @@ function useSignTypedData() {
6025
6225
  DecaneError,
6026
6226
  DecaneErrorCode,
6027
6227
  DecaneKit,
6228
+ DecaneLogo,
6229
+ DecaneMark,
6230
+ DecaneWordmark,
6028
6231
  ExportNotAuthorizedError,
6029
6232
  InvalidApiKeyError,
6030
6233
  InvalidPinError,
@@ -6053,6 +6256,7 @@ function useSignTypedData() {
6053
6256
  useSignMessage,
6054
6257
  useSignTypedData,
6055
6258
  useSocialAuth,
6259
+ useSocialBalance,
6056
6260
  useSocialWallet,
6057
6261
  useWalletSelector,
6058
6262
  verifyEmailCode