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 +370 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +369 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
2406
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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 (
|
|
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,
|