Le Guide Essentiel de l'Abstraction de Compte sur IoTeX : Un Guide Pratique des Signatures p256
Avec notre communauté votant en faveur de la Proposition d'Amélioration IoTeX 14, l'Abstraction de Compte a enfin atteint le Mainnet et le Testnet d'IoTeX, et ses fonctionnalités sont désormais disponibles pour tous les développeurs de l'écosystème. Alors, qu'est-ce que l'AA, comment cela fonctionne-t-il, et comment pouvez-vous l'utiliser dans votre prochaine application ?
Un Petit Rappel
L'Abstraction de Compte (AA) telle que définie par l'ERC-4337, "permet aux utilisateurs d'utiliser des portefeuilles de contrats intelligents contenant une logique de vérification arbitraire au lieu d'EOAs comme compte principal." L'ERC-4337 introduit de nombreux avantages pour l'expérience utilisateur, notamment en permettant aux utilisateurs d'utiliser des contrats intelligents comme comptes principaux.
L'ERC-4337 fonctionne au-dessus de la blockchain et ne nécessite aucun changement à la blockchain elle-même. Actuellement, le code d'Abstraction de Compte IoTeX est basé sur la version 0.6.0 de l'ERC-4337.
Composants de l'Infrastructure AA
Les composants de l'infrastructure AA sont :
- Services de Regroupement : un point de terminaison pour Mainnet (https://bundler.w3bstream.com) et un pour Testnet (https://bundler.testnet.w3bstream.com). Un regroupeur est un nœud hors chaîne qui agrège plusieurs opérations utilisateur abstraites en une seule transaction que la blockchain sous-jacente peut traiter. Cette transaction est envoyée à l'autre composant fixe, appelé le
EntryPointContrat. EntryPointContrat : Il existe deux contratsEntryPointdéployés sur IoTeX, un pour Mainnet (0xc3527348De07d591c9d567ce1998eFA2031B8675) et un pour Testnet (0xc3527348De07d591c9d567ce1998eFA2031B8675). Un contratEntryPointest chargé de créer/déployer certains contrats spéciaux, appelés contratsAccountFactory, qui à leur tour sont chargés de créer certains comptes (contrats de portefeuille) qui peuvent être utilisés à des fins spécifiques.
Pour utiliser l'abstraction de compte pour créer un nouveau compte personnalisé, il y a certains composants que le développeur d'application décentralisée devra créer en fonction des besoins de son application :
- Contrat
Account, qui implémente la logique de validation dans la méthodevalidateUserOp, et toute logique d'exécution qu'une opération utilisateur peut nécessiter. - Contrat
AccountFactory, qui est chargé, comme indiqué ci-dessus, de créer/déployer de nouveaux contrats de compte personnalisés. - Du code client qui construit les opérations utilisateur compatibles avec les règles de vérification implémentées dans le
AccountFactory. - Un paymaster est une partie optionnelle de l'architecture AA. IoTeX fournit un service de paymaster uniquement pour le Testnet à https://paymaster.testnet.w3bstream.com. Le rôle du paymaster est de financer le gaz nécessaire à l'exécution des opérations utilisateur, soit en les sponsorisant complètement, soit en permettant aux utilisateurs de les payer avec différents tokens.
Exemple : Le P256AccountFactory
Comme premier exemple, nous avons fourni un contrat officiel P256AccountFactory (Mainnet 0xD98d2B6cBca981c777037c5784721d8179D7030b et Testnet 0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB) qui permet aux développeurs de créer des contrats de compte pouvant vérifier des opérations utilisateur signées avec la cryptographie "p256", plutôt qu'avec la courbe elliptique native "secp256k1" d'Ethereum et d'IoTeX. Cela est incroyablement utile, car cela permet aux développeurs de créer des applications où les utilisateurs peuvent, par exemple, signer des transactions avec leurs biométries, ou se passer de phrases de récupération, ou même bénéficier d'une sécurité supérieure lorsque leur appareil prend en charge une puce de sécurité dédiée (par exemple, l'Élément Sécurisé d'Android et l'Enclave Sécurisée d'Apple, etc.). Le code source du P256AccountFactorycan peut être trouvé à https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/accounts/secp256r1/P256AccountFactory.sol tandis que les contrats d'Abstraction de Compte open source reposent sur l'implémentation par l'auteur d'origine de l'EIP-4337 pour Ethereum ici https://github.com/iotexproject/account-abstraction-contracts/tree/main.
Le P256AccountFactory prend également en charge la gestion d'un service de paymaster, composé de deux composants, un contrat VerifyingPaymaster (https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol) et un point de terminaison de service hors chaîne pour générer une preuve de paiement pour le contrat de paymaster (https://paymaster.testnet.w3bstream.com, pour Testnet uniquement).
Le code ci-dessous vous montre comment interagir avec l'implémentation du compte p256 à partir d'un client javascript afin de créer un compte :
async function main() {
// charger les contrats déployés
const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint
// un compte EOA pour envoyer des UserOperations
const bundler = new ethers.Wallet(process.env.BUNDLER!, ethers.provider)
// charger la paire de clés secp256r1
const keyContent = fs.readFileSync(path.join(__dirname, "key.pem"))
const keyPair = ecPem.loadPrivateKey(keyContent)
const publicKey = "0x" + keyPair.getPublicKey("hex").substring(2)
const index = 0
const account = await factory.getAddress(publicKey, index)
// créer l'opération UserOperation de création de compte
const initCode = hexConcat([ factory.address, factory.interface.encodeFunctionData("createAccount", [publicKey, index]),
])
const createOp = {
sender: account,
initCode: initCode,
}
const fullCreateOp = await fillUserOp(createOp, entryPoint)
// miser IOTX pour le gaz
const stake = await entryPoint.balanceOf(account)
if (stake.isZero()) {
console.log(`déposer du gaz pour le compte ${account}`)
const tx = await entryPoint
.connect(bundler)
.depositTo(account, { value: ethers.utils.parseEther("10") })
await tx.wait()
}
// signer l'opération UserOperation en utilisant la courbe secp256r1
const chainId = (await ethers.provider.getNetwork()).chainId
const signedOp = await signOp(
fullCreateOp,
entryPoint.address,
chainId,
new P2565Signer(keyPair)
)
// simuler l'opération UserOperation
const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
if (err.errorName === "FailedOp") {
console.error(`simuler l'erreur d'opération ${err.errorArgs.at(-1)}`)
return
} else if (err.errorName !== "ValidationResult") {
console.error(`erreur inconnue ${err}`)
return
}
console.log(`simulation de l'opération réussie`)
// envoyer UserOperation à EntryPoint
const tx = await entryPoint.connect(bundler).handleOps([signedOp], bundler.address)
console.log(`transaction de création de compte : ${tx.hash}, compte : ${account}`)
}
Tandis que le code suivant vous montrera comment transférer IOTX en utilisant le service de regroupement et le paymaster :
async function main() {
const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
const accountTpl = await ethers.getContractFactory("P256Account")
const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint
const paymaster = await ethers.getContract("VerifyingPaymaster")
const bundler = new JsonRpcProvider("http://localhost:4337")
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!)
const keyContent = fs.readFileSync(path.join(__dirname, "key.pem"))
const keyPair = ecPem.loadPrivateKey(keyContent)
const publicKey = "0x" + keyPair.getPublicKey("hex").substring(2)
const index = 0
const account = await factory.getAddress(publicKey, index)
const callData = accountTpl.interface.encodeFunctionData("execute", [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
ethers.utils.parseEther("0.1"),
"0x",
])
const transferOp = {
sender: account,
callData,
preVerificationGas: 50000,
}
const fullCreateOp = await fillUserOp(transferOp, entryPoint)
fullCreateOp.paymasterAndData = hexConcat([
paymaster.address,
defaultAbiCoder.encode(["uint48", "uint48"], [0, 0]),
"0x" + "00".repeat(65),
])
const validAfter = Math.floor(new Date().getTime() / 1000)
const validUntil = validAfter + 86400 // un jour
const pendingOpHash = await paymaster.getHash(fullCreateOp, validUntil, validAfter)
const paymasterSignature = await signer.signMessage(arrayify(pendingOpHash))
fullCreateOp.paymasterAndData = hexConcat([
paymaster.address,
defaultAbiCoder.encode(["uint48", "uint48"], [validUntil, validAfter]),
paymasterSignature,
])
const chainId = (await ethers.provider.getNetwork()).chainId
const signedOp = await signOp(
fullCreateOp,
entryPoint.address,
chainId,
new P2565Signer(keyPair)
)
const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
if (err.errorName === "FailedOp") {
console.error(`simuler l'erreur d'opération ${err.errorArgs.at(-1)}`)
return
} else if (err.errorName !== "ValidationResult") {
console.error(`erreur inconnue ${err}`)
return
}
console.log(`simulation de l'opération réussie`)
const hexifiedUserOp = deepHexlify(await resolveProperties(signedOp))
const result = await bundler.send("eth_sendUserOperation", [hexifiedUserOp, entryPoint.address])
console.log(`transfert utilisant le service de regroupement réussi opHash : ${result}`)
}
Le reste de l'exemple sur la façon d'interagir avec l'implémentation du compte p256 à partir d'un client javascript peut être trouvé sur https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1