Skip to content

Instantly share code, notes, and snippets.

@drichar
Created July 6, 2025 03:03
Show Gist options
  • Save drichar/940298f2b31a4f01df90434eeb6052e9 to your computer and use it in GitHub Desktop.
Save drichar/940298f2b31a4f01df90434eeb6052e9 to your computer and use it in GitHub Desktop.
TypeScript utility module that integrates Web3Auth Single Factor Auth with Algorand blockchain for React Native applications, enabling Firebase JWT-based authentication and Algorand transaction signing through Web3Auth's managed private keys
import { CHAIN_NAMESPACES, IBaseProvider } from '@web3auth/base'
import { CommonPrivateKeyProvider } from '@web3auth/base-provider'
import { Web3Auth, SDK_MODE } from '@web3auth/single-factor-auth'
import algosdk from 'algosdk'
// Create an in-memory storage that doesn't persist between app sessions
// This prevents automatic session restoration while satisfying Web3Auth's storage requirement
class InMemoryStorage {
private storage: Map<string, string> = new Map()
async getItem(key: string): Promise<string | null> {
return this.storage.get(key) || null
}
async setItem(key: string, value: string): Promise<void> {
this.storage.set(key, value)
}
async removeItem(key: string): Promise<void> {
this.storage.delete(key)
}
async clear(): Promise<void> {
this.storage.clear()
}
}
// Configure the chain config for Algorand
const chainConfig = {
chainNamespace: CHAIN_NAMESPACES.OTHER,
chainId: 'algorand',
rpcTarget: 'https://mainnet-api.algonode.cloud',
displayName: 'Algorand Mainnet',
blockExplorer: 'https://allo.info',
ticker: 'ALGO',
tickerName: 'Algorand',
}
// Create the private key provider for Algorand
const privateKeyProvider = new CommonPrivateKeyProvider({
config: { chainConfig },
}) as IBaseProvider<string>
// Configure Web3Auth Single Factor Auth instance
export const web3auth = new Web3Auth({
clientId: process.env.EXPO_PUBLIC_WEB3AUTH_CLIENT_ID ?? '',
web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' for development
// Use in-memory storage to prevent session persistence between app restarts
storage: new InMemoryStorage(),
mode: SDK_MODE.REACT_NATIVE,
privateKeyProvider,
})
/**
* Initializes Web3Auth SFA
*/
export const initWeb3Auth = async (): Promise<void> => {
try {
await web3auth.init()
console.log('Web3Auth SFA initialized successfully')
} catch (error) {
console.error('Failed to initialize Web3Auth SFA:', error)
throw error
}
}
/**
* Connects to Web3Auth using Firebase JWT
* @param idToken The Firebase ID token
* @param verifierId The user's Firebase UID (sub claim)
* @returns Promise that resolves when connected
*/
export const connectWeb3Auth = async (
idToken: string,
verifierId: string,
): Promise<void> => {
const verifier = 'web3auth-haystack' // Verifier name from Web3Auth dashboard
await web3auth.connect({
verifier,
verifierId,
idToken,
})
}
/**
* Gets the current Web3Auth private key
* @returns The private key as a hex string
*/
export const getWeb3AuthPrivateKey = async (): Promise<string> => {
if (!web3auth.connected) {
throw new Error('Web3Auth not connected')
}
// Get the private key using the provider request method
const privateKey = (await web3auth.provider?.request({
method: 'private_key',
})) as string
if (!privateKey) {
throw new Error('No private key available from Web3Auth')
}
return privateKey
}
/**
* Creates a transaction signer function that uses Web3Auth to sign Algorand transactions
* @returns A transaction signer function compatible with the NFD SDK and algosdk TransactionSigner type
*/
export const createWeb3AuthSigner = (): algosdk.TransactionSigner => {
/**
* Signs a group of transactions using Web3Auth private key
* @param txnGroup Array of transactions to sign
* @param indexesToSign Array of indexes in the transaction group that should be signed
* @returns A promise which resolves an array of encoded signed transactions. The length of the
* array will be the same as the length of indexesToSign, and each index i in the array
* corresponds to the signed transaction from txnGroup[indexesToSign[i]]
*/
return async (
txnGroup: algosdk.Transaction[],
indexesToSign: number[],
): Promise<Uint8Array[]> => {
// If there's nothing to sign, return an empty array
if (indexesToSign.length === 0) {
return []
}
try {
// Get the private key from Web3Auth
const privateKey = await getWeb3AuthPrivateKey()
if (!privateKey) {
throw new Error('No private key available from Web3Auth')
}
// Convert hex private key to Uint8Array if needed
const cleanPrivateKey = privateKey.startsWith('0x')
? privateKey.slice(2)
: privateKey
const privateKeyBytes = new Uint8Array(
Buffer.from(cleanPrivateKey, 'hex'),
)
// Create Algorand account from private key
const account = algosdk.mnemonicToSecretKey(
algosdk.secretKeyToMnemonic(privateKeyBytes),
)
const signedTxns: Uint8Array[] = []
// Sign each transaction that matches our indexes
for (const index of indexesToSign) {
const txn = txnGroup[index]
// Convert sender Address to string for comparison
const senderAddress = txn.sender.toString()
// Verify this transaction is meant to be signed by this account
if (senderAddress !== account.addr.toString()) {
throw new Error(
`Transaction at index ${index} has sender ${senderAddress} but expected ${account.addr}`,
)
}
// Sign the transaction
const signedTxn = algosdk.signTransaction(txn, account.sk)
signedTxns.push(signedTxn.blob)
}
console.log(`Signed ${signedTxns.length} transactions with Web3Auth`)
return signedTxns
} catch (error) {
console.error('Web3Auth transaction signing failed:', error)
throw new Error(`Failed to sign transactions: ${error}`)
}
}
}
/**
* Gets the Algorand address from the current Web3Auth private key
* @returns The Algorand address
*/
export const getAlgorandAddressFromWeb3Auth = async (): Promise<string> => {
try {
const privateKey = await getWeb3AuthPrivateKey()
if (!privateKey) {
throw new Error('No private key available from Web3Auth')
}
return getAlgorandAddressFromPrivateKey(privateKey)
} catch (error) {
console.error(
'Failed to derive Algorand address from Web3Auth private key:',
error,
)
throw new Error(`Failed to derive Algorand address: ${error}`)
}
}
/**
* Gets the Algorand address from a private key
* @param privateKey The private key from Web3Auth (hex string)
* @returns The Algorand address
*/
export const getAlgorandAddressFromPrivateKey = (
privateKey: string,
): string => {
try {
// Convert hex private key to Uint8Array if needed
const cleanPrivateKey = privateKey.startsWith('0x')
? privateKey.slice(2)
: privateKey
const privateKeyBytes = new Uint8Array(Buffer.from(cleanPrivateKey, 'hex'))
// Create Algorand account from private key
const account = algosdk.mnemonicToSecretKey(
algosdk.secretKeyToMnemonic(privateKeyBytes),
)
return account.addr.toString()
} catch (error) {
console.error('Failed to derive Algorand address from private key:', error)
throw new Error(`Failed to derive Algorand address: ${error}`)
}
}
/**
* Gets the Algorand mnemonic from the current Web3Auth private key
* @returns The 25-word mnemonic phrase
*/
export const getAlgorandMnemonicFromWeb3Auth = async (): Promise<string> => {
try {
const privateKey = await getWeb3AuthPrivateKey()
if (!privateKey) {
throw new Error('No private key available from Web3Auth')
}
// Convert hex private key to Uint8Array if needed
const cleanPrivateKey = privateKey.startsWith('0x')
? privateKey.slice(2)
: privateKey
const privateKeyBytes = new Uint8Array(Buffer.from(cleanPrivateKey, 'hex'))
// Convert private key to mnemonic
const mnemonic = algosdk.secretKeyToMnemonic(privateKeyBytes)
return mnemonic
} catch (error) {
console.error('Failed to derive mnemonic from Web3Auth private key:', error)
throw new Error(`Failed to derive mnemonic: ${error}`)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment