WIP update - Web3Auth x Wallet Connect signing integration

This commit is contained in:
SaraJane
2026-01-13 10:44:18 +00:00
parent 6386a98986
commit 06a4b7ef67
15 changed files with 4914 additions and 164 deletions

View File

@ -0,0 +1,194 @@
// web3authIntegration.ts
import algosdk, { TransactionSigner } from 'algosdk'
import { AlgorandAccountFromWeb3Auth } from './algorandAdapter'
/**
* Integration Utilities for Web3Auth with AlgorandClient
*
* IMPORTANT:
* @algorandfoundation/algokit-utils AlgorandClient expects `signer` to be a *function*
* (algosdk.TransactionSigner), NOT an object like { sign: fn }.
*
* If you pass an object, youll hit: TypeError: signer is not a function
*/
/**
* (Legacy) Your old shape (kept only for compatibility if other code uses it).
* AlgorandClient does NOT accept this shape as `signer`.
*/
export interface AlgorandTransactionSigner {
sign: (transactions: Uint8Array[]) => Promise<Uint8Array[]>
sender?: string
}
/**
* ✅ Correct: Convert Web3Auth Algorand account to an AlgorandClient-compatible signer function.
*
* Returns algosdk.TransactionSigner which matches AlgoKit / AlgorandClient expectations:
* (txnGroup, indexesToSign) => Promise<Uint8Array[]>
*
* Use like:
* const signer = createWeb3AuthSigner(algorandAccount)
* await algorand.send.assetCreate({ sender: algorandAccount.address, signer, ... })
*/
export function createWeb3AuthSigner(account: AlgorandAccountFromWeb3Auth): TransactionSigner {
// Web3Auth account should contain a Uint8Array secretKey (Algorand secret key).
// We build an algosdk basic account signer (official helper).
const sk = account.secretKey
const addr = account.address
// If your secretKey is not a Uint8Array for some reason, try to coerce it.
// (This is defensive; ideally it is already Uint8Array.)
const secretKey: Uint8Array =
sk instanceof Uint8Array
? sk
: // @ts-expect-error - allow Array<number> fallback
Array.isArray(sk)
? Uint8Array.from(sk)
: (() => {
throw new Error('Web3Auth secretKey is not a Uint8Array (or number[]). Cannot sign transactions.')
})()
return algosdk.makeBasicAccountTransactionSigner({
addr,
sk: secretKey,
})
}
/**
* (Optional helper) If you *still* want the old object shape for other code,
* this returns { sign, sender } — but DO NOT pass this as AlgorandClient `signer`.
*/
export function createWeb3AuthSignerObject(account: AlgorandAccountFromWeb3Auth): AlgorandTransactionSigner {
const signerFn = createWeb3AuthSigner(account)
// Wrap TransactionSigner into "sign(bytes[])" style ONLY if you need it elsewhere
const sign = async (transactions: Uint8Array[]) => {
// These are already bytes; we need Transaction objects for TransactionSigner.
// This wrapper is best-effort and not recommended for AlgoKit usage.
const txns = transactions.map((b) => algosdk.decodeUnsignedTransaction(b))
const signed = await signerFn(txns, txns.map((_, i) => i))
return signed
}
return {
sign,
sender: account.address,
}
}
/**
* Create a multi-signature compatible signer for Web3Auth accounts
*
* For AlgoKit, you still want the signer to be a TransactionSigner function.
* This returns both the function and some metadata.
*/
export function createWeb3AuthMultiSigSigner(account: AlgorandAccountFromWeb3Auth) {
return {
signer: createWeb3AuthSigner(account), // ✅ TransactionSigner function
sender: account.address,
account, // original account for context
}
}
/**
* Get account information needed for transaction construction
*
* Returns the public key and address in formats needed for
* transaction construction and verification
*/
export function getWeb3AuthAccountInfo(account: AlgorandAccountFromWeb3Auth) {
const decodedAddress = algosdk.decodeAddress(account.address)
return {
address: account.address,
publicKeyBytes: decodedAddress.publicKey,
publicKeyBase64: Buffer.from(decodedAddress.publicKey).toString('base64'),
secretKeyHex: Buffer.from(account.secretKey).toString('hex'),
mnemonicPhrase: account.mnemonic,
}
}
/**
* Verify that a transaction was signed by the Web3Auth account
*
* Useful for verification and testing
*/
export function verifyWeb3AuthSignature(signedTransaction: Uint8Array, account: AlgorandAccountFromWeb3Auth): boolean {
try {
const decodedTxn = algosdk.decodeSignedTransaction(signedTransaction)
// In algosdk, signature can be represented differently depending on type.
// Well attempt to compare signer public key where available.
const txnSigner = decodedTxn.sig?.signers?.[0] ?? decodedTxn.sig?.signer
if (!txnSigner) return false
const decodedAddress = algosdk.decodeAddress(account.address)
return Buffer.from(txnSigner).equals(decodedAddress.publicKey)
} catch (error) {
console.error('Error verifying signature:', error)
return false
}
}
/**
* Get transaction group size details
*/
export function analyzeTransactionGroup(transactions: Uint8Array[]) {
return {
count: transactions.length,
totalSize: transactions.reduce((sum, txn) => sum + txn.length, 0),
averageSize: transactions.reduce((sum, txn) => sum + txn.length, 0) / transactions.length,
}
}
/**
* Format a transaction amount with proper decimals for display
*/
export function formatAmount(amount: bigint | number, decimals: number = 6): string {
const amountStr = amount.toString()
const decimalPoints = decimals
if (amountStr.length <= decimalPoints) {
return `0.${amountStr.padStart(decimalPoints, '0')}`
}
const integerPart = amountStr.slice(0, -decimalPoints)
const decimalPart = amountStr.slice(-decimalPoints)
return `${integerPart}.${decimalPart}`
}
/**
* Parse a user-input amount string to base units (reverse of formatAmount)
*/
export function parseAmount(amount: string, decimals: number = 6): bigint {
const trimmed = amount.trim()
if (!trimmed) {
throw new Error('Amount is required')
}
if (!/^\d+(\.\d+)?$/.test(trimmed)) {
throw new Error('Invalid amount format')
}
const [integerPart = '0', decimalPart = ''] = trimmed.split('.')
if (decimalPart.length > decimals) {
throw new Error(`Too many decimal places (maximum ${decimals})`)
}
const paddedDecimal = decimalPart.padEnd(decimals, '0')
const combined = integerPart + paddedDecimal
return BigInt(combined)
}
/**
* Check if Web3Auth account has sufficient balance for a transaction
*/
export function hasSufficientBalance(balance: bigint, requiredAmount: bigint, minFee: bigint = BigInt(1000)): boolean {
const totalRequired = requiredAmount + minFee
return balance >= totalRequired
}