@femdev/klyde-sdk
v0.1.0
Published
Klyde framework-agnostic SDK.
Readme
@klyde/sdk
Framework-agnostic public TypeScript SDK.
First Public Surface
createKlydeloginauth.startEmailVerificationauth.verifyEmailauth.startPhoneVerificationauth.verifyPhoneauth.loginWithSocialTokenauth.startPasskeyRegistrationauth.verifyPasskeyRegistrationauth.startPasskeyLoginauth.verifyPasskeyLoginauth.refreshSessionauth.bootstrapHostAppSessionauth.logoutauth.listDevicesgetUsergetWalletgetWalletSnapshotgetBalanceslistRecentRecipientsquoteDepositdepositgetDepositquoteWithdrawalwithdrawgetWithdrawalquoteConversionexecuteConversiongetFundingInstructionssimulateFundingReceivedlocal/sandbox testing onlygetReceiptlistReceiptslistTransactions
The SDK should expose rails, not third-party app business actions.
Example
import { createKlyde } from "@klyde/sdk";
const klyde = createKlyde({
appId: "app_123",
environment: "sandbox",
market: "NG",
});
await klyde.login({
method: "email",
email: "[email protected]",
});
// login stores the returned session token and hydrates klyde.wallets
// when the API returns an embedded wallet.
const connection = klyde.wallets.getConnection();
const quote = await klyde.quoteDeposit({
amount: {
display: "10000",
currency: "NGN",
},
});
const deposit = await klyde.deposit(
{
quoteId: quote.id,
source: {
type: "bank_transfer",
},
},
{
idempotencyKey: "deposit_001",
},
);
const snapshot = await klyde.getWalletSnapshot();
const valuedSnapshot = await klyde.getWalletSnapshot({ baseCurrency: "USD" });
const balances = await klyde.getBalances();
const history = await klyde.listTransactions({ page: 1, pageSize: 25 });
const recipients = await klyde.listRecentRecipients({ limit: 10 });
const funding = await klyde.getFundingInstructions({ currency: "NGN" });
const usdcFunding = await klyde.getFundingInstructions({
currency: "USDC",
rail: "wallet",
chainId: 31337,
});
// usdcFunding.rail === "wallet"
// Show usdcFunding.address and usdcFunding.qrPayload in local/sandbox stablecoin funding screens.
// Local/sandbox helper for demo apps when provider sandbox cannot actually fund a virtual account.
// The Klyde API rejects this in production.
await klyde.simulateFundingReceived({
amount: { display: "1000", currency: "NGN", minorUnits: "100000", scale: 2 },
provider: "flutterwave",
});
const conversionQuote = await klyde.quoteConversion({
amount: { display: "1500", currency: "NGN", minorUnits: "150000", scale: 2 },
targetCurrency: "USDC",
});
const conversion = await klyde.executeConversion({ quoteId: conversionQuote.id });For local stablecoin funding, prefer the real onchain path over the simulator:
KLYDE_FUND_TOKEN=USDC \
KLYDE_FUND_AMOUNT=10 \
KLYDE_FUND_TO=<address from usdcFunding.address> \
npm run fund:stablecoinWith KLYDE_STABLECOIN_DEPOSIT_MONITOR_ENABLED=true, the worker watches the mock USDC/USDT
Transfer event, creates the normal Klyde deposit and receipt, finalizes after confirmations, and
the app can poll listTransactions() or getWalletSnapshot().
snapshot.valuation is intentionally explicit. When Klyde has a configured FX provider, it returns an
indicative base-currency total with provider/rate metadata. When no real rate is available, it returns
status: "unavailable" instead of adding NGN, USDC, and USDT blindly.
Auth Helpers
Apps should use the SDK helpers instead of calling Klyde auth endpoints directly:
const challenge = await klyde.auth.startEmailVerification({
email: "[email protected]",
});
const result = await klyde.auth.verifyEmail({
email: "[email protected]",
challengeId: challenge.challengeId,
code: "123456",
});
// verifyEmail stores the returned session token and hydrates klyde.wallets
// when the API returns an embedded wallet.
console.log(result.user.id, result.wallet?.address);For social sign-in, the app obtains the Google/Apple ID token in its frontend, then gives it to Klyde:
await klyde.auth.loginWithSocialToken({
provider: "google",
token: googleIdToken,
});For refresh-token rotation:
const refreshed = await klyde.auth.refreshSession({
refreshToken,
deviceName: "Chrome",
});For embedded apps, persist Klyde tokens in the host app's secure storage and pass a sessionStore.
When a money request receives an expired session response, the SDK will call /v1/sessions/refresh,
persist the rotated tokens, and retry the original request once.
const klyde = createKlyde({
appId: "app_paayee_local",
environment: "local",
apiUrl: "http://localhost:4000",
sessionStore: {
getSessionToken: () => secureStore.get("klyde_session"),
setSessionToken: (token) => secureStore.set("klyde_session", token),
getRefreshToken: () => secureStore.get("klyde_refresh"),
setRefreshToken: (token) => secureStore.set("klyde_refresh", token),
},
});If the refresh token is missing, expired, or revoked, the host app backend can mint a short-lived host assertion for a known linked user. The mobile app passes that assertion to Klyde and receives a normal Klyde session plus refresh token, without asking the user for another email OTP during a money operation.
await klyde.auth.bootstrapHostAppSession({
assertion,
deviceName: "Paayee iPhone",
});Design Rules
- Keep this package framework-agnostic.
- Keep provider details behind the API and adapter packages.
- Keep business verbs like save, contribute, buy, lend, and stake outside the SDK.
- Use idempotency keys for rail-creating calls.
Wallet Protocol Surface
The SDK exposes a lightweight wallet manager:
klyde.wallets.registerAdapter(adapter);
const connection = await klyde.wallets.connect("embedded", {
appId: "app_123",
userId: "user_123",
chainId: 84532,
});Protocol types are re-exported from @klyde/protocols so application code can use one SDK import for wallet adapters, EIP-1193, EIP-712, SIWE, FHEVM, and ERC-7984 request shapes.
The wallet manager is backed by the protocol runtime, so it keeps an active session, adapter id, protocol family, capabilities, and connection state together.
It can also create or install a Klyde EIP-1193 provider:
const provider = klyde.wallets.createEthereumProvider({
defaultAdapterId: "embedded",
});
await provider.request({ method: "eth_requestAccounts" });
await klyde.wallets.sendTransaction({
to: "0x0000000000000000000000000000000000000001",
value: "0x1",
});For browser discovery, Klyde can announce itself through EIP-6963:
const injection = klyde.wallets.installEthereumProvider({
exposeLegacyEthereum: false,
});
// later
injection.cleanup();Privacy Surface
Configure FHEVM once when you need low-level encrypted input preparation:
klyde.privacy.configureFhevm(fhevmRelayer, confidentialTokenCallBuilder);
const transfer = await klyde.privacy.prepareConfidentialTransfer({
token: cUSDC,
from: userAddress,
to: merchantAddress,
amount: "1000000",
});For live confidential token UX, configure a higher-level privacy token engine. The first production-shaped adapter is ZamaSdkPrivacyAdapter from @klyde/adapters, which wraps an initialized @zama-fhe/sdk instance:
klyde.privacy.configurePrivacyTokenEngine(zamaSdkPrivacyAdapter);
await klyde.privacy.shieldToken({
token: cUSDC,
amount: "1000000",
});
await klyde.privacy.transferPrivately({
token: cUSDC,
to: recipientAddress,
amount: "500000",
});For Klyde-managed confidential stablecoin flows, prefer the client helpers. They resolve the registered confidential asset, create a tracked Klyde privacy operation, execute through the configured privacy token engine, then mark the operation submitted/completed:
await klyde.shieldConfidentialToken({
userId,
chainId: 84532,
symbol: "cUSDC",
toAddress: userWalletAddress,
amount: {
currency: "USDC",
minorUnits: "1000000",
scale: 6,
},
});
await klyde.transferConfidentialToken({
userId,
chainId: 84532,
symbol: "cUSDC",
toAddress: recipientAddress,
amount: {
currency: "USDC",
minorUnits: "500000",
scale: 6,
},
});
await klyde.unshieldConfidentialToken({
userId,
chainId: 84532,
symbol: "cUSDC",
amount: {
currency: "USDC",
minorUnits: "250000",
scale: 6,
},
});When settlement auto-shield is enabled, the worker can prepare the confidential deposit operation after an inbound deposit settles. The app can then fetch and execute that prepared operation without creating a duplicate:
const [operation] = await klyde.listConfidentialOperations({ page: 1, pageSize: 10 });
if (operation?.status === "prepared" && operation.metadata.autoShield) {
await klyde.executePreparedConfidentialOperation({
operation,
approvalStrategy: "exact",
});
}
const balance = await klyde.getConfidentialBalance({
chainId: 31337,
symbol: "cUSDC",
owner: userWalletAddress,
});For controlled auto-execution, first create a user consent policy with allowlists and limits. The worker will only auto-execute prepared shield operations when this policy passes:
await klyde.createAutoShieldPolicy({
userId,
consentReference: "consent_v1_2026_06_04",
operatorAddress: klydeOperatorAddress,
allowedChainIds: [31337],
allowedUnderlyingSymbols: ["USDC"],
singleLimitMinorUnits: "10000000",
dailyLimitMinorUnits: "50000000",
currency: "USDC",
});Keep KLYDE_AUTO_SHIELD_EXECUTION_ENABLED=false until the app has explicit user consent and the worker signer is safe
for the target environment.
Advanced apps can bring their own confidential contracts while still using Klyde for wallet, rails, asset lookup, and operation tracking. Configure the app privacy mode in Klyde, register the app's confidential asset, then prepare a confidential operation without forcing a mode:
await klyde.prepareConfidentialOperation({
userId,
chainId: 84532,
kind: "app_managed",
underlyingSymbol: "USDC",
amountReference: {
amountCommitment: "0x...",
metadataEnvelopeId: "env_...",
operationPublicLabel: "Private USDC transfer",
},
});Klyde resolves the active rail from the app privacy config. Klyde records operation status and asset references, but the app owns the custom contract call and private business logic.
For a local Hardhat proof with Zama cleartext mode, deploy the local token rails, keep the node running, then run:
npm run test:privacy:localThe SDK still keeps actual Zama SDK, Zama Relayer SDK, ethers, or viem implementations behind ports, so apps can choose their runtime without changing Klyde protocol logic.
