diff --git a/apps/demo_web/src/components/oko_provider/use_oko.ts b/apps/demo_web/src/components/oko_provider/use_oko.ts
index 6ffe24c3a..35e54980a 100644
--- a/apps/demo_web/src/components/oko_provider/use_oko.ts
+++ b/apps/demo_web/src/components/oko_provider/use_oko.ts
@@ -7,17 +7,19 @@ import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
export function useInitOko() {
const initOkoCosmos = useSDKState((state) => state.initOkoCosmos);
const initOkoEth = useSDKState((state) => state.initOkoEth);
- // const initOkoSol = useSDKState((state) => state.initOkoSol);
+ const initOkoSol = useSDKState((state) => state.initOkoSol);
const isInitialized = useSDKState(
- (state) => state.oko_cosmos !== null && state.oko_eth !== null,
- // state.oko_sol !== null,
+ (state) =>
+ state.oko_cosmos !== null &&
+ state.oko_eth !== null &&
+ state.oko_sol !== null,
);
useEffect(() => {
initOkoCosmos();
initOkoEth();
- // initOkoSol();
+ initOkoSol();
}, []);
return { isInitialized };
diff --git a/apps/demo_web/src/components/preview_panel/preview_panel.tsx b/apps/demo_web/src/components/preview_panel/preview_panel.tsx
index ba4207e74..f167a8678 100644
--- a/apps/demo_web/src/components/preview_panel/preview_panel.tsx
+++ b/apps/demo_web/src/components/preview_panel/preview_panel.tsx
@@ -12,18 +12,17 @@ import { CosmosOnchainSignWidget } from "@oko-wallet-demo-web/components/widgets
import { CosmosOffChainSignWidget } from "@oko-wallet-demo-web/components/widgets/cosmos_offchain_sign_widget/cosmos_offchain_sign_widget";
import { EthereumOnchainSignWidget } from "@oko-wallet-demo-web/components/widgets/ethereum_onchain_sign_widget/ethereum_onchain_sign_widget";
import { EthereumOffchainSignWidget } from "@oko-wallet-demo-web/components/widgets/ethereum_offchain_sign_widget/ethereum_offchain_sign_widget";
-// import { SolanaOffchainSignWidget } from "@oko-wallet-demo-web/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget";
-// TODO: refactor this @chemonoworld @Ryz0nd
-// import { SolanaOnchainSignWidget } from "@oko-wallet-demo-web/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget";
+import { SolanaOffchainSignWidget } from "@oko-wallet-demo-web/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget";
+import { SolanaOnchainSignWidget } from "@oko-wallet-demo-web/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget";
import { useUserInfoState } from "@oko-wallet-demo-web/state/user_info";
import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
export const PreviewPanel: FC = () => {
const isLazyInitialized = useSDKState(
- (st) => st.isCosmosLazyInitialized && st.isEthLazyInitialized,
- // TODO: refactor this @chemonoworld @Ryz0nd
- // &&
- // st.isSolLazyInitialized,
+ (st) =>
+ st.isCosmosLazyInitialized &&
+ st.isEthLazyInitialized &&
+ st.isSolLazyInitialized,
);
const isSignedIn = useUserInfoState((state) => state.isSignedIn);
@@ -51,9 +50,8 @@ export const PreviewPanel: FC = () => {
)}
{isSignedIn && (
- {/* TODO: refactor this @chemonoworld @Ryz0nd */}
- {/* */}
- {/* */}
+
+
)}
>
diff --git a/apps/demo_web/src/components/widgets/account_widget/account_info_widget.tsx b/apps/demo_web/src/components/widgets/account_widget/account_info_widget.tsx
index 02ba46e62..1b9074421 100644
--- a/apps/demo_web/src/components/widgets/account_widget/account_info_widget.tsx
+++ b/apps/demo_web/src/components/widgets/account_widget/account_info_widget.tsx
@@ -16,7 +16,8 @@ import type { LoginMethod } from "@oko-wallet-demo-web/types/login";
export type AccountInfoWidgetProps = {
type: LoginMethod;
email: string;
- publicKey: string;
+ publicKeySecp256k1: string;
+ publicKeyEd25519: string | null;
name: string | null;
onSignOut: () => void;
};
@@ -24,7 +25,8 @@ export type AccountInfoWidgetProps = {
export const AccountInfoWidget: FC = ({
type,
email,
- publicKey,
+ publicKeySecp256k1,
+ publicKeyEd25519,
name,
onSignOut,
}) => {
@@ -56,13 +58,36 @@ export const AccountInfoWidget: FC = ({
color="tertiary"
className={styles.label}
>
- Public Key
+ Public Key (secp256k1)
- {publicKey}
+ {publicKeySecp256k1}
+ {publicKeyEd25519 && (
+ <>
+
+
+
+ Public Key (ed25519)
+
+
+ {publicKeyEd25519}
+
+
+ >
+ )}
+
diff --git a/apps/demo_web/src/components/widgets/account_widget/account_widget.tsx b/apps/demo_web/src/components/widgets/account_widget/account_widget.tsx
index 8a3b6f87b..b59e42adf 100644
--- a/apps/demo_web/src/components/widgets/account_widget/account_widget.tsx
+++ b/apps/demo_web/src/components/widgets/account_widget/account_widget.tsx
@@ -28,7 +28,10 @@ export const AccountWidget: FC
= () => {
status: "ready",
});
const email = useUserInfoState((state) => state.email);
- const publicKey = useUserInfoState((state) => state.publicKey);
+ const publicKeySecp256k1 = useUserInfoState(
+ (state) => state.publicKeySecp256k1,
+ );
+ const publicKeyEd25519 = useUserInfoState((state) => state.publicKeyEd25519);
const name = useUserInfoState((state) => state.name);
const authType = useUserInfoState((state) => state.authType);
const isSignedIn = useUserInfoState((state) => state.isSignedIn);
@@ -118,7 +121,8 @@ export const AccountWidget: FC = () => {
diff --git a/apps/demo_web/src/components/widgets/address_widget/address_row.tsx b/apps/demo_web/src/components/widgets/address_widget/address_row.tsx
index 344f3bed1..d49bd7755 100644
--- a/apps/demo_web/src/components/widgets/address_widget/address_row.tsx
+++ b/apps/demo_web/src/components/widgets/address_widget/address_row.tsx
@@ -4,10 +4,18 @@ import { Tooltip } from "@oko-wallet/oko-common-ui/tooltip";
import styles from "./address_row.module.scss";
+const chainConfig: Record<
+ AddressRowProps["chain"],
+ { label: string; prefix: string }
+> = {
+ ethereum: { label: "Ethereum", prefix: "0x" },
+ cosmos: { label: "Cosmos Hub", prefix: "cosmos1" },
+ solana: { label: "Solana", prefix: "" },
+};
+
export const AddressRow: FC = ({ icon, chain, address }) => {
const isLoggedIn = !!address;
- const label = chain === "ethereum" ? "Ethereum" : "Cosmos Hub";
- const prefix = chain === "ethereum" ? "0x" : "cosmos1";
+ const { label, prefix } = chainConfig[chain];
const renderChainLabel = () => (
@@ -63,6 +71,6 @@ export const AddressRow: FC
= ({ icon, chain, address }) => {
export interface AddressRowProps {
icon: ReactElement;
- chain: "ethereum" | "cosmos";
+ chain: "ethereum" | "cosmos" | "solana";
address?: string;
}
diff --git a/apps/demo_web/src/components/widgets/address_widget/address_widget.tsx b/apps/demo_web/src/components/widgets/address_widget/address_widget.tsx
index 213f9e256..5f4414357 100644
--- a/apps/demo_web/src/components/widgets/address_widget/address_widget.tsx
+++ b/apps/demo_web/src/components/widgets/address_widget/address_widget.tsx
@@ -1,6 +1,7 @@
import { useState, type FC } from "react";
import { CosmosIcon } from "@oko-wallet/oko-common-ui/icons/cosmos_icon";
import { EthereumBlueIcon } from "@oko-wallet/oko-common-ui/icons/ethereum_blue_icon";
+import { SolanaIcon } from "@oko-wallet/oko-common-ui/icons/solana_icon";
import { WalletIcon } from "@oko-wallet/oko-common-ui/icons/wallet";
import { Spacing } from "@oko-wallet/oko-common-ui/spacing";
import { Typography } from "@oko-wallet/oko-common-ui/typography";
@@ -15,7 +16,7 @@ import { useGetChainInfos } from "@oko-wallet-demo-web/hooks/use_get_chain_infos
export const AddressWidget: FC = ({}) => {
const [showModal, setShowModal] = useState(false);
- const { cosmosAddress, ethAddress } = useAddresses();
+ const { cosmosAddress, ethAddress, solanaAddress } = useAddresses();
const { data: chains } = useGetChainInfos();
@@ -57,6 +58,13 @@ export const AddressWidget: FC = ({}) => {
/>
+ }
+ chain="solana"
+ address={formatAddress(solanaAddress)}
+ />
+
+
diff --git a/apps/demo_web/src/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget.tsx b/apps/demo_web/src/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget.tsx
index 76e280e64..4373584e3 100644
--- a/apps/demo_web/src/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget.tsx
+++ b/apps/demo_web/src/components/widgets/solana_offchain_sign_widget/solana_offchain_sign_widget.tsx
@@ -1,38 +1,36 @@
-// TODO: refactor this widget @chemonoworld @Ryz0nd
-
-// import { SolanaIcon } from "@oko-wallet/oko-common-ui/icons/solana_icon";
-
-// import { SignWidget } from "@oko-wallet-demo-web/components/widgets/sign_widget/sign_widget";
-// import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
-
-// export const SolanaOffchainSignWidget = () => {
-// const okoSol = useSDKState((state) => state.oko_sol);
-
-// const handleClickSolOffchainSign = async () => {
-// if (okoSol === null) {
-// throw new Error("okoSol is not initialized");
-// }
-
-// // Connect if not already connected
-// if (!okoSol.connected) {
-// await okoSol.connect();
-// }
-
-// const message = "Welcome to Oko! Try generating an Ed25519 MPC signature.";
-// const messageBytes = new TextEncoder().encode(message);
-
-// const signature = await okoSol.signMessage(messageBytes);
-
-// // Log signature for demo purposes
-// console.log("Solana signature:", Buffer.from(signature).toString("hex"));
-// };
-
-// return (
-// }
-// signType="offchain"
-// signButtonOnClick={handleClickSolOffchainSign}
-// />
-// );
-// };
+import { SolanaIcon } from "@oko-wallet/oko-common-ui/icons/solana_icon";
+
+import { SignWidget } from "@oko-wallet-demo-web/components/widgets/sign_widget/sign_widget";
+import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
+
+export const SolanaOffchainSignWidget = () => {
+ const okoSol = useSDKState((state) => state.oko_sol);
+
+ const handleClickSolOffchainSign = async () => {
+ if (okoSol === null) {
+ throw new Error("okoSol is not initialized");
+ }
+
+ // Connect if not already connected
+ if (!okoSol.connected) {
+ await okoSol.connect();
+ }
+
+ const message = "Welcome to Oko! Try generating an Ed25519 MPC signature.";
+ const messageBytes = new TextEncoder().encode(message);
+
+ const signature = await okoSol.signMessage(messageBytes);
+
+ // Log signature for demo purposes
+ console.log("Solana signature:", Buffer.from(signature).toString("hex"));
+ };
+
+ return (
+ }
+ signType="offchain"
+ signButtonOnClick={handleClickSolOffchainSign}
+ />
+ );
+};
diff --git a/apps/demo_web/src/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget.tsx b/apps/demo_web/src/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget.tsx
index ac630f71a..5273de680 100644
--- a/apps/demo_web/src/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget.tsx
+++ b/apps/demo_web/src/components/widgets/solana_onchain_sign_widget/solana_onchain_sign_widget.tsx
@@ -1,130 +1,128 @@
-// TODO: refactor this widget @chemonoworld @Ryz0nd
-
-// import { useCallback, useState } from "react";
-// import {
-// Connection,
-// PublicKey,
-// SystemProgram,
-// Transaction,
-// TransactionMessage,
-// VersionedTransaction,
-// LAMPORTS_PER_SOL,
-// } from "@solana/web3.js";
-// import { SolanaIcon } from "@oko-wallet/oko-common-ui/icons/solana_icon";
-// import { Checkbox } from "@oko-wallet/oko-common-ui/checkbox";
-
-// import styles from "./solana_onchain_sign_widget.module.scss";
-// import { SignWidget } from "@oko-wallet-demo-web/components/widgets/sign_widget/sign_widget";
-// import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
-
-// const SOLANA_RPC_URL = "https://api.devnet.solana.com";
-
-// export const SolanaOnchainSignWidget = () => {
-// const okoSol = useSDKState((state) => state.oko_sol);
-// const [isLegacy, setIsLegacy] = useState(false);
-
-// const handleClickSolOnchainSignV0 = useCallback(async () => {
-// if (okoSol === null) {
-// throw new Error("okoSol is not initialized");
-// }
-
-// if (!okoSol.connected) {
-// await okoSol.connect();
-// }
-
-// if (!okoSol.publicKey) {
-// throw new Error("No public key available");
-// }
-
-// const connection = new Connection(SOLANA_RPC_URL);
-
-// const toAddress = new PublicKey("11111111111111111111111111111111");
-
-// const { blockhash } = await connection.getLatestBlockhash();
-
-// const instructions = [
-// SystemProgram.transfer({
-// fromPubkey: okoSol.publicKey,
-// toPubkey: toAddress,
-// lamports: 0.001 * LAMPORTS_PER_SOL,
-// }),
-// ];
-
-// const messageV0 = new TransactionMessage({
-// payerKey: okoSol.publicKey,
-// recentBlockhash: blockhash,
-// instructions,
-// }).compileToV0Message();
-
-// const versionedTransaction = new VersionedTransaction(messageV0);
-
-// const signedTransaction =
-// await okoSol.signTransaction(versionedTransaction);
-
-// console.log(
-// "Solana v0 signed transaction:",
-// Buffer.from(signedTransaction.signatures[0]).toString("hex"),
-// );
-// }, [okoSol]);
-
-// const handleClickSolOnchainSignLegacy = useCallback(async () => {
-// if (okoSol === null) {
-// throw new Error("okoSol is not initialized");
-// }
-
-// if (!okoSol.connected) {
-// await okoSol.connect();
-// }
-
-// if (!okoSol.publicKey) {
-// throw new Error("No public key available");
-// }
-
-// const connection = new Connection(SOLANA_RPC_URL);
-
-// const toAddress = new PublicKey("11111111111111111111111111111111");
-
-// const transaction = new Transaction().add(
-// SystemProgram.transfer({
-// fromPubkey: okoSol.publicKey,
-// toPubkey: toAddress,
-// lamports: 0.001 * LAMPORTS_PER_SOL,
-// }),
-// );
-
-// const { blockhash } = await connection.getLatestBlockhash();
-// transaction.recentBlockhash = blockhash;
-// transaction.feePayer = okoSol.publicKey;
-
-// const signedTransaction = await okoSol.signTransaction(transaction);
-
-// console.log(
-// "Solana legacy signed transaction:",
-// signedTransaction.signatures.map((sig) =>
-// sig.signature ? Buffer.from(sig.signature).toString("hex") : null,
-// ),
-// );
-// }, [okoSol]);
-
-// return (
-// }
-// signType="onchain"
-// signButtonOnClick={
-// isLegacy ? handleClickSolOnchainSignLegacy : handleClickSolOnchainSignV0
-// }
-// renderBottom={() => (
-//
-// setIsLegacy((prevState) => !prevState)}
-// label="Legacy Transaction"
-// />
-//
-// )}
-// />
-// );
-// };
+import { useCallback, useState } from "react";
+import {
+ Connection,
+ PublicKey,
+ SystemProgram,
+ Transaction,
+ TransactionMessage,
+ VersionedTransaction,
+ LAMPORTS_PER_SOL,
+} from "@solana/web3.js";
+import { SolanaIcon } from "@oko-wallet/oko-common-ui/icons/solana_icon";
+import { Checkbox } from "@oko-wallet/oko-common-ui/checkbox";
+
+import styles from "./solana_onchain_sign_widget.module.scss";
+import { SignWidget } from "@oko-wallet-demo-web/components/widgets/sign_widget/sign_widget";
+import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
+
+const SOLANA_RPC_URL = "https://api.devnet.solana.com";
+
+export const SolanaOnchainSignWidget = () => {
+ const okoSol = useSDKState((state) => state.oko_sol);
+ const [isLegacy, setIsLegacy] = useState(false);
+
+ const handleClickSolOnchainSignV0 = useCallback(async () => {
+ if (okoSol === null) {
+ throw new Error("okoSol is not initialized");
+ }
+
+ if (!okoSol.connected) {
+ await okoSol.connect();
+ }
+
+ if (!okoSol.publicKey) {
+ throw new Error("No public key available");
+ }
+
+ const connection = new Connection(SOLANA_RPC_URL);
+
+ const toAddress = new PublicKey("11111111111111111111111111111111");
+
+ const { blockhash } = await connection.getLatestBlockhash();
+
+ const instructions = [
+ SystemProgram.transfer({
+ fromPubkey: okoSol.publicKey,
+ toPubkey: toAddress,
+ lamports: 0.001 * LAMPORTS_PER_SOL,
+ }),
+ ];
+
+ const messageV0 = new TransactionMessage({
+ payerKey: okoSol.publicKey,
+ recentBlockhash: blockhash,
+ instructions,
+ }).compileToV0Message();
+
+ const versionedTransaction = new VersionedTransaction(messageV0);
+
+ const signedTransaction =
+ await okoSol.signTransaction(versionedTransaction);
+
+ console.log(
+ "Solana v0 signed transaction:",
+ Buffer.from(signedTransaction.signatures[0]).toString("hex"),
+ );
+ }, [okoSol]);
+
+ const handleClickSolOnchainSignLegacy = useCallback(async () => {
+ if (okoSol === null) {
+ throw new Error("okoSol is not initialized");
+ }
+
+ if (!okoSol.connected) {
+ await okoSol.connect();
+ }
+
+ if (!okoSol.publicKey) {
+ throw new Error("No public key available");
+ }
+
+ const connection = new Connection(SOLANA_RPC_URL);
+
+ const toAddress = new PublicKey("11111111111111111111111111111111");
+
+ const transaction = new Transaction().add(
+ SystemProgram.transfer({
+ fromPubkey: okoSol.publicKey,
+ toPubkey: toAddress,
+ lamports: 0.001 * LAMPORTS_PER_SOL,
+ }),
+ );
+
+ const { blockhash } = await connection.getLatestBlockhash();
+ transaction.recentBlockhash = blockhash;
+ transaction.feePayer = okoSol.publicKey;
+
+ const signedTransaction = await okoSol.signTransaction(transaction);
+
+ console.log(
+ "Solana legacy signed transaction:",
+ signedTransaction.signatures.map((sig) =>
+ sig.signature ? Buffer.from(sig.signature).toString("hex") : null,
+ ),
+ );
+ }, [okoSol]);
+
+ return (
+ }
+ signType="onchain"
+ signButtonOnClick={
+ isLegacy ? handleClickSolOnchainSignLegacy : handleClickSolOnchainSignV0
+ }
+ renderBottom={() => (
+
+ setIsLegacy((prevState) => !prevState)}
+ label="Legacy Transaction"
+ />
+
+ )}
+ />
+ );
+};
diff --git a/apps/demo_web/src/hooks/wallet.ts b/apps/demo_web/src/hooks/wallet.ts
index 2bfd50035..e59f7ef79 100644
--- a/apps/demo_web/src/hooks/wallet.ts
+++ b/apps/demo_web/src/hooks/wallet.ts
@@ -1,4 +1,6 @@
import { useEffect, useRef, useState } from "react";
+import type { OkoSolWalletInterface } from "@oko-wallet/oko-sdk-sol";
+import type { Result } from "@oko-wallet/stdlib-js";
import { COSMOS_CHAIN_ID } from "@oko-wallet-demo-web/constants/cosmos";
import { useSDKState } from "@oko-wallet-demo-web/state/sdk";
@@ -7,12 +9,14 @@ import { useUserInfoState } from "@oko-wallet-demo-web/state/user_info";
export function useAddresses() {
const okoCosmos = useSDKState((state) => state.oko_cosmos);
const okoEth = useSDKState((state) => state.oko_eth);
+ const okoSol = useSDKState((state) => state.oko_sol);
const isSignedIn = useUserInfoState((state) => state.isSignedIn);
const isSignedRef = useRef(isSignedIn);
isSignedRef.current = isSignedIn;
const [cosmosAddress, setCosmosAddress] = useState(null);
const [ethAddress, setEthAddress] = useState(null);
+ const [solanaAddress, setSolanaAddress] = useState(null);
useEffect(() => {
if (!isSignedIn) {
@@ -23,12 +27,16 @@ export function useAddresses() {
if (ethAddress) {
setEthAddress(null);
}
+
+ if (solanaAddress) {
+ setSolanaAddress(null);
+ }
return;
}
const loadAddresses = async () => {
try {
- const promises = [];
+ const promises: Promise[] = [];
if (okoCosmos) {
promises.push(
@@ -50,6 +58,16 @@ export function useAddresses() {
);
}
+ if (okoSol) {
+ promises.push(
+ new Promise((resolve, reject) => {
+ connectSol(okoSol, isSignedRef.current, setSolanaAddress)
+ .then(resolve)
+ .catch(reject);
+ }),
+ );
+ }
+
await Promise.all(promises);
} catch (err) {
console.error("Failed to load addresses:", err);
@@ -59,7 +77,37 @@ export function useAddresses() {
if (isSignedIn) {
loadAddresses();
}
- }, [isSignedIn, okoCosmos, okoEth]);
+ }, [
+ isSignedIn,
+ okoCosmos,
+ okoEth,
+ okoSol,
+ cosmosAddress,
+ ethAddress,
+ solanaAddress,
+ ]);
+
+ return { cosmosAddress, ethAddress, solanaAddress };
+}
+
+async function connectSol(
+ okoSol: OkoSolWalletInterface,
+ isSignedRef: boolean,
+ setSolanaAddress: (pk: string) => void,
+): Promise> {
+ try {
+ // this might have been done in lazyInit()
+ if (!okoSol.connected) {
+ await okoSol.connect();
+ }
+
+ if (okoSol.publicKey && isSignedRef) {
+ setSolanaAddress(okoSol.publicKey.toBase58());
+ }
- return { cosmosAddress, ethAddress };
+ return { success: true, data: void 0 };
+ } catch (err: any) {
+ console.error("Failed to get Solana address:", err);
+ return { success: false, err };
+ }
}
diff --git a/apps/demo_web/src/state/sdk.ts b/apps/demo_web/src/state/sdk.ts
index 23f14d6a7..62f8f8220 100644
--- a/apps/demo_web/src/state/sdk.ts
+++ b/apps/demo_web/src/state/sdk.ts
@@ -8,10 +8,10 @@ import {
OkoEthWallet,
type OkoEthWalletInterface,
} from "@oko-wallet/oko-sdk-eth";
-// import {
-// OkoSolWallet,
-// type OkoSolWalletInterface,
-// } from "@oko-wallet/oko-sdk-sol";
+import {
+ OkoSolWallet,
+ type OkoSolWalletInterface,
+} from "@oko-wallet/oko-sdk-sol";
import { create } from "zustand";
import { combine } from "zustand/middleware";
@@ -20,7 +20,7 @@ import { useUserInfoState } from "@oko-wallet-demo-web/state/user_info";
interface SDKState {
oko_eth: OkoEthWalletInterface | null;
oko_cosmos: OkoCosmosWalletInterface | null;
- // oko_sol: OkoSolWalletInterface | null;
+ oko_sol: OkoSolWalletInterface | null;
isEthInitializing: boolean;
isEthLazyInitialized: boolean;
@@ -35,13 +35,13 @@ interface SDKState {
interface SDKActions {
initOkoEth: () => Promise;
initOkoCosmos: () => Promise;
- // initOkoSol: () => Promise;
+ initOkoSol: () => Promise;
}
const initialState: SDKState = {
oko_eth: null,
oko_cosmos: null,
- // oko_sol: null,
+ oko_sol: null,
isEthInitializing: false,
isEthLazyInitialized: false,
@@ -145,10 +145,10 @@ export const useSDKState = create(
initOkoSol: async () => {
const state = get();
- // if (state.oko_sol || state.isSolInitializing) {
- // console.log("Sol SDK already initialized or initializing, skipping...");
- // return state.oko_sol;
- // }
+ if (state.oko_sol || state.isSolInitializing) {
+ console.log("Sol SDK already initialized or initializing, skipping...");
+ return state.oko_sol;
+ }
try {
console.log("Initializing Sol SDK...");
@@ -156,39 +156,41 @@ export const useSDKState = create(
isSolInitializing: true,
});
- // const initRes = OkoSolWallet.init({
- // api_key:
- // "72bd2afd04374f86d563a40b814b7098e5ad6c7f52d3b8f84ab0c3d05f73ac6c",
- // sdk_endpoint: process.env.NEXT_PUBLIC_OKO_SDK_ENDPOINT,
- // });
-
- // if (initRes.success) {
- // console.log("Sol SDK initialized");
-
- // const okoSol = initRes.data;
- // set({
- // // oko_sol: okoSol,
- // isSolInitializing: false,
- // });
-
- // try {
- // await okoSol.waitUntilInitialized;
- // console.log("Sol SDK lazy initialized");
- // set({
- // isSolLazyInitialized: true,
- // });
- // } catch (e) {
- // console.error("Sol SDK lazy init failed:", e);
- // set({ isSolLazyInitialized: true }); // Still mark as done to not block
- // }
-
- // return okoSol;
- // } else {
- // console.error("Sol sdk init fail, err: %s", initRes.err);
- // set({ isSolInitializing: false, isSolLazyInitialized: true });
-
- // return null;
- // }
+ const initRes = OkoSolWallet.init({
+ api_key:
+ "72bd2afd04374f86d563a40b814b7098e5ad6c7f52d3b8f84ab0c3d05f73ac6c",
+ sdk_endpoint: process.env.NEXT_PUBLIC_OKO_SDK_ENDPOINT,
+ });
+
+ if (initRes.success) {
+ console.log("Sol SDK initialized");
+
+ const okoSol = initRes.data;
+ setupSolListener(okoSol);
+
+ set({
+ oko_sol: okoSol,
+ isSolInitializing: false,
+ });
+
+ try {
+ await okoSol.waitUntilInitialized;
+ console.log("Sol SDK lazy initialized");
+ set({
+ isSolLazyInitialized: true,
+ });
+ } catch (e) {
+ console.error("Sol SDK lazy init failed:", e);
+ set({ isSolLazyInitialized: true }); // Still mark as done to not block
+ }
+
+ return okoSol;
+ } else {
+ console.error("Sol sdk init fail, err: %s", initRes.err);
+ set({ isSolInitializing: false, isSolLazyInitialized: true });
+
+ return null;
+ }
} catch (e) {
console.error("Sol SDK init error:", e);
set({ isSolInitializing: false, isSolLazyInitialized: true });
@@ -222,3 +224,16 @@ function setupCosmosListener(cosmosSDK: OkoCosmosWalletInterface) {
});
}
}
+
+function setupSolListener(solSDK: OkoSolWalletInterface) {
+ const setPublicKeyEd25519 = useUserInfoState.getState().setPublicKeyEd25519;
+
+ console.log("[Demo] Setting up Sol accountChanged listener");
+ solSDK.on("accountChanged", () => {
+ const ed25519Key = solSDK.state.publicKeyRaw;
+ console.log("[Demo] Sol accountChanged event received:", {
+ ed25519Key: ed25519Key ? "exists" : "null",
+ });
+ setPublicKeyEd25519(ed25519Key);
+ });
+}
diff --git a/apps/demo_web/src/state/user_info.ts b/apps/demo_web/src/state/user_info.ts
index e6bdfd843..d2a10da4d 100644
--- a/apps/demo_web/src/state/user_info.ts
+++ b/apps/demo_web/src/state/user_info.ts
@@ -5,7 +5,8 @@ import { combine } from "zustand/middleware";
interface UserInfoState {
authType: AuthType | null;
email: string | null;
- publicKey: string | null;
+ publicKeySecp256k1: string | null;
+ publicKeyEd25519: string | null;
name: string | null;
isSignedIn: boolean;
}
@@ -15,8 +16,10 @@ interface UserInfoActions {
authType: AuthType | null;
email: string | null;
publicKey: string | null;
+ publicKeyEd25519?: string | null;
name?: string | null;
}) => void;
+ setPublicKeyEd25519: (publicKeyEd25519: string | null) => void;
clearUserInfo: () => void;
}
@@ -25,7 +28,8 @@ export const useUserInfoState = create(
{
authType: null,
email: null,
- publicKey: null,
+ publicKeySecp256k1: null,
+ publicKeyEd25519: null,
name: null,
isSignedIn: false,
},
@@ -34,16 +38,21 @@ export const useUserInfoState = create(
set({
authType: info.authType,
email: info.email ?? null,
- publicKey: info.publicKey,
+ publicKeySecp256k1: info.publicKey,
+ publicKeyEd25519: info.publicKeyEd25519 ?? null,
name: info.name ?? null,
isSignedIn: !!info.publicKey,
});
},
+ setPublicKeyEd25519: (publicKeyEd25519) => {
+ set({ publicKeyEd25519 });
+ },
clearUserInfo: () => {
set({
authType: null,
email: null,
- publicKey: null,
+ publicKeySecp256k1: null,
+ publicKeyEd25519: null,
name: null,
isSignedIn: false,
});
diff --git a/backend/tss_api/src/api/sign_ed25519/index.ts b/backend/tss_api/src/api/sign_ed25519/index.ts
index 7d49e0ccc..d727fe0b9 100644
--- a/backend/tss_api/src/api/sign_ed25519/index.ts
+++ b/backend/tss_api/src/api/sign_ed25519/index.ts
@@ -54,39 +54,24 @@ export async function runSignEd25519Round1(
}
const wallet = validateWalletEmailAndCurveTypeRes.data;
- const encryptedShare = wallet.enc_tss_share.toString("utf-8");
- const decryptedShare = await decryptDataAsync(
- encryptedShare,
+ // Decrypt and reconstruct key package
+ const storedShares = await decryptEd25519WalletShares(
+ wallet.enc_tss_share,
encryptionSecret,
);
- const storedShares = JSON.parse(decryptedShare) as {
- signing_share: number[];
- verifying_share: number[];
- };
-
- // Reconstruct key_package from stored shares
- const serverIdentifier = participantToIdentifier(Participant.P1);
- const verifyingKey = Array.from(wallet.public_key);
- const minSigners = 2;
-
- let keyPackageBytes: Uint8Array;
- try {
- keyPackageBytes = reconstructKeyPackageEd25519(
- new Uint8Array(storedShares.signing_share),
- new Uint8Array(storedShares.verifying_share),
- new Uint8Array(serverIdentifier),
- new Uint8Array(verifyingKey),
- minSigners,
- );
- } catch (error) {
+ const keyPackageRes = reconstructServerKeyPackageEd25519(
+ storedShares,
+ wallet.public_key,
+ );
+ if (!keyPackageRes.success) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to reconstruct key_package: ${error instanceof Error ? error.message : String(error)}`,
+ msg: keyPackageRes.error,
};
}
- const round1Result = runSignRound1Ed25519(keyPackageBytes);
+ const round1Result = runSignRound1Ed25519(keyPackageRes.data);
// Create TSS session
const sessionRes = await createTssSession(db, {
@@ -212,35 +197,20 @@ export async function runSignEd25519Round2(
};
}
- const encryptedShare = wallet.enc_tss_share.toString("utf-8");
- const decryptedShare = await decryptDataAsync(
- encryptedShare,
+ // Decrypt and reconstruct key package
+ const storedShares = await decryptEd25519WalletShares(
+ wallet.enc_tss_share,
encryptionSecret,
);
- const storedShares = JSON.parse(decryptedShare) as {
- signing_share: number[];
- verifying_share: number[];
- };
-
- // Reconstruct key_package from stored shares
- const serverIdentifier = participantToIdentifier(Participant.P1);
- const verifyingKey = Array.from(wallet.public_key);
- const minSigners = 2;
-
- let keyPackageBytes: Uint8Array;
- try {
- keyPackageBytes = reconstructKeyPackageEd25519(
- new Uint8Array(storedShares.signing_share),
- new Uint8Array(storedShares.verifying_share),
- new Uint8Array(serverIdentifier),
- new Uint8Array(verifyingKey),
- minSigners,
- );
- } catch (error) {
+ const keyPackageRes = reconstructServerKeyPackageEd25519(
+ storedShares,
+ wallet.public_key,
+ );
+ if (!keyPackageRes.success) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `Failed to reconstruct key_package: ${error instanceof Error ? error.message : String(error)}`,
+ msg: keyPackageRes.error,
};
}
@@ -258,7 +228,7 @@ export async function runSignEd25519Round2(
const round2Result = runSignRound2Ed25519(
new Uint8Array(msg),
- keyPackageBytes,
+ keyPackageRes.data,
new Uint8Array(nonces),
allCommitments,
);
@@ -301,3 +271,51 @@ export async function runSignEd25519Round2(
};
}
}
+
+interface Ed25519StoredShares {
+ signing_share: number[];
+ verifying_share: number[];
+}
+
+/**
+ * Decrypt ed25519 wallet shares from encrypted storage.
+ */
+async function decryptEd25519WalletShares(
+ encTssShare: Buffer,
+ encryptionSecret: string,
+): Promise {
+ const encryptedShare = encTssShare.toString("utf-8");
+ const decryptedShare = await decryptDataAsync(
+ encryptedShare,
+ encryptionSecret,
+ );
+ return JSON.parse(decryptedShare) as Ed25519StoredShares;
+}
+
+/**
+ * Reconstruct server's key package from stored shares.
+ * Returns the key package bytes or an error response.
+ */
+function reconstructServerKeyPackageEd25519(
+ storedShares: Ed25519StoredShares,
+ publicKey: Buffer,
+): { success: true; data: Uint8Array } | { success: false; error: string } {
+ const serverIdentifier = participantToIdentifier(Participant.P1);
+ const verifyingKey = Array.from(publicKey);
+
+ try {
+ const keyPackageBytes = reconstructKeyPackageEd25519(
+ new Uint8Array(storedShares.signing_share),
+ new Uint8Array(storedShares.verifying_share),
+ new Uint8Array(serverIdentifier),
+ new Uint8Array(verifyingKey),
+ 2,
+ );
+ return { success: true, data: keyPackageBytes };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to reconstruct key_package: ${error instanceof Error ? error.message : String(error)}`,
+ };
+ }
+}
diff --git a/backend/tss_api/src/api/v2/keygen/index.ts b/backend/tss_api/src/api/v2/keygen/index.ts
index 95c18c802..c7e4f6e2a 100644
--- a/backend/tss_api/src/api/v2/keygen/index.ts
+++ b/backend/tss_api/src/api/v2/keygen/index.ts
@@ -153,26 +153,22 @@ export async function runKeygenV2(
}
// 3. Validate ed25519 public key
- const ed25519PublicKeyUint8 = new Uint8Array(keygen_2_ed25519.public_key);
- const ed25519PublicKeyHex = Buffer.from(ed25519PublicKeyUint8).toString(
- "hex",
- );
-
- const ed25519PublicKeyRes = Bytes.fromUint8Array(ed25519PublicKeyUint8, 32);
- if (ed25519PublicKeyRes.success === false) {
+ const ed25519PkValidation = validateEd25519PublicKey(keygen_2_ed25519.public_key);
+ if (!ed25519PkValidation.success) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `ed25519 publicKeyRes error: ${ed25519PublicKeyRes.err}`,
+ msg: ed25519PkValidation.error,
};
}
- const ed25519PublicKeyBytes = ed25519PublicKeyRes.data;
+ const {
+ publicKeyHex: ed25519PublicKeyHex,
+ publicKeyBytes: ed25519PublicKeyBytes,
+ publicKeyBuffer: ed25519PublicKeyBuffer,
+ } = ed25519PkValidation.data;
// Check for duplicate ed25519 public key
- const ed25519WalletByPkRes = await getWalletByPublicKey(
- db,
- Buffer.from(ed25519PublicKeyBytes.toUint8Array()),
- );
+ const ed25519WalletByPkRes = await getWalletByPublicKey(db, ed25519PublicKeyBuffer);
if (ed25519WalletByPkRes.success === false) {
return {
success: false,
@@ -245,24 +241,10 @@ export async function runKeygenV2(
);
// 7. Encrypt ed25519 share (extract signing_share and verifying_share)
- const ed25519KeyPackageShares = extractKeyPackageSharesEd25519(
- new Uint8Array(keygen_2_ed25519.key_package),
- );
- const ed25519SharesData = {
- signing_share: ed25519KeyPackageShares.signing_share,
- verifying_share: ed25519KeyPackageShares.verifying_share,
- };
- const ed25519EncryptedShare = await encryptDataAsync(
- JSON.stringify(ed25519SharesData),
- encryptionSecret,
- );
- const ed25519EncryptedShareBuffer = Buffer.from(
- ed25519EncryptedShare,
- "utf-8",
- );
- const serverVerifyingShareEd25519Hex = Buffer.from(
- ed25519KeyPackageShares.verifying_share,
- ).toString("hex");
+ const {
+ encryptedShareBuffer: ed25519EncryptedShareBuffer,
+ serverVerifyingShareHex: serverVerifyingShareEd25519Hex,
+ } = await encryptEd25519KeyPackageShares(keygen_2_ed25519.key_package, encryptionSecret);
// 8. Get SSS threshold
const getKeyshareNodeMetaRes = await getKeyShareNodeMeta(db);
@@ -468,37 +450,33 @@ export async function runKeygenEd25519(
};
}
- const ed25519PublicKeyUint8 = new Uint8Array(keygen_2.public_key);
- const ed25519PublicKeyHex = Buffer.from(ed25519PublicKeyUint8).toString(
- "hex",
- );
-
- const ed25519PublicKeyRes = Bytes.fromUint8Array(ed25519PublicKeyUint8, 32);
- if (ed25519PublicKeyRes.success === false) {
+ const ed25519PkValidation = validateEd25519PublicKey(keygen_2.public_key);
+ if (!ed25519PkValidation.success) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `ed25519 publicKeyRes error: ${ed25519PublicKeyRes.err}`,
+ msg: ed25519PkValidation.error,
};
}
- const ed25519PublicKeyBytes = ed25519PublicKeyRes.data;
+ const {
+ publicKeyHex: ed25519PublicKeyHex,
+ publicKeyBytes: ed25519PublicKeyBytes,
+ publicKeyBuffer: ed25519PublicKeyBuffer,
+ } = ed25519PkValidation.data;
- const walletByPublicKeyRes = await getWalletByPublicKey(
- db,
- Buffer.from(ed25519PublicKeyBytes.toUint8Array()),
- );
+ const walletByPublicKeyRes = await getWalletByPublicKey(db, ed25519PublicKeyBuffer);
if (walletByPublicKeyRes.success === false) {
return {
success: false,
code: "UNKNOWN_ERROR",
- msg: `getWalletByPublicKey error: ${walletByPublicKeyRes.err}`,
+ msg: `getWalletByPublicKey (ed25519) error: ${walletByPublicKeyRes.err}`,
};
}
if (walletByPublicKeyRes.data !== null) {
return {
success: false,
code: "DUPLICATE_PUBLIC_KEY",
- msg: `Duplicate public key: ${ed25519PublicKeyHex}`,
+ msg: `Duplicate ed25519 public key: ${ed25519PublicKeyHex}`,
};
}
@@ -536,25 +514,11 @@ export async function runKeygenEd25519(
}
const ksNodeIds = ed25519KsNodeIds;
- // Extract signing_share and verifying_share from key_package
- const ed25519KeyPackageShares = extractKeyPackageSharesEd25519(
- new Uint8Array(keygen_2.key_package),
- );
-
- // Store only signing_share and verifying_share (64 bytes total)
- const sharesData = {
- signing_share: ed25519KeyPackageShares.signing_share,
- verifying_share: ed25519KeyPackageShares.verifying_share,
- };
-
- const encryptedShare = await encryptDataAsync(
- JSON.stringify(sharesData),
- encryptionSecret,
- );
- const encryptedShareBuffer = Buffer.from(encryptedShare, "utf-8");
- const serverVerifyingShareEd25519Hex = Buffer.from(
- ed25519KeyPackageShares.verifying_share,
- ).toString("hex");
+ // Extract and encrypt ed25519 key package shares
+ const {
+ encryptedShareBuffer,
+ serverVerifyingShareHex: serverVerifyingShareEd25519Hex,
+ } = await encryptEd25519KeyPackageShares(keygen_2.key_package, encryptionSecret);
const getKeyshareNodeMetaRes = await getKeyShareNodeMeta(db);
if (getKeyshareNodeMetaRes.success === false) {
@@ -650,3 +614,68 @@ export async function runKeygenEd25519(
};
}
}
+
+interface Ed25519PublicKeyValidationResult {
+ publicKeyHex: string;
+ publicKeyBytes: Bytes<32>;
+ publicKeyBuffer: Buffer;
+}
+
+/**
+ * Validate and convert ed25519 public key from number array to various formats.
+ */
+function validateEd25519PublicKey(
+ publicKeyArray: number[],
+): { success: true; data: Ed25519PublicKeyValidationResult } | { success: false; error: string } {
+ const publicKeyUint8 = new Uint8Array(publicKeyArray);
+ const publicKeyHex = Buffer.from(publicKeyUint8).toString("hex");
+
+ const publicKeyRes = Bytes.fromUint8Array(publicKeyUint8, 32);
+ if (publicKeyRes.success === false) {
+ return {
+ success: false,
+ error: `ed25519 publicKeyRes error: ${publicKeyRes.err}`,
+ };
+ }
+
+ return {
+ success: true,
+ data: {
+ publicKeyHex,
+ publicKeyBytes: publicKeyRes.data,
+ publicKeyBuffer: Buffer.from(publicKeyRes.data.toUint8Array()),
+ },
+ };
+}
+
+interface Ed25519EncryptedShareResult {
+ encryptedShareBuffer: Buffer;
+ serverVerifyingShareHex: string;
+}
+
+/**
+ * Extract shares from ed25519 key package and encrypt for storage.
+ */
+async function encryptEd25519KeyPackageShares(
+ keyPackage: number[],
+ encryptionSecret: string,
+): Promise {
+ const keyPackageShares = extractKeyPackageSharesEd25519(
+ new Uint8Array(keyPackage),
+ );
+
+ const sharesData = {
+ signing_share: keyPackageShares.signing_share,
+ verifying_share: keyPackageShares.verifying_share,
+ };
+
+ const encryptedShare = await encryptDataAsync(
+ JSON.stringify(sharesData),
+ encryptionSecret,
+ );
+
+ return {
+ encryptedShareBuffer: Buffer.from(encryptedShare, "utf-8"),
+ serverVerifyingShareHex: Buffer.from(keyPackageShares.verifying_share).toString("hex"),
+ };
+}
diff --git a/crypto/tecdsa/api_lib/src/index.ts b/crypto/tecdsa/api_lib/src/index.ts
index b232c1810..02cc87ae7 100644
--- a/crypto/tecdsa/api_lib/src/index.ts
+++ b/crypto/tecdsa/api_lib/src/index.ts
@@ -51,7 +51,7 @@ import {
type SignInResponse,
type SignInResponseV2,
} from "@oko-wallet/oko-types/user";
-import type { KeygenBodyV2 } from "@oko-wallet/oko-types/tss";
+import type { KeygenRequestBodyV2 } from "@oko-wallet/oko-types/tss";
import {
type ErrorCode,
type OkoApiErrorResponse,
@@ -198,7 +198,7 @@ export async function reqKeygen(
export async function reqKeygenV2(
endpoint: string,
- payload: KeygenBodyV2,
+ payload: KeygenRequestBodyV2,
authToken: string,
) {
const resp: OkoApiResponse = await makePostRequest(
diff --git a/crypto/teddsa/api_lib/src/index.ts b/crypto/teddsa/api_lib/src/index.ts
index a4209962d..450547207 100644
--- a/crypto/teddsa/api_lib/src/index.ts
+++ b/crypto/teddsa/api_lib/src/index.ts
@@ -1,5 +1,5 @@
import type {
- KeygenEd25519Body,
+ KeygenEd25519RequestBody,
SignEd25519Round1Body,
SignEd25519Round1Response,
SignEd25519Round2Body,
@@ -118,7 +118,7 @@ async function makePostRequest(
export async function reqKeygenEd25519(
endpoint: string,
- payload: KeygenEd25519Body,
+ payload: KeygenEd25519RequestBody,
authToken: string,
) {
const resp: OkoApiResponse = await makePostRequest(
diff --git a/embed/oko_attached/src/window_msgs/oauth_info_pass/user_v2.ts b/embed/oko_attached/src/window_msgs/oauth_info_pass/user_v2.ts
index ffe2d57d1..9639c1f3d 100644
--- a/embed/oko_attached/src/window_msgs/oauth_info_pass/user_v2.ts
+++ b/embed/oko_attached/src/window_msgs/oauth_info_pass/user_v2.ts
@@ -39,6 +39,7 @@ import {
runTeddsaKeygen,
serializeKeyPackage,
serializePublicKeyPackage,
+ type TeddsaKeygenOutputBytes,
} from "@oko-wallet/teddsa-hooks";
import { reqKeygenEd25519 } from "@oko-wallet/teddsa-api-lib";
import { reqKeygenV2 } from "@oko-wallet/api-lib";
@@ -83,16 +84,17 @@ export async function handleNewUserV2(
const { keygen_1: secp256k1Keygen1, keygen_2: secp256k1Keygen2 } =
secp256k1KeygenRes.data;
- // 2. ed25519 keygen
- const ed25519KeygenRes = await runTeddsaKeygen();
- if (ed25519KeygenRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519KeygenRes.err },
- };
+ // 2. ed25519 keygen and split
+ const ed25519KeygenSplitRes =
+ await runEd25519KeygenAndSplit(keyshareNodeMeta);
+ if (ed25519KeygenSplitRes.success === false) {
+ return { success: false, err: ed25519KeygenSplitRes.err };
}
- const { keygen_1: ed25519Keygen1, keygen_2: ed25519Keygen2 } =
- ed25519KeygenRes.data;
+ const {
+ keygen1: ed25519Keygen1,
+ keygen2: ed25519Keygen2,
+ userKeyShares: ed25519UserKeyShares,
+ } = ed25519KeygenSplitRes.data;
// 3. secp256k1 key share split
const splitUserKeySharesRes = await splitUserKeyShares(
@@ -107,28 +109,6 @@ export async function handleNewUserV2(
}
const secp256k1UserKeyShares = splitUserKeySharesRes.data;
- // 4. ed25519 key share split
- const ed25519SigningShareRes = extractSigningShare(
- ed25519Keygen1.key_package,
- );
- if (ed25519SigningShareRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SigningShareRes.err },
- };
- }
- const ed25519SplitRes = await splitTeddsaSigningShare(
- ed25519SigningShareRes.data,
- keyshareNodeMeta,
- );
- if (ed25519SplitRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SplitRes.err },
- };
- }
- const ed25519UserKeyShares = ed25519SplitRes.data;
-
// 5. Send key shares by both curves to ks nodes using V2 API
const registerKeySharesResults: Result[] = await Promise.all(
secp256k1UserKeyShares.map((keyShareByNode, index) =>
@@ -161,6 +141,7 @@ export async function handleNewUserV2(
const reqKeygenV2Res = await reqKeygenV2(
TSS_V2_ENDPOINT,
{
+ auth_type: authType,
keygen_2_secp256k1: {
public_key: secp256k1Keygen1.public_key.toHex(),
private_share: secp256k1Keygen2.tss_private_share.toHex(),
@@ -229,38 +210,11 @@ export async function handleExistingUserV2(
authType: AuthType,
): Promise> {
// 1. Sign in to API server
- const signInRes = await makeAuthorizedOkoApiRequest(
- "user/signin",
- idToken,
- {
- auth_type: authType,
- },
- TSS_V2_ENDPOINT,
- );
- if (!signInRes.success) {
- console.error("[attached] sign in failed, err: %s", signInRes.err);
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: signInRes.err.toString() },
- };
- }
-
- const apiResponse = signInRes.data;
- if (!apiResponse.success) {
- console.error(
- "[attached] sign in request failed, err: %s",
- apiResponse.msg,
- );
- return {
- success: false,
- err: {
- type: "sign_in_request_fail",
- error: `code: ${apiResponse.code}`,
- },
- };
+ const signInResult = await signInV2(idToken, authType);
+ if (!signInResult.success) {
+ return { success: false, err: signInResult.err };
}
-
- const signInResp = apiResponse.data;
+ const signInResp = signInResult.data;
// 2. Request secp256k1 and ed25519 shares from KS nodes using V2 API
const requestSharesRes = await requestKeySharesV2(
@@ -302,37 +256,16 @@ export async function handleExistingUserV2(
};
}
- // 3. Combine secp256k1 shares (convert hex strings to Point256)
- const secp256k1SharesByNode: UserKeySharePointByNode[] = [];
- for (const item of requestSharesRes.data) {
- const shareHex = item.shares.secp256k1;
- if (!shareHex) {
- return {
- success: false,
- err: {
- type: "key_share_combine_fail",
- error: `secp256k1 share missing from node: ${item.node.name}`,
- },
- };
- }
- const point256Res = decodeKeyShareStringToPoint256(shareHex);
- if (point256Res.success === false) {
- return {
- success: false,
- err: {
- type: "key_share_combine_fail",
- error: `secp256k1 decode err: ${point256Res.err}`,
- },
- };
- }
- secp256k1SharesByNode.push({
- node: item.node,
- share: point256Res.data,
- });
+ // 3. Decode and combine secp256k1 shares
+ const secp256k1DecodeRes = await decodeSecp256k1SharesByNode(
+ requestSharesRes.data,
+ );
+ if (!secp256k1DecodeRes.success) {
+ return { success: false, err: secp256k1DecodeRes.err };
}
const keyshare1Secp256k1Res = await combineUserShares(
- secp256k1SharesByNode,
+ secp256k1DecodeRes.data,
keyshareNodeMetaSecp256k1.threshold,
);
if (keyshare1Secp256k1Res.success === false) {
@@ -507,40 +440,20 @@ export async function handleExistingUserNeedsEd25519Keygen(
keyshareNodeMetaEd25519: KeyShareNodeMetaWithNodeStatusInfo,
authType: AuthType,
): Promise> {
- // 1. ed25519 keygen
- const ed25519KeygenRes = await runTeddsaKeygen();
- if (ed25519KeygenRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519KeygenRes.err },
- };
- }
- const { keygen_1: ed25519Keygen1, keygen_2: ed25519Keygen2 } =
- ed25519KeygenRes.data;
-
- // 2. ed25519 key share split
- const ed25519SigningShareRes = extractSigningShare(
- ed25519Keygen1.key_package,
- );
- if (ed25519SigningShareRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SigningShareRes.err },
- };
- }
- const ed25519SplitRes = await splitTeddsaSigningShare(
- ed25519SigningShareRes.data,
+ // 1. ed25519 keygen and split
+ const ed25519KeygenSplitRes = await runEd25519KeygenAndSplit(
keyshareNodeMetaEd25519,
);
- if (ed25519SplitRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SplitRes.err },
- };
+ if (ed25519KeygenSplitRes.success === false) {
+ return { success: false, err: ed25519KeygenSplitRes.err };
}
- const ed25519UserKeyShares = ed25519SplitRes.data;
+ const {
+ keygen1: ed25519Keygen1,
+ keygen2: ed25519Keygen2,
+ userKeyShares: ed25519UserKeyShares,
+ } = ed25519KeygenSplitRes.data;
- // 3. Send ed25519 key shares to ks nodes using V2 API
+ // 2. Send ed25519 key shares to ks nodes using V2 API
const registerEd25519Results: Result[] = await Promise.all(
keyshareNodeMetaEd25519.nodes.map((node, index) =>
registerKeyShareEd25519V2(
@@ -569,6 +482,7 @@ export async function handleExistingUserNeedsEd25519Keygen(
const reqKeygenEd25519Res = await reqKeygenEd25519(
TSS_V2_ENDPOINT,
{
+ auth_type: authType,
keygen_2: {
key_package: serializeKeyPackage(ed25519Keygen2.key_package),
public_key_package: serializePublicKeyPackage(
@@ -628,37 +542,16 @@ export async function handleExistingUserNeedsEd25519Keygen(
};
}
- // 7. Combine secp256k1 shares (convert hex strings to Point256)
- const secp256k1SharesByNode: UserKeySharePointByNode[] = [];
- for (const item of requestSharesRes.data) {
- const shareHex = item.shares.secp256k1;
- if (!shareHex) {
- return {
- success: false,
- err: {
- type: "key_share_combine_fail",
- error: `secp256k1 share missing from node: ${item.node.name}`,
- },
- };
- }
- const point256Res = decodeKeyShareStringToPoint256(shareHex);
- if (point256Res.success === false) {
- return {
- success: false,
- err: {
- type: "key_share_combine_fail",
- error: `secp256k1 decode err: ${point256Res.err}`,
- },
- };
- }
- secp256k1SharesByNode.push({
- node: item.node,
- share: point256Res.data,
- });
+ // 6. Decode and combine secp256k1 shares
+ const secp256k1DecodeRes = await decodeSecp256k1SharesByNode(
+ requestSharesRes.data,
+ );
+ if (!secp256k1DecodeRes.success) {
+ return { success: false, err: secp256k1DecodeRes.err };
}
const keyshare1Secp256k1Res = await combineUserShares(
- secp256k1SharesByNode,
+ secp256k1DecodeRes.data,
keyshareNodeMetaSecp256k1.threshold,
);
if (keyshare1Secp256k1Res.success === false) {
@@ -671,7 +564,7 @@ export async function handleExistingUserNeedsEd25519Keygen(
};
}
- // 8. Convert ed25519 keygen1 to hex format for storage
+ // 7. Convert ed25519 keygen1 to hex format for storage
const keyPackageEd25519Hex = teddsaKeygenToHex(ed25519Keygen1);
return {
@@ -721,38 +614,11 @@ export async function handleReshareV2(
ed25519NeedsReshare: boolean,
): Promise> {
// 1. Sign in to API server to get public keys and server verifying share
- const signInRes = await makeAuthorizedOkoApiRequest(
- "user/signin",
- idToken,
- {
- auth_type: authType,
- },
- TSS_V2_ENDPOINT,
- );
- if (!signInRes.success) {
- console.error("[attached] sign in failed, err: %s", signInRes.err);
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: signInRes.err.toString() },
- };
- }
-
- const apiResponse = signInRes.data;
- if (!apiResponse.success) {
- console.error(
- "[attached] sign in request failed, err: %s",
- apiResponse.msg,
- );
- return {
- success: false,
- err: {
- type: "sign_in_request_fail",
- error: `code: ${apiResponse.code}`,
- },
- };
+ const signInResult = await signInV2(idToken, authType);
+ if (!signInResult.success) {
+ return { success: false, err: signInResult.err };
}
-
- const signInResp = apiResponse.data;
+ const signInResp = signInResult.data;
// Parse public keys
const publicKeySecp256k1Res = Bytes.fromHexString(
@@ -868,80 +734,28 @@ export async function handleReshareAndEd25519Keygen(
};
}
- // 2. ed25519 keygen (new)
- const ed25519KeygenRes = await runTeddsaKeygen();
- if (ed25519KeygenRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519KeygenRes.err },
- };
- }
- const { keygen_1: ed25519Keygen1, keygen_2: ed25519Keygen2 } =
- ed25519KeygenRes.data;
-
- // 3. ed25519 key share split
- const ed25519SigningShareRes = extractSigningShare(
- ed25519Keygen1.key_package,
- );
- if (ed25519SigningShareRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SigningShareRes.err },
- };
- }
- const ed25519SplitRes = await splitTeddsaSigningShare(
- ed25519SigningShareRes.data,
+ // 2. ed25519 keygen and split
+ const ed25519KeygenSplitRes = await runEd25519KeygenAndSplit(
keyshareNodeMetaEd25519,
);
- if (ed25519SplitRes.success === false) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: ed25519SplitRes.err },
- };
+ if (ed25519KeygenSplitRes.success === false) {
+ return { success: false, err: ed25519KeygenSplitRes.err };
}
- const ed25519UserKeyShares = ed25519SplitRes.data;
-
- // 4. Request secp256k1 shares from ACTIVE nodes
- const requestSharesRes = await requestKeySharesV2(
- idToken,
- activeNodes,
- keyshareNodeMetaSecp256k1.threshold,
- authType,
- {
- secp256k1: undefined, // Will be filled after sign-in
- },
- );
-
- // We need to sign in first to get the public key
- const signInRes = await makeAuthorizedOkoApiRequest(
- "user/signin",
- idToken,
- {
- auth_type: authType,
- },
- TSS_V2_ENDPOINT,
- );
- if (!signInRes.success) {
- return {
- success: false,
- err: { type: "sign_in_request_fail", error: signInRes.err.toString() },
- };
+ const {
+ keygen1: ed25519Keygen1,
+ keygen2: ed25519Keygen2,
+ userKeyShares: ed25519UserKeyShares,
+ } = ed25519KeygenSplitRes.data;
+
+ // 3. Sign in to get the public key
+ const signInResult = await signInV2(idToken, authType);
+ if (!signInResult.success) {
+ return { success: false, err: signInResult.err };
}
-
- const apiResponse = signInRes.data;
- if (!apiResponse.success) {
- return {
- success: false,
- err: {
- type: "sign_in_request_fail",
- error: `code: ${apiResponse.code}`,
- },
- };
- }
- const signInResp = apiResponse.data;
+ const signInResp = signInResult.data;
const secp256k1PublicKey = signInResp.user.public_key_secp256k1;
- // 5. Request secp256k1 shares with public key
+ // 4. Request secp256k1 shares with public key
const requestSecp256k1SharesRes = await requestKeySharesV2(
idToken,
activeNodes,
@@ -961,31 +775,25 @@ export async function handleReshareAndEd25519Keygen(
};
}
- // 6. secp256k1 expand
- const secp256k1SharesByNode: UserKeySharePointByNode[] = [];
- for (const item of requestSecp256k1SharesRes.data) {
- const shareHex = item.shares.secp256k1;
- if (!shareHex) {
- continue;
- }
- const point256Res = decodeKeyShareStringToPoint256(shareHex);
- if (!point256Res.success) {
- return {
- success: false,
- err: {
- type: "reshare_fail",
- error: `secp256k1 decode err: ${point256Res.err}`,
- },
- };
- }
- secp256k1SharesByNode.push({
- node: item.node,
- share: point256Res.data,
- });
+ // 5. Decode secp256k1 shares (filter out items without shares for reshare case)
+ const itemsWithSecp256k1 = requestSecp256k1SharesRes.data.filter(
+ (item) => item.shares.secp256k1,
+ );
+ const secp256k1DecodeRes =
+ await decodeSecp256k1SharesByNode(itemsWithSecp256k1);
+ if (!secp256k1DecodeRes.success) {
+ return {
+ success: false,
+ err: {
+ type: "reshare_fail",
+ error: secp256k1DecodeRes.err.error,
+ },
+ };
}
+ // 6. Expand secp256k1 shares to additional nodes
const secp256k1ExpandRes = await runExpandShares(
- secp256k1SharesByNode,
+ secp256k1DecodeRes.data,
additionalNodes,
keyshareNodeMetaSecp256k1.threshold,
);
@@ -1075,6 +883,7 @@ export async function handleReshareAndEd25519Keygen(
const reqKeygenEd25519Res = await reqKeygenEd25519(
TSS_V2_ENDPOINT,
{
+ auth_type: authType,
keygen_2: {
key_package: serializeKeyPackage(ed25519Keygen2.key_package),
public_key_package: serializePublicKeyPackage(
@@ -1165,3 +974,152 @@ async function saveReferralV2(
throw new Error(`Save referral V2 API error: ${apiResponse.msg}`);
}
}
+
+/**
+ * Decode secp256k1 shares from hex strings to Point256 format.
+ * Used by multiple handlers that need to process shares from KS nodes.
+ */
+async function decodeSecp256k1SharesByNode(
+ sharesData: Array<{
+ node: { name: string; endpoint: string };
+ shares: { secp256k1?: string };
+ }>,
+): Promise<
+ Result<
+ UserKeySharePointByNode[],
+ { type: "key_share_combine_fail"; error: string }
+ >
+> {
+ const sharesByNode: UserKeySharePointByNode[] = [];
+
+ for (const item of sharesData) {
+ const shareHex = item.shares.secp256k1;
+ if (!shareHex) {
+ return {
+ success: false,
+ err: {
+ type: "key_share_combine_fail",
+ error: `secp256k1 share missing from node: ${item.node.name}`,
+ },
+ };
+ }
+ const point256Res = decodeKeyShareStringToPoint256(shareHex);
+ if (point256Res.success === false) {
+ return {
+ success: false,
+ err: {
+ type: "key_share_combine_fail",
+ error: `secp256k1 decode err: ${point256Res.err}`,
+ },
+ };
+ }
+ sharesByNode.push({
+ node: item.node,
+ share: point256Res.data,
+ });
+ }
+
+ return { success: true, data: sharesByNode };
+}
+
+interface Ed25519KeygenSplitResult {
+ keygen1: TeddsaKeygenOutputBytes;
+ keygen2: TeddsaKeygenOutputBytes;
+ userKeyShares: TeddsaKeyShareByNode[];
+}
+
+/**
+ * Run ed25519 keygen and split the signing share for distribution to KS nodes.
+ * Used by handlers that need to create new ed25519 wallets.
+ */
+async function runEd25519KeygenAndSplit(
+ keyshareNodeMeta: KeyShareNodeMetaWithNodeStatusInfo,
+): Promise<
+ Result<
+ Ed25519KeygenSplitResult,
+ { type: "sign_in_request_fail"; error: string }
+ >
+> {
+ // 1. ed25519 keygen
+ const ed25519KeygenRes = await runTeddsaKeygen();
+ if (ed25519KeygenRes.success === false) {
+ return {
+ success: false,
+ err: { type: "sign_in_request_fail", error: ed25519KeygenRes.err },
+ };
+ }
+ const { keygen_1: keygen1, keygen_2: keygen2 } = ed25519KeygenRes.data;
+
+ // 2. Extract signing share from key package
+ const signingShareRes = extractSigningShare(keygen1.key_package);
+ if (signingShareRes.success === false) {
+ return {
+ success: false,
+ err: { type: "sign_in_request_fail", error: signingShareRes.err },
+ };
+ }
+
+ // 3. Split signing share for distribution
+ const splitRes = await splitTeddsaSigningShare(
+ signingShareRes.data,
+ keyshareNodeMeta,
+ );
+ if (splitRes.success === false) {
+ return {
+ success: false,
+ err: { type: "sign_in_request_fail", error: splitRes.err },
+ };
+ }
+
+ return {
+ success: true,
+ data: {
+ keygen1,
+ keygen2,
+ userKeyShares: splitRes.data,
+ },
+ };
+}
+
+interface SignInRequestV2 {
+ auth_type: AuthType;
+}
+
+/**
+ * Sign in to API server and return user data.
+ * Used by handlers that need to authenticate before requesting shares.
+ */
+async function signInV2(
+ idToken: string,
+ authType: AuthType,
+): Promise> {
+ const signInRes = await makeAuthorizedOkoApiRequest<
+ SignInRequestV2,
+ SignInResponseV2
+ >("user/signin", idToken, { auth_type: authType }, TSS_V2_ENDPOINT);
+
+ if (!signInRes.success) {
+ console.error("[attached] sign in failed, err: %s", signInRes.err);
+ return {
+ success: false,
+ err: { type: "sign_in_request_fail", error: signInRes.err.toString() },
+ };
+ }
+
+ const apiResponse = signInRes.data;
+ if (!apiResponse.success) {
+ console.error(
+ "[attached] sign in request failed, err: %s",
+ apiResponse.msg,
+ );
+ return {
+ success: false,
+ err: {
+ type: "sign_in_request_fail",
+ error: `code: ${apiResponse.code}`,
+ },
+ };
+ }
+
+ return { success: true, data: apiResponse.data };
+}
diff --git a/internals/ci/src/cmds/deploy.ts b/internals/ci/src/cmds/deploy.ts
index 3ebdd7b7a..a31c8c9e1 100644
--- a/internals/ci/src/cmds/deploy.ts
+++ b/internals/ci/src/cmds/deploy.ts
@@ -30,6 +30,9 @@ const APP_CONFIGS = {
"oko-sandbox-evm": {
path: path.join(paths.root, "sandbox/sandbox_evm"),
},
+ "oko-sandbox-sol": {
+ path: path.join(paths.root, "sandbox/sandbox_sol"),
+ },
};
function listDeployableApps(): void {
diff --git a/internals/docker/oko_api_server.Dockerfile b/internals/docker/oko_api_server.Dockerfile
index cecbcea76..82e685a49 100644
--- a/internals/docker/oko_api_server.Dockerfile
+++ b/internals/docker/oko_api_server.Dockerfile
@@ -71,16 +71,16 @@ RUN yarn workspaces focus @oko-wallet/tecdsa-interface
WORKDIR /home/node/oko/crypto/tecdsa/tecdsa_interface
RUN yarn run build
-# Build teddsa-interface
+# Build oko-types (depends on stdlib-js, bytes, tecdsa-interface)
WORKDIR /home/node/oko
-RUN yarn workspaces focus @oko-wallet/teddsa-interface
-WORKDIR /home/node/oko/crypto/teddsa/teddsa_interface
+RUN yarn workspaces focus @oko-wallet/oko-types
+WORKDIR /home/node/oko/common/oko_types
RUN yarn run build
-# Build oko-types (depends on stdlib-js, tecdsa-interface, teddsa-interface)
+# Build teddsa-interface (depends on bytes, oko-types/teddsa)
WORKDIR /home/node/oko
-RUN yarn workspaces focus @oko-wallet/oko-types
-WORKDIR /home/node/oko/common/oko_types
+RUN yarn workspaces focus @oko-wallet/teddsa-interface
+WORKDIR /home/node/oko/crypto/teddsa/teddsa_interface
RUN yarn run build
# Install dependencies for crypto-js
diff --git a/sandbox/sandbox_sol/src/hooks/use_oko_sol.ts b/sandbox/sandbox_sol/src/hooks/use_oko_sol.ts
index 40a3ed835..7d27701b3 100644
--- a/sandbox/sandbox_sol/src/hooks/use_oko_sol.ts
+++ b/sandbox/sandbox_sol/src/hooks/use_oko_sol.ts
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState, useCallback } from "react";
+import type { PublicKey } from "@solana/web3.js";
import { OkoSolWallet, registerOkoWallet } from "@oko-wallet/oko-sdk-sol";
import { useSdkStore } from "@/store/sdk";
@@ -22,7 +23,9 @@ export function useOkoSol() {
// Initialize SDK
useEffect(() => {
- if (isInitialized || isInitializing) return;
+ if (isInitialized || isInitializing) {
+ return;
+ }
const initSdk = async () => {
setIsInitializing(true);
@@ -63,9 +66,10 @@ export function useOkoSol() {
await solWallet.okoWallet.getPublicKeyEd25519();
if (existingEd25519Pubkey) {
await solWallet.connect();
- const pk = solWallet.publicKey?.toBase58() ?? null;
- setConnected(true, pk);
- console.log("[sandbox_sol] Reconnected:", pk);
+ console.log(
+ "[sandbox_sol] Reconnected:",
+ solWallet.publicKey?.toBase58(),
+ );
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
@@ -83,9 +87,40 @@ export function useOkoSol() {
setOkoWallet,
setOkoSolWallet,
setInitialized,
- setConnected,
]);
+ useEffect(() => {
+ if (!okoSolWallet) {
+ return;
+ }
+
+ const handleAccountChanged = (pk: PublicKey | null) => {
+ const pubkeyStr = pk?.toBase58() ?? null;
+ setConnected(!!pk, pubkeyStr);
+ console.log("[sandbox_sol] accountChanged event:", pubkeyStr);
+ };
+
+ const handleConnect = (pk: PublicKey) => {
+ setConnected(true, pk.toBase58());
+ console.log("[sandbox_sol] connect event:", pk.toBase58());
+ };
+
+ const handleDisconnect = () => {
+ setConnected(false, null);
+ console.log("[sandbox_sol] disconnect event");
+ };
+
+ okoSolWallet.on("accountChanged", handleAccountChanged);
+ okoSolWallet.on("connect", handleConnect);
+ okoSolWallet.on("disconnect", handleDisconnect);
+
+ return () => {
+ okoSolWallet.off("accountChanged", handleAccountChanged);
+ okoSolWallet.off("connect", handleConnect);
+ okoSolWallet.off("disconnect", handleDisconnect);
+ };
+ }, [okoSolWallet, setConnected]);
+
const connect = useCallback(async () => {
if (!okoSolWallet) {
setError("SDK not initialized");
@@ -104,30 +139,32 @@ export function useOkoSol() {
// connect() internally handles Ed25519 key creation if needed
await okoSolWallet.connect();
- const pk = okoSolWallet.publicKey?.toBase58() ?? null;
- setConnected(true, pk);
- console.log("[sandbox_sol] Connected:", pk);
+ console.log(
+ "[sandbox_sol] Connected:",
+ okoSolWallet.publicKey?.toBase58(),
+ );
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(message);
console.error("[sandbox_sol] Failed to connect:", message);
}
- }, [okoSolWallet, setConnected]);
+ }, [okoSolWallet]);
// Disconnect wallet
const disconnect = useCallback(async () => {
- if (!okoSolWallet) return;
+ if (!okoSolWallet) {
+ return;
+ }
try {
await okoSolWallet.disconnect();
- setConnected(false, null);
console.log("[sandbox_sol] Disconnected");
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setError(message);
console.error("[sandbox_sol] Failed to disconnect:", message);
}
- }, [okoSolWallet, setConnected]);
+ }, [okoSolWallet]);
return {
okoWallet,
diff --git a/sandbox/sandbox_sol/src/lib/connection.ts b/sandbox/sandbox_sol/src/lib/connection.ts
index 1d3efd242..5352382fc 100644
--- a/sandbox/sandbox_sol/src/lib/connection.ts
+++ b/sandbox/sandbox_sol/src/lib/connection.ts
@@ -1,6 +1,8 @@
import { Connection } from "@solana/web3.js";
+const DEFAULT_RPC_URL = "https://api.devnet.solana.com";
+
export const DEVNET_CONNECTION = new Connection(
- "https://api.devnet.solana.com",
+ process.env.NEXT_PUBLIC_SOLANA_RPC_URL || DEFAULT_RPC_URL,
"confirmed",
);
diff --git a/sdk/oko_sdk_core/src/methods/sign_in/index.ts b/sdk/oko_sdk_core/src/methods/sign_in/index.ts
index 15c2cd4c6..9baf573cd 100644
--- a/sdk/oko_sdk_core/src/methods/sign_in/index.ts
+++ b/sdk/oko_sdk_core/src/methods/sign_in/index.ts
@@ -33,7 +33,6 @@ export async function signIn(this: OkoWalletInterface, type: SignInType) {
throw new Error(`Sign in error, err: ${err}`);
}
- // @TODO: required for ed25519 support
const walletInfo = await this.getWalletInfo();
if (!walletInfo) {