Files
TokenizeRWATemplate/projects/TokenizeRWATemplate-frontend/src/utils/web3auth/web3authIntegration.ts

195 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}