From 407e19aebbf3ea44e6e7a86ee7777501bdff7ed9 Mon Sep 17 00:00:00 2001
From: Joseph Chimebuka
Date: Fri, 8 Nov 2024 16:09:29 +0000
Subject: [PATCH] Implement Wallet-Based Authentication with Stellar Wallet Kit
(#20)
* integrated the stellar wallet
* added functions for register and login with wallet
* added connect wallet function for login with wallet button
* updated changes as requested in review
* added compoent for the wallet component
* updated the changes with the requested changes
* removed imports not needed
* adjusted the code according to previous review
* removed yarn lock file
* refactor(walletStore): clean up code and improve transaction signing logic
* feat: update stellar-wallets-kit dependency and enhance wallet user confirmation flow
* refactor(walletStore): simplify network type assignment in wallet creation
---------
Co-authored-by: Elliot Voris
---
package.json | 7 +-
src/lib/components/ConfirmationModal.svelte | 48 +-
src/lib/components/WalletKitProvider.svelte | 42 +
src/lib/stores/walletStore.js | 75 +-
src/routes/dashboard/+layout.svelte | 2 +-
src/routes/login/+page.svelte | 13 +-
src/routes/signup/+page.svelte | 12 +-
yarn.lock | 1466 ++++++++++++++++++-
8 files changed, 1600 insertions(+), 65 deletions(-)
create mode 100644 src/lib/components/WalletKitProvider.svelte
diff --git a/package.json b/package.json
index 300004d..00e6515 100644
--- a/package.json
+++ b/package.json
@@ -48,5 +48,8 @@
"uuid": "^10.0.0",
"vite": "^5.4.8"
},
- "type": "module"
-}
\ No newline at end of file
+ "type": "module",
+ "dependencies": {
+ "@creit.tech/stellar-wallets-kit": "^1.2.3"
+ }
+}
diff --git a/src/lib/components/ConfirmationModal.svelte b/src/lib/components/ConfirmationModal.svelte
index 2bbd616..dca913a 100644
--- a/src/lib/components/ConfirmationModal.svelte
+++ b/src/lib/components/ConfirmationModal.svelte
@@ -36,6 +36,7 @@ on the following occasions:
// A Svelte "context" is used to control when to `open` and `close` a given
// modal from within other components
import { getContext } from 'svelte'
+ import { get } from 'svelte/store'
const { close } = getContext('simple-modal')
// `onConfirm` is a dummy function that will be overridden from the
@@ -49,12 +50,18 @@ on the following occasions:
isWaiting = true
try {
- // We make sure the user has supplied the correct pincode
- await walletStore.confirmPincode({
- pincode: pincode,
- firstPincode: firstPincode,
- signup: firstPincode ? true : false,
- })
+ // Check if this is a wallet user
+ const { keyId, publicKey } = get(walletStore)
+ const isWalletUser = keyId === publicKey
+
+ if (!isWalletUser) {
+ // Only verify pincode for non-wallet users
+ await walletStore.confirmPincode({
+ pincode: pincode,
+ firstPincode: firstPincode,
+ signup: firstPincode ? true : false,
+ })
+ }
// We call the `onConfirm` function that was given to the modal by
// the outside component. This method allows each page that needs to
@@ -62,14 +69,15 @@ on the following occasions:
// should take place when the pincode is confirmed. (i.e., submit
// the transaction to the network, login to the app, etc.)
// @ts-ignore
- await onConfirm(pincode)
+ // Pass pincode only for non-wallet users
+ await onConfirm(isWalletUser ? undefined : pincode)
// Now we can close the modal window
close()
} catch (err) {
// If there was an error, we set our `errorMessage` alert
// @ts-ignore
- errorMessage.set(err.body.message)
+ errorMessage.set(err.body?.message || err.message || 'Transaction failed')
}
isWaiting = false
}
@@ -112,6 +120,9 @@ on the following occasions:
let isWaiting = false
let pincode = ''
+ // Get wallet status
+ $: isWalletUser = get(walletStore).keyId === get(walletStore).publicKey
+
// The `$: variableName` syntax marks the output of some **expression** (as
// opposed to an assignment) as _reactive_. In this case, every time
// `transactionXDR` or `transactionNetwork` changes, `transaction` will be
@@ -141,8 +152,8 @@ on the following occasions:
>{transaction.memo.type === 'text'
? transaction.memo?.value?.toString('utf-8')
: transaction.memo.type === 'hash'
- ? transaction.memo?.value?.toString('base64')
- : transaction.memo.value}
{/if}
@@ -185,7 +196,7 @@ on the following occasions:
- {#if hasPincodeForm}
+ {#if hasPincodeForm && !isWalletUser}
+ {:else if isWalletUser}
+
+
+
+
+
{/if}
diff --git a/src/lib/components/WalletKitProvider.svelte b/src/lib/components/WalletKitProvider.svelte
new file mode 100644
index 0000000..4e88f42
--- /dev/null
+++ b/src/lib/components/WalletKitProvider.svelte
@@ -0,0 +1,42 @@
+
+
+
+
+
diff --git a/src/lib/stores/walletStore.js b/src/lib/stores/walletStore.js
index 65bc17b..cd00391 100644
--- a/src/lib/stores/walletStore.js
+++ b/src/lib/stores/walletStore.js
@@ -3,6 +3,13 @@ import { get } from 'svelte/store'
import { persisted } from 'svelte-local-storage-store'
import { KeyManager, LocalStorageKeyStore, ScryptEncrypter, KeyType } from '@stellar/typescript-wallet-sdk-km'
import { TransactionBuilder } from '@stellar/stellar-sdk'
+import {
+ StellarWalletsKit,
+ WalletNetwork,
+ allowAllModules,
+ XBULL_ID
+ } from '@creit.tech/stellar-wallets-kit';
+
/** @typedef {import('@stellar/stellar-sdk').Transaction} Transaction */
@@ -16,11 +23,30 @@ import { TransactionBuilder } from '@stellar/stellar-sdk'
function createWalletStore() {
/** @type {import('svelte/store').Writable} */
- const { subscribe, set } = persisted('bpa:walletStore', { keyId: '', publicKey: '' })
-
+ const { subscribe, set, } = persisted('bpa:walletStore', { keyId: '', publicKey: '' })
return {
subscribe,
+
+
+ /**
+ * Connects a user by their public key (wallet-based registration)
+ * @param {Object} opts Options object
+ * @param {string} opts.publicKey Public Stellar address
+ */
+ connectWallet: async ({ publicKey }) => {
+ try {
+ // This effectively both "registers" and "logs in" the wallet
+ set({
+ keyId: publicKey,
+ publicKey: publicKey,
+ })
+ } catch (err) {
+ console.error('Error connecting wallet', err)
+ throw error(400, { message: 'Failed to connect wallet' })
+ }
+ },
+
/**
* Registers a user by storing their encrypted keypair in the browser's localStorage.
* @param {Object} opts Options object
@@ -57,7 +83,7 @@ function createWalletStore() {
console.error('Error saving key', err)
// @ts-ignore
throw error(400, { message: err.toString() })
- }
+ }
},
/**
@@ -95,19 +121,46 @@ function createWalletStore() {
*/
sign: async ({ transactionXDR, network, pincode }) => {
try {
- const keyManager = setupKeyManager()
- let signedTransaction = await keyManager.signTransaction({
+
+ const { keyId, publicKey } = get(walletStore);
+
+ if (keyId === publicKey) {
+
+ const kit = new StellarWalletsKit({
// @ts-ignore
- transaction: TransactionBuilder.fromXDR(transactionXDR, network),
- id: get(walletStore).keyId,
- password: pincode,
- })
- return signedTransaction
- } catch (err) {
+ network: network,
+ selectedWalletId: XBULL_ID,
+ modules: allowAllModules(),
+ });
+ const { address } = await kit.getAddress();
+
+ // Sign the transaction using the wallet address
+ const { signedTxXdr } = await kit.signTransaction(transactionXDR, {
+ address,
+ networkPassphrase: network, // or use your specific network passphrase
+ });
+
+ // @ts-ignore
+ return signedTxXdr; // Return the signed transaction
+ } else {
+
+ const keyManager = setupKeyManager();
+ // Fallback to signing with pincode if no wallet
+ const signedTransaction = await keyManager.signTransaction({
+ // @ts-ignore
+ transaction: TransactionBuilder.fromXDR(transactionXDR, network),
+ id: keyId,
+ password: pincode,
+ });
+ // @ts-ignore
+ return signedTransaction;
+ }
+ }catch (err) {
console.error('Error signing transaction', err)
// @ts-ignore
throw error(400, { message: err.toString() })
}
+
},
}
}
diff --git a/src/routes/dashboard/+layout.svelte b/src/routes/dashboard/+layout.svelte
index 1c2e386..4235172 100644
--- a/src/routes/dashboard/+layout.svelte
+++ b/src/routes/dashboard/+layout.svelte
@@ -20,7 +20,7 @@
import Navbar from './components/Navbar.svelte'
import Drawer from './components/Drawer.svelte'
import Footer from './components/Footer.svelte'
-
+
diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte
index d7518fc..d36268e 100644
--- a/src/routes/login/+page.svelte
+++ b/src/routes/login/+page.svelte
@@ -25,10 +25,16 @@ for submission to the network.
import { goto } from '$app/navigation'
import { errorMessage } from '$lib/stores/alertsStore'
import { walletStore } from '$lib/stores/walletStore'
-
+ import WalletKitProvider from '$lib/components/WalletKitProvider.svelte'
// Define some component variables that will be used throughout the page
let pincode = ''
+
+
+
+
+
+
/**
* Our `login` function ensures the the user has entered a valid pincode for the encrypted keypair, and then redirects them to the dashboard page.
* @async
@@ -86,6 +92,11 @@ for submission to the network.
+
+
+
+
+
diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte
index 59b93ad..ff81c1e 100644
--- a/src/routes/signup/+page.svelte
+++ b/src/routes/signup/+page.svelte
@@ -28,7 +28,7 @@ circumstance.
import { goto } from '$app/navigation'
import { walletStore } from '$lib/stores/walletStore'
import { fundWithFriendbot } from '$lib/stellar/horizonQueries'
-
+ import WalletKitProvider from '$lib/components/WalletKitProvider.svelte'
// The `open` Svelte context is used to open the confirmation modal
import { getContext } from 'svelte'
const { open } = getContext('simple-modal')
@@ -40,6 +40,8 @@ circumstance.
let showSecret = false
let pincode = ''
+
+
/**
* Takes an action after the pincode has been confirmed by the user.
* @async
@@ -74,6 +76,11 @@ circumstance.
onConfirm: onConfirm,
})
}
+
+
+
+
+