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