{"id":13350,"date":"2026-02-03T15:09:53","date_gmt":"2026-02-03T15:09:53","guid":{"rendered":"https:\/\/cheesecakelabs.com\/blog\/"},"modified":"2026-03-17T13:33:23","modified_gmt":"2026-03-17T13:33:23","slug":"building-a-passkey-enabled-smart-wallet-on-the-stellar-network","status":"publish","type":"post","link":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/","title":{"rendered":"Building a Passkey-Enabled Smart Wallet on the Stellar Network"},"content":{"rendered":"\n<p>Traditional blockchain wallets require users to manage private keys \u2014 a major barrier to mainstream adoption. Lost keys mean lost funds, and many security incidents originate from poor key-handling practices.<\/p>\n\n\n\n<p>In this article, we demonstrate how to build a <strong>smart wallet powered by passkeys (WebAuthn)<\/strong> for authentication and transaction signing on the <strong><a href=\"https:\/\/cheesecakelabs.com\/blog\/how-to-use-stellar-soroban-to-write-a-bond-smart-contract\/\" target=\"_blank\" rel=\"noreferrer noopener\">Stellar\/Soroban<\/a><\/strong> blockchain. By leveraging biometric authentication (Touch ID, Face ID, Windows Hello), we enable a seamless and secure <strong>user experience <\/strong>\u2014 without user-managed private keys.<\/p>\n\n\n\n<p>Cryptographic key material still exists, but is generated, stored, and used exclusively inside the authenticator\u2019s secure hardware and never exposed to the user or backend.<\/p>\n\n\n\n<p>This article is aimed at developers already familiar with at least one of the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>WebAuthn \/ Passkeys<\/li>\n\n\n\n<li>Smart contracts<\/li>\n\n\n\n<li>Stellar \/ Soroban (at a conceptual level)<\/li>\n<\/ul>\n\n\n\n<p>It is not a beginner tutorial, but a technical walkthrough of an existing implementation, focusing on:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Architecture decisions<\/li>\n\n\n\n<li>Security trade-offs<\/li>\n\n\n\n<li>How WebAuthn and Soroban are wired together in practice<\/li>\n<\/ul>\n\n\n\n<p>A runnable PoC (Proof-of-Concept) is provided in the repository for those who want to explore the code further. The complete PoC is available on <a href=\"https:\/\/github.com\/CheesecakeLabs\/soroban-smart-wallet-poc\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites &amp; project setup<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1. Problem and Goal<\/strong><\/h3>\n\n\n\n<p>The main goal of this architecture is to create a smart wallet that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Eliminates Private Keys:<\/strong> The user never interacts with or stores a private key. Authentication and signatures are handled by a Passkey (WebAuthn), which uses the device&#8217;s secure hardware.<\/li>\n\n\n\n<li><strong>Delegates Signature: <\/strong>The smart wallet contract is ultimately responsible for verifying the WebAuthn signature and authorizing transactions on Soroban.<\/li>\n\n\n\n<li><strong>Ensures Secure Recovery:<\/strong> A recovery mechanism controlled by the backend allows Passkey rotation in case of lost access.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Threat Model (At a Glance)<\/strong><\/h3>\n\n\n\n<p>This PoC assumes a semi-trusted backend that sponsors fees and controls recovery. Attackers are assumed to have network access but not control of the user device&#8217;s secure enclave. Compromise of the backend recovery key is considered catastrophic and intentionally highlighted as a non-production trade-off. This helps security reviewers instantly calibrate their reading.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2. High-Level Architecture<\/strong><\/h2>\n\n\n\n<p>The PoC is divided into three main components, coordinated by the WebAuthn flow:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Component<\/strong><\/td><td><strong>Description<\/strong><\/td><\/tr><tr><td>Backend<\/td><td>Express.js API for WebAuthn and Soroban interactions.<\/td><\/tr><tr><td>Web<\/td><td>React frontend for wallet operations.<\/td><\/tr><tr><td>Contracts<\/td><td>Soroban smart contracts (Factory + Smart Wallet).<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>TL;DR Architecture Summary<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Users never see or manage private keys<\/li>\n\n\n\n<li>Passkeys sign WebAuthn challenges<\/li>\n\n\n\n<li>Challenges are bound to Soroban transactions<\/li>\n\n\n\n<li>Smart contracts verify signatures on-chain<\/li>\n\n\n\n<li>Backend sponsors fees and enables recovery<\/li>\n<\/ul>\n\n\n\n<p>Before diving into the individual flows, it\u2019s useful to understand the common authorization pattern shared by wallet creation, transfers, and recovery:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img decoding=\"async\" width=\"512\" height=\"289\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/High-level-authorization-flow.png\" alt=\"Figure 1 - High-level authorization flow:\nAll user actions follow the same pattern: a backend-generated challenge is signed via WebAuthn and later used to authorize a Soroban transaction.\n\" class=\"wp-image-13385\" style=\"aspect-ratio:1.7716680183833469;width:629px;height:auto\"\/><figcaption class=\"wp-element-caption\">Figure 1 &#8211; <strong>High-level authorization flow:<\/strong><br>All user actions follow the same pattern: a backend-generated challenge is signed via WebAuthn and later used to authorize a Soroban transaction.<\/figcaption><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Real-World Usage<\/strong><\/h3>\n\n\n\n<p>The authorization model presented here is not theoretical. The same core architecture \u2014 passkey-based authorization bound to Soroban transactions \u2014 is used in production in <a href=\"https:\/\/www.linkedin.com\/feed\/update\/urn:li:activity:7376730934466842624\/\" target=\"_blank\" rel=\"noreferrer noopener\">Meridian Pay<\/a>. This proof of concept intentionally simplifies certain aspects (such as recovery and fee sponsoring) to focus on the core design.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Read more:<\/strong> <a href=\"https:\/\/cheesecakelabs.com\/portfolio\/moneygram-wallet\/\" target=\"_blank\" rel=\"noreferrer noopener\">Building a Global Non-Custodial Wallet on Stellar for Cross-Border Payments.<br>Arrow button<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Component Responsibilities<\/strong><\/h3>\n\n\n\n<p><strong>Backend (API Server)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Generates and validates WebAuthn challenges<\/li>\n\n\n\n<li>Stores user and passkey data (SQLite)<\/li>\n\n\n\n<li>Interacts with Soroban RPC<\/li>\n\n\n\n<li>Sponsors transaction fees<\/li>\n<\/ul>\n\n\n\n<p><strong>Web (Frontend)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wallet UI<\/li>\n\n\n\n<li>Initiates WebAuthn browser flows<\/li>\n\n\n\n<li>Communicates with backend<\/li>\n<\/ul>\n\n\n\n<p><strong>Smart Contracts<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Factory Contract<\/strong>: Deploys wallets with deterministic addresses<\/li>\n\n\n\n<li><strong>Smart Wallet Contract: <\/strong>Verifies WebAuthn signatures and supports signer rotation<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3. Core Design Decisions<\/strong><\/h2>\n\n\n\n<p>This architecture was built based on specific design decisions to integrate WebAuthn and Soroban:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Cryptographic Authentication (Passkeys):<\/strong> Instead of using External Owned Account (EOA) keys or traditional Stellar key pairs, we use Passkeys that leverage cryptographic curves supported by Soroban (like<strong> ES256\/secp256r1<\/strong>) for on-chain signatures.<\/li>\n\n\n\n<li><strong>Fee Sponsoring: <\/strong>The <mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\"><strong>OPEX_WALLET<\/strong><\/mark> in the backend pays all transaction fees (Gas Abstraction) to provide a frictionless user experience.<\/li>\n\n\n\n<li><strong>Centralized Recovery Account (PoC):<\/strong> For the proof-of-concept, the backend controls a<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">RECOVERY_WALLET<\/mark><\/strong><br>that is configured as a backup signer. This allows Passkey rotation in case of loss, but is a security consideration (see  <strong><a href=\"#security-trust-model\" type=\"internal\" id=\"#security-trust-model\">Security &amp; Trust Model<\/a><\/strong>).<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em>\u26a0\ufe0f This PoC intentionally centralizes some responsibilities (fee sponsoring and recovery) to simplify UX. These trade-offs are discussed in detail in the <\/em><strong><em><a href=\"#security-trust-model\" type=\"internal\" id=\"#security-trust-model\">Security &amp; Trust Model<\/a><\/em><\/strong><em> section.<\/em><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Why WebAuthn Requires a Two-Endpoint Backend<\/strong><\/h3>\n\n\n\n<p>The WebAuthn protocol requires a <strong>challenge\u2013response model<\/strong> to ensure security. This leads to a two-endpoint pattern for any operation requiring authentication:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Options Endpoints:<\/strong> The server generates and stores a cryptographic challenge that is sent to the client.<\/li>\n\n\n\n<li><strong>Action Endpoints:<\/strong> The client sends the cryptographic response (signed by the Passkey) back to the server, which validates it against the stored challenge.<\/li>\n<\/ul>\n\n\n\n<p>This split guarantees:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Server-generated challenges: <\/strong>Prevents replay attacks.<\/li>\n\n\n\n<li><strong>Challenge storage: <\/strong>The server must store the challenge to validate the response.<\/li>\n\n\n\n<li><strong>Response validation:<\/strong> Cryptographic verification must happen server-side.<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Pattern<\/strong><\/td><td><strong>Options Endpoint<\/strong><\/td><td><strong>Action Endpoint<\/strong><\/td><\/tr><tr><td>Wallet Creation<\/td><td>GET \/api\/create-wallet-options\/:email<\/td><td>POST \/api\/create-wallet<\/td><\/tr><tr><td>Transfer<\/td><td>GET \/api\/transfer-options<\/td><td>POST \/api\/transfer<\/td><\/tr><tr><td>Sign In<\/td><td>GET \/api\/sign-in-options\/:email<\/td><td>POST \/api\/sign-in<\/td><\/tr><tr><td>Wallet Recovery<\/td><td>GET \/api\/recover-wallet-options\/:email<\/td><td>POST \/api\/recover-wallet<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4. Core Flows<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Wallet Creation Flow<\/strong><\/h3>\n\n\n\n<p>Wallet creation is the only moment where a new cryptographic identity is introduced into the system. From this point on, the Passkey becomes the wallet\u2019s primary signer, fundamentally shifting wallet ownership away from a private key.<\/p>\n\n\n\n<p>Wallet creation uses the WebAuthn <strong>registration<\/strong> flow to create a new Passkey and deploy the Smart Wallet.<\/p>\n\n\n\n<p><strong>Read more: <\/strong><a href=\"https:\/\/cheesecakelabs.com\/blog\/custodial-vs-non-custodial-wallets\/\" target=\"_blank\" rel=\"noreferrer noopener\">Custodial vs. Non-Custodial Wallets: Key Differences and How to Choose<\/a><\/p>\n\n\n\n<p><strong>Overview (Three Steps)<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <strong>Backend<\/strong> generates registration options with a <strong>random<\/strong> challenge and stores it.<\/li>\n\n\n\n<li>The <strong>Frontend<\/strong> uses these options to initiate Passkey creation in the browser (triggering the biometric prompt).<\/li>\n\n\n\n<li>The <strong>Backend<\/strong> validates the response, stores the Passkey credentials in the DB, and deploys the Smart Wallet on Soroban via the Factory Contract.<\/li>\n<\/ol>\n\n\n\n<p><strong>Key Code: Generating Options (Backend)<\/strong><\/p>\n\n\n\n<p>The core of the registration is to generate a random challenge (for session authentication) and configure the Passkey to use the ES256 algorithm (supported by Soroban) and require userVerification (biometric\/PIN).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ apps\/backend\/src\/helpers\/webauthn\/registration\/index.ts<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> generateOptions(input: WebAuthnRegistrationGenerateOptionsInput): <span class=\"hljs-built_in\">Promise<\/span>&lt;string&gt; {\n  <span class=\"hljs-comment\">\/\/ ... (setup code)<\/span>\n\n  <span class=\"hljs-keyword\">const<\/span> challenge = <span class=\"hljs-keyword\">this<\/span>.webauthnChallengeService.createChallenge();\n  \n  <span class=\"hljs-keyword\">const<\/span> options = <span class=\"hljs-keyword\">await<\/span> generateRegistrationOptions({\n    <span class=\"hljs-comment\">\/\/ ... (rpName, rpID, userID, userName, userDisplayName)<\/span>\n    <span class=\"hljs-attr\">challenge<\/span>: challenge,\n    <span class=\"hljs-attr\">supportedAlgorithmIDs<\/span>: &#91;<span class=\"hljs-number\">-7<\/span>], <span class=\"hljs-comment\">\/\/ ES256 (secp256r1) which Soroban supports natively<\/span>\n    <span class=\"hljs-attr\">attestationType<\/span>: <span class=\"hljs-string\">\"none\"<\/span>,\n    <span class=\"hljs-comment\">\/\/ ... (excludeCredentials, timeout)<\/span>\n    <span class=\"hljs-attr\">authenticatorSelection<\/span>: {\n      <span class=\"hljs-attr\">authenticatorAttachment<\/span>: <span class=\"hljs-string\">\"platform\"<\/span>,\n      <span class=\"hljs-attr\">residentKey<\/span>: <span class=\"hljs-string\">\"preferred\"<\/span>,\n      <span class=\"hljs-attr\">userVerification<\/span>: <span class=\"hljs-string\">\"required\"<\/span>, <span class=\"hljs-comment\">\/\/ Ensures biometric\/PIN authentication<\/span>\n    },\n  });\n\n  <span class=\"hljs-comment\">\/\/ Store challenge linked to user identifier for later validation<\/span>\n  <span class=\"hljs-keyword\">this<\/span>.webauthnChallengeService.storeChallenge(identifier, options.challenge);\n  \n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">JSON<\/span>.stringify(options);\n} <\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Key Code: Validation and Deployment (Backend)<\/strong><\/p>\n\n\n\n<p>After successful Passkey validation, the backend derives a deterministic salt and calls the<\/p>\n\n\n\n<p><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">deploy<\/mark><\/strong><\/p>\n\n\n\n<p>method of the Factory Contract, passing the new Passkey&#8217;s public key as the initial signer and the<\/p>\n\n\n\n<p><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">RECOVERY_WALLET<\/mark><\/strong><\/p>\n\n\n\n<p>&#8216;s public key.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ apps\/backend\/src\/api\/create-wallet.ts<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> handle(payload: { <span class=\"hljs-attr\">email<\/span>: string; registrationResponseJSON: string }) {\n  <span class=\"hljs-comment\">\/\/ 1. Verify challenge resolution &amp; create user\/store passkey (similar to previous steps)<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/\/ 3. Derive deterministic salt from email<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/\/ 4. Deploy smart wallet via factory<\/span>\n  <span class=\"hljs-keyword\">const<\/span> args: xdr.ScVal&#91;] = &#91;\n    ScConvert.bufferToScVal(hashedSalt),\n    ScConvert.addressToScVal(<span class=\"hljs-keyword\">this<\/span>.recoveryWalletPublicKey),\n    ScConvert.hexPublicKeyToScVal(newPasskey.credentialHexPublicKey),\n  ];\n  \n  <span class=\"hljs-keyword\">const<\/span> { tx } = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.sorobanService.simulateContractOperation({\n    <span class=\"hljs-attr\">contractId<\/span>: <span class=\"hljs-keyword\">this<\/span>.factoryContractId,\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"deploy\"<\/span>,\n    args,\n  });\n  \n  <span class=\"hljs-comment\">\/\/ 5. Compute and store wallet address (remaining steps)<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n} \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><strong>Binding Passkey Signatures to On-Chain Transactions (Transfer Flow)<\/strong><\/h3>\n\n\n\n<p>The transfer flow uses WebAuthn <strong>authentication<\/strong> to sign a blockchain transaction.<\/p>\n\n\n\n<p><strong>Critical Difference: TX-Derived Challenge<\/strong><\/p>\n\n\n\n<p>WebAuthn is used here to authorize a specific action (the transfer), not just to authenticate a session. That&#8217;s why the WebAuthn challenge <strong>cannot be random<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>CRITICAL: <\/strong>The challenge must be derived from the Soroban transaction simulation. This ensures the Passkey signature is <strong>bound<\/strong> to the exact transaction being authorized.<\/li>\n<\/ul>\n\n\n\n<p><strong>Key Code: Challenge Generation (Backend)<\/strong><\/p>\n\n\n\n<p>The important part here is not the Soroban boilerplate, but how the WebAuthn challenge is generated and attached to the transaction. The transaction simulation is performed first to obtain data that will be used to generate the WebAuthn challenge.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ apps\/backend\/src\/api\/transfer-options.ts<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> handle(payload: { <span class=\"hljs-attr\">fromWalletAddress<\/span>: string; toWalletAddress: string; amount: number }) {\n  <span class=\"hljs-comment\">\/\/ 1. Build transfer transaction args &amp; simulate the transaction<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/\/ 3. Generate challenge from TX simulation (CRITICAL!)<\/span>\n  <span class=\"hljs-keyword\">const<\/span> challenge = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.sorobanService.generateWebAuthnChallenge({\n    <span class=\"hljs-attr\">contractId<\/span>: <span class=\"hljs-keyword\">this<\/span>.nativeTokenContractId,\n    simulationResponse,\n    <span class=\"hljs-attr\">signer<\/span>: { <span class=\"hljs-attr\">addressId<\/span>: user.walletContractAddress! },\n  });\n  \n  <span class=\"hljs-comment\">\/\/ 4. Generate authentication options with TX-linked challenge<\/span>\n  <span class=\"hljs-keyword\">const<\/span> options = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.webauthnAuthenticationHelper.generateOptions({\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    <span class=\"hljs-attr\">customChallenge<\/span>: challenge, <span class=\"hljs-comment\">\/\/ The TX-derived challenge is passed here<\/span>\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n  });\n  \n  <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">options_json<\/span>: options };\n} \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Key Code: Validation and Submission (Backend)<\/strong><\/p>\n\n\n\n<p>After successful authentication (the Passkey signature), the backend attaches this WebAuthn signature to the Soroban transaction as an authorization entry (Auth Entry) and submits it to the network.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ apps\/backend\/src\/api\/transfer.ts<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> handle(payload: { <span class=\"hljs-comment\">\/* ... *\/<\/span> }) {\n  <span class=\"hljs-comment\">\/\/ 1. Verify authentication challenge (successful validation guarantees the signature is valid for the TX-derived challenge)<\/span>\n  <span class=\"hljs-keyword\">const<\/span> verifyAuth = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.webauthnAuthenticationHelper.complete({ <span class=\"hljs-comment\">\/* ... *\/<\/span> });\n\n  <span class=\"hljs-comment\">\/\/ 2. Retrieve TX metadata stored during options generation<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n  <span class=\"hljs-comment\">\/\/ 3. Build contract signer with passkey signature<\/span>\n  <span class=\"hljs-keyword\">const<\/span> passkeySigner: ContractSigner = {\n    <span class=\"hljs-attr\">addressId<\/span>: user.walletContractAddress!,\n    <span class=\"hljs-attr\">methodOptions<\/span>: {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"webauthn\"<\/span>,\n      <span class=\"hljs-attr\">options<\/span>: {\n        <span class=\"hljs-attr\">clientDataJSON<\/span>: verifyAuth.clientDataJSON,\n        <span class=\"hljs-attr\">authenticatorData<\/span>: verifyAuth.authenticatorData,\n        <span class=\"hljs-attr\">signature<\/span>: verifyAuth.compactSignature,\n      },\n    },\n  };\n\n  <span class=\"hljs-comment\">\/\/ 4. Sign auth entries with passkey signature<\/span>\n  <span class=\"hljs-keyword\">const<\/span> tx = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.sorobanService.signAuthEntries({\n    <span class=\"hljs-attr\">contractId<\/span>: <span class=\"hljs-keyword\">this<\/span>.nativeTokenContractId,\n    <span class=\"hljs-attr\">tx<\/span>: customMetadata.tx,\n    <span class=\"hljs-attr\">simulationResponse<\/span>: customMetadata.simulationResponse,\n    <span class=\"hljs-attr\">signers<\/span>: &#91;passkeySigner], <span class=\"hljs-comment\">\/\/ The Passkey signature is attached here<\/span>\n  });\n\n  <span class=\"hljs-comment\">\/\/ 5. Re-simulate, prepare, sign with source account, and submit<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n} <\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\"><strong>Sign-In Flow Off-Chain<\/strong><\/h3>\n\n\n\n<p>The sign-in flow authenticates users using their existing Passkeys <strong>without any on-chain interaction<\/strong>. This is purely for session authentication, unlike the Transfer flow, which authorizes a specific transaction.<\/p>\n\n\n\n<p><strong>Flow Comparison<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Aspect<\/td><td>Sign-In<\/td><td>Transfer<\/td><\/tr><tr><td>Challenge<\/td><td>Random server-generated<\/td><td>TX-derived<\/td><\/tr><tr><td>On-chain<\/td><td>No<\/td><td>Yes<\/td><\/tr><tr><td>Purpose<\/td><td>Session auth<\/td><td>TX authorization<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>Implementation<\/strong><\/p>\n\n\n\n<p>The Sign-In flow reuses the same WebAuthn authentication helper (<strong>webauthnAuthenticationHelper<\/strong>) used for transfers, but with a random challenge (standard type) instead of a transaction-derived one.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Read more: <\/strong><a href=\"https:\/\/cheesecakelabs.com\/blog\/ux-in-blockchain-web-3\/\" target=\"_blank\" rel=\"noreferrer noopener\">The Role of User Experience in Blockchain and Web3 Adoption<\/a><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Wallet Recovery Flow<\/h2>\n\n\n\n<p>The recovery flow enables users who have lost access to their original Passkey to regain wallet control by registering a <strong>new Passkey<\/strong> via the <strong>Recovery Account<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How Recovery Works<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The wallet stores two signers: the user&#8217;s Passkey and the Recovery Account.<\/li>\n\n\n\n<li>The Recovery Account (controlled by the backend) is authorized to call the<br>rotate_signer<br>method on the wallet contract.<\/li>\n<\/ol>\n\n\n\n<p><strong>Flow Comparison<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Aspect<\/td><td>Wallet Creation<\/td><td>Wallet Recovery<\/td><\/tr><tr><td>WebAuthn Flow<\/td><td>Registration<\/td><td>Registration<\/td><\/tr><tr><td>On-chain Action<\/td><td>Deploy contract<\/td><td>Rotate signer<\/td><\/tr><tr><td>Signer<\/td><td>User\u2019s passkey<\/td><td>Recovery account<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>Smart Contract: rotate_signer<\/strong><\/p>\n\n\n\n<p>The critical constraint is the check that only the configured recovery account can call the method.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ contracts\/smart-wallet\/src\/lib.rs<\/span>\n\nimpl Recovery <span class=\"hljs-keyword\">for<\/span> AccountContract {\n    fn rotate_signer(env: Env, <span class=\"hljs-attr\">new_signer<\/span>: BytesN&lt;<span class=\"hljs-number\">65<\/span>&gt;) -&gt; Result&lt;(), RecoveryError&gt; {\n        <span class=\"hljs-comment\">\/\/ Only the recovery account can call this method<\/span>\n        <span class=\"hljs-keyword\">let<\/span> recovery = env\n            <span class=\"hljs-comment\">\/\/ ...<\/span>\n        recovery.require_auth();\n  \n        <span class=\"hljs-comment\">\/\/ Replace the signer with the new passkey public key<\/span>\n        env.storage().instance().set(&amp;DataKey::Signer, &amp;new_signer);\n  \n        Ok(())\n    }\n} \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Key Code: Signer Rotation (Backend)<\/strong><\/p>\n\n\n\n<p>The backend validates the new Passkey created by the user, and then uses the secret key of the recovery account (<strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">RECOVERY_WALLET_SECRET_KEY<\/mark><\/strong>) to sign and submit the <strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">rotate_signer<\/mark><\/strong> transaction.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ apps\/backend\/src\/api\/recover-wallet.ts<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> handle(payload: { <span class=\"hljs-attr\">email<\/span>: string; registrationResponseJSON: string }) {\n  <span class=\"hljs-comment\">\/\/ 1. Validate new passkey registration &amp; Store the new passkey in the database<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n  \n  <span class=\"hljs-comment\">\/\/ 3. Prepare rotate_signer transaction<\/span>\n  <span class=\"hljs-keyword\">const<\/span> args: xdr.ScVal&#91;] = &#91;\n    ScConvert.hexPublicKeyToScVal(newPasskey.credentialHexPublicKey),\n  ];\n  \n  <span class=\"hljs-comment\">\/\/ 4. Recovery account signs the transaction (not the user!)<\/span>\n  <span class=\"hljs-keyword\">const<\/span> signers: ContractSigner&#91;] = &#91;\n    {\n      <span class=\"hljs-attr\">addressId<\/span>: <span class=\"hljs-keyword\">this<\/span>.recoveryWalletKeypair.publicKey(),\n      <span class=\"hljs-attr\">methodOptions<\/span>: {\n        <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"keypair\"<\/span>,\n        <span class=\"hljs-attr\">options<\/span>: {\n          <span class=\"hljs-attr\">secret<\/span>: <span class=\"hljs-keyword\">this<\/span>.recoveryWalletKeypair.secret(),\n        },\n      },\n    },\n  ];\n  \n  <span class=\"hljs-comment\">\/\/ 5. Simulate and execute the transaction<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { tx } = <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.sorobanService.simulateContractOperation({\n    <span class=\"hljs-attr\">contractId<\/span>: user.walletContractAddress, <span class=\"hljs-comment\">\/\/ Call the user's wallet contract<\/span>\n    <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"rotate_signer\"<\/span>,\n    args,\n    signers,\n  });\n  \n  <span class=\"hljs-comment\">\/\/ ... (remaining steps for preparing\/signing\/sending TX)<\/span>\n} \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"security-trust-model\"><strong>5. Security &amp; Trust Model<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Aspect<\/strong><\/td><td><strong>Implication<\/strong><\/td><\/tr><tr><td>Recovery account is centralized<\/td><td>The backend controls the recovery key. In production, consider multi-sig or time-locked recovery.<\/td><\/tr><tr><td>User identity verification<\/td><td>This PoC only requires email. Production systems should add additional verification (KYC, security questions, etc.).<\/td><\/tr><tr><td>Old passkey invalidation<\/td><td>Once rotated, the old passkey cannot sign transactions, preventing malicious use of compromised credentials.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>Security Guarantees<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Passkey security:<\/strong> Private key never leaves the user&#8217;s device.<\/li>\n\n\n\n<li><strong>Biometric verification:<\/strong> User presence is required for each operation.<\/li>\n\n\n\n<li><strong>Challenge binding:<\/strong> Signatures are tied to specific operations (TX-derived challenge).<\/li>\n\n\n\n<li><strong>On-chain verification: <\/strong>The Smart Contract validates WebAuthn signatures.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6. Running and Extending the PoC <\/strong><strong>(Optional)<\/strong><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>PoC Installation and Setup&nbsp;<\/strong><\/h3>\n\n\n\n<p>This section contains all the prerequisites and configurations to run the project locally.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Requirements<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>For Node.js Applications<\/strong><\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Node.js 24 LTS or higher<\/li>\n\n\n\n<li>npm 10.x or higher<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>For Smart Contracts<\/strong><\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rust (via rustup)<\/li>\n\n\n\n<li><a href=\"https:\/\/developers.stellar.org\/docs\/tools\/cli\/install-cli\" target=\"_blank\" rel=\"noreferrer noopener\">Stellar CLI<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Project structure<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">soroban-smart-wallet-poc\/\n\n\u251c\u2500\u2500 apps\/\n\u2502   \u251c\u2500\u2500 backend\/      <span class=\"hljs-comment\"># Express.js API server<\/span>\n\u2502   \u2514\u2500\u2500 web\/          <span class=\"hljs-comment\"># React frontend<\/span>\n\u251c\u2500\u2500 contracts\/\n\u2502   \u251c\u2500\u2500 factory\/      <span class=\"hljs-comment\"># Wallet factory contract<\/span>\n\u2502   \u2514\u2500\u2500 smart-wallet\/ <span class=\"hljs-comment\"># WebAuthn-enabled smart wallet<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Installation<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Clone repository<\/span>\ngit <span class=\"hljs-keyword\">clone<\/span> https:<span class=\"hljs-comment\">\/\/github.com\/CheesecakeLabs\/soroban-smart-wallet-poc<\/span>\ncd soroban-smart-wallet-poc\n\n<span class=\"hljs-comment\"># Install dependencies<\/span>\nnpm install<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Smart Contract Deployment<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Step 1: Deploy the Smart Wallet Contract<\/h4>\n\n\n\n<p>The smart wallet contract must be deployed first as the factory requires its WASM hash.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Configure the<br><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\"><strong>SOURCE_ACCOUNT<\/strong><br><\/mark>under<br><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\"><strong>contracts\/smart-wallet\/<\/strong><br><\/mark>to be the recovery account.<\/li>\n\n\n\n<li>Execute the build and upload:<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">cd contracts\/smart-wallet\n<span class=\"hljs-comment\"># Build the contract<\/span>\nmake build\n<span class=\"hljs-comment\"># Upload the WASM to the network<\/span>\nmake upload<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Important:<\/strong> Note the returned WASM hash.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Step 2: Deploy the Factory Contract<\/h4>\n\n\n\n<p>In<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">contracts\/factory\/<br><\/mark><\/strong>, update the<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">SOURCE_ACCOUNT<br><\/mark><\/strong>and the<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">WASM_HASH<br><\/mark><\/strong>(from the previous step) in the<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">Makefile<\/mark><\/strong><\/p>\n\n\n\n<p>Execute the build and deploy:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">cd contracts\/factory\n<span class=\"hljs-comment\"># Build the factory contract<\/span>\nmake build\n<span class=\"hljs-comment\"># Deploy to the network<\/span>\nmake deploy<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Important:<\/strong> Note the returned<br><strong><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">contract_id<\/mark><br><\/strong>for backend configuration.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Step 3: Configure Backend Environment<\/h4>\n\n\n\n<p>Create the<\/p>\n\n\n\n<p><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">.env<\/mark><\/p>\n\n\n\n<p>file in<\/p>\n\n\n\n<p><mark style=\"background-color:#115EFB\" class=\"has-inline-color has-white-color\">apps\/backend\/<\/mark><\/p>\n\n\n\n<p>and fill in the contract IDs and secret keys (for testing):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># Server Configuration  <\/span>\nPORT=<span class=\"hljs-number\">3000<\/span>  \n  \n<span class=\"hljs-comment\"># WebAuthn Configuration  <\/span>\nWEBAUTHN_RP_NAME=<span class=\"hljs-string\">\"Soroban Smart Wallet POC\"<\/span>  \nWEBAUTHN_RP_ORIGIN=<span class=\"hljs-string\">\"http:\/\/localhost:5173\"<\/span>  \n  \n<span class=\"hljs-comment\"># Stellar\/Soroban Configuration  <\/span>\nSTELLAR_RPC_URL=<span class=\"hljs-string\">\"https:\/\/soroban-testnet.stellar.org\"<\/span>  \nSTELLAR_NETWORK_PASSPHRASE=<span class=\"hljs-string\">\"Test SDF Network ; September 2015\"<\/span>  \nSTELLAR_MAX_FEE=<span class=\"hljs-string\">\"10000000\"<\/span>  \n  \n<span class=\"hljs-comment\"># Contract Configuration  <\/span>\nWALLET_FACTORY_CONTRACT_ID=<span class=\"hljs-string\">\"C...\"<\/span>  <span class=\"hljs-comment\"># Factory contract ID from deployment  <\/span>\nNATIVE_TOKEN_CONTRACT_ID=<span class=\"hljs-string\">\"CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC\"<\/span>  \n  \n<span class=\"hljs-comment\"># Wallet Configuration  <\/span>\nOPEX_WALLET_SECRET_KEY=<span class=\"hljs-string\">\"S...\"<\/span>       <span class=\"hljs-comment\"># Sponsors fees and funds wallets (must be funded)  <\/span>\nRECOVERY_WALLET_SECRET_KEY=<span class=\"hljs-string\">\"S...\"<\/span>   <span class=\"hljs-comment\"># Recovery account secret key (starts with S) <\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Variable<\/strong><\/td><td><strong>Description<\/strong><\/td><\/tr><tr><td>WALLET_FACTORY_CONTRACT_ID<\/td><td>The deployed factory contract address<\/td><\/tr><tr><td>OPEX_WALLET_SECRET_KEY<\/td><td>The secret key of the wallet that sponsors transaction fees and funds new wallets. Must be a funded testnet wallet<\/td><\/tr><tr><td>RECOVERY_WALLET_SECRET_KEY<\/td><td>Secret key, its public key is set as recovery signer on new wallets. Enables passkey rotation for lost access<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Additional Features<\/h3>\n\n\n\n<p>The PoC includes additional features:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Fund Wallet<\/strong>\n<ul class=\"wp-block-list\">\n<li>POST \/api\/fund-wallet<\/li>\n\n\n\n<li>Transfers testnet XLM from the operations wallet to newly created wallets.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Get Balance<\/strong>\n<ul class=\"wp-block-list\">\n<li>GET \/api\/balance?wallet_address=C&#8230;<\/li>\n\n\n\n<li>Retrieves the native token balance for a wallet.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Session management by Tokens<\/strong> &#8211; Enhances system security<\/li>\n\n\n\n<li><strong>Multi-passkey support <\/strong>&#8211; Allow multiple devices per wallet<\/li>\n\n\n\n<li><strong>Decentralized recovery<\/strong> &#8211; Replace centralized recovery with multi-sig or time-locked mechanisms<\/li>\n\n\n\n<li><strong>Gas abstraction<\/strong> &#8211; Advanced fee sponsorship strategies<\/li>\n\n\n\n<li><strong>Social recovery<\/strong> &#8211; Multiple recovery signers with threshold signatures<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Try It Yourself<\/h2>\n\n\n\n<p>The complete proof of concept is available at <a href=\"https:\/\/github.com\/CheesecakeLabs\/soroban-smart-wallet-poc\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a>.<\/p>\n\n\n\n<p><strong>Feel free to:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Deploy your own contracts on testnet<\/li>\n\n\n\n<li>Experiment with wallet creation, transfer, and recovery flows<\/li>\n\n\n\n<li>Test the passkey rotation mechanism<\/li>\n\n\n\n<li>Extend the PoC with additional features<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">References<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.w3.org\/TR\/webauthn-2\/\" target=\"_blank\" rel=\"noreferrer noopener\">WebAuthn Specification<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/simplewebauthn.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">SimpleWebAuthn Library<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/soroban.stellar.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Soroban Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/stellar.github.io\/js-stellar-sdk\/\" target=\"_blank\" rel=\"noreferrer noopener\">Stellar SDK<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/stellar\/sep-smart-wallet\" target=\"_blank\" rel=\"noreferrer noopener\">SEP Smart Wallet Reference<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Traditional blockchain wallets require users to manage private keys \u2014 a major barrier to mainstream adoption. Lost keys mean lost funds, and many security incidents originate from poor key-handling practices. In this article, we demonstrate how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar\/Soroban blockchain. By [&hellip;]<\/p>\n","protected":false},"author":92,"featured_media":13366,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[432],"tags":[1353,1352,1200],"class_list":["post-13350","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-passkey","tag-smart-wallet","tag-stellar"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Eliminating Private Key Management with Passkeys on Stellar<\/title>\n<meta name=\"description\" content=\"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Eliminating Private Key Management with Passkeys on Stellar\" \/>\n<meta property=\"og:description\" content=\"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\" \/>\n<meta property=\"og:site_name\" content=\"Cheesecake Labs\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/cheesecakelabs\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-03T15:09:53+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-17T13:33:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1536\" \/>\n\t<meta property=\"og:image:height\" content=\"689\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Cheesecake Labs\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:site\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\"},\"author\":{\"name\":\"Roberson Costa\"},\"headline\":\"Building a Passkey-Enabled Smart Wallet on the Stellar Network\",\"datePublished\":\"2026-02-03T15:09:53+00:00\",\"dateModified\":\"2026-03-17T13:33:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\"},\"wordCount\":1866,\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg\",\"keywords\":[\"passkey\",\"smart wallet\",\"stellar\"],\"articleSection\":[\"Engineering\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\",\"name\":\"Eliminating Private Key Management with Passkeys on Stellar\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg\",\"datePublished\":\"2026-02-03T15:09:53+00:00\",\"dateModified\":\"2026-03-17T13:33:23+00:00\",\"author\":{\"@type\":\"person\",\"name\":\"Roberson Costa\"},\"description\":\"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!\",\"breadcrumb\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg\",\"width\":1536,\"height\":689},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cheesecakelabs.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Passkey-Enabled Smart Wallet on the Stellar Network\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/\",\"name\":\"Cheesecake Labs\",\"description\":\"Nearshore outsourcing company for Web and Mobile design and engineering services, and staff augmentation for startups and enterprises..\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"name\":\"Roberson Costa\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/roberson-costa.jpg\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/roberson-costa.jpg\",\"caption\":\"Roberson Costa\"},\"url\":\"https:\/\/cheesecakelabs.com\/blog\/autor\/roberson-costa\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Eliminating Private Key Management with Passkeys on Stellar","description":"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/","og_locale":"en_US","og_type":"article","og_title":"Eliminating Private Key Management with Passkeys on Stellar","og_description":"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!","og_url":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/","og_site_name":"Cheesecake Labs","article_publisher":"https:\/\/www.facebook.com\/cheesecakelabs","article_published_time":"2026-02-03T15:09:53+00:00","article_modified_time":"2026-03-17T13:33:23+00:00","og_image":[{"width":1536,"height":689,"url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg","type":"image\/jpeg"}],"author":"Cheesecake Labs","twitter_card":"summary_large_image","twitter_creator":"@cheesecakelabs","twitter_site":"@cheesecakelabs","twitter_misc":{"Written by":null,"Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#article","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/"},"author":{"name":"Roberson Costa"},"headline":"Building a Passkey-Enabled Smart Wallet on the Stellar Network","datePublished":"2026-02-03T15:09:53+00:00","dateModified":"2026-03-17T13:33:23+00:00","mainEntityOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/"},"wordCount":1866,"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg","keywords":["passkey","smart wallet","stellar"],"articleSection":["Engineering"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/","url":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/","name":"Eliminating Private Key Management with Passkeys on Stellar","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage"},"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg","datePublished":"2026-02-03T15:09:53+00:00","dateModified":"2026-03-17T13:33:23+00:00","author":{"@type":"person","name":"Roberson Costa"},"description":"Discover how to build a smart wallet powered by passkeys (WebAuthn) for authentication and transaction signing on the Stellar!","breadcrumb":{"@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#primaryimage","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/passkey-with-stellar.jpg","width":1536,"height":689},{"@type":"BreadcrumbList","@id":"https:\/\/cheesecakelabs.com\/blog\/building-a-passkey-enabled-smart-wallet-on-the-stellar-network\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cheesecakelabs.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Passkey-Enabled Smart Wallet on the Stellar Network"}]},{"@type":"WebSite","@id":"https:\/\/cheesecakelabs.com\/blog\/#website","url":"https:\/\/cheesecakelabs.com\/blog\/","name":"Cheesecake Labs","description":"Nearshore outsourcing company for Web and Mobile design and engineering services, and staff augmentation for startups and enterprises..","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","name":"Roberson Costa","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/roberson-costa.jpg","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2026\/02\/roberson-costa.jpg","caption":"Roberson Costa"},"url":"https:\/\/cheesecakelabs.com\/blog\/autor\/roberson-costa\/"}]}},"_links":{"self":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/13350","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/users\/92"}],"replies":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/comments?post=13350"}],"version-history":[{"count":8,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/13350\/revisions"}],"predecessor-version":[{"id":13390,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/13350\/revisions\/13390"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media\/13366"}],"wp:attachment":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media?parent=13350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/categories?post=13350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/tags?post=13350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}