Hướng Dẫn Cần Thiết về Trừu Tượng Tài Khoản trên IoTeX: Hướng Dẫn Thực Hành về Chữ Ký p256
Với sự ủng hộ hoàn toàn từ cộng đồng trong cuộc bỏ phiếu về Đề Xuất Cải Tiến IoTeX 14, Trừu Tượng Tài Khoản đã chính thức xuất hiện trên mạng chính và mạng thử nghiệm IoTeX, và các tính năng của nó hiện đã có sẵn cho tất cả các nhà phát triển trong hệ sinh thái. Vậy AA là gì, nó hoạt động như thế nào, và bạn có thể sử dụng nó cho ứng dụng tiếp theo của mình như thế nào?
Một Lời Nhắc Nhanh
Trừu Tượng Tài Khoản (AA) được định nghĩa bởi ERC-4337, "cho phép người dùng sử dụng ví hợp đồng thông minh chứa logic xác minh tùy ý thay vì EOAs làm tài khoản chính của họ." ERC-4337 giới thiệu nhiều lợi ích về trải nghiệm người dùng, điều nổi bật nhất là cho phép mọi người sử dụng Hợp Đồng Thông Minh làm tài khoản chính của họ.
ERC-4337 chạy trên nền tảng blockchain và không yêu cầu bất kỳ thay đổi nào cho chính blockchain. Hiện tại, mã Trừu Tượng Tài Khoản IoTeX dựa trên phiên bản phát hành ERC-4337 0.6.0.
Các Thành Phần của Cơ Sở Hạ Tầng AA
Các thành phần của hạ tầng AA bao gồm:
- Dịch Vụ Bundler: một điểm kết nối cho Mainnet (https://bundler.w3bstream.com) và một cho Testnet (https://bundler.testnet.w3bstream.com). Một bundler là một nút offchain tập hợp nhiều hoạt động người dùng đã được trừu tượng hóa thành một giao dịch duy nhất mà blockchain cơ sở có thể xử lý. Giao dịch này được gửi tới thành phần cố định khác, gọi là
EntryPointContract. EntryPointContract: Có hai hợp đồngEntryPointđược triển khai trên IoTeX, một cho Mainnet (0xc3527348De07d591c9d567ce1998eFA2031B8675) và một cho Testnet (0xc3527348De07d591c9d567ce1998eFA2031B8675). Hợp đồngEntryPointchịu trách nhiệm tạo/triển khai một số hợp đồng đặc biệt, gọi là hợp đồngAccountFactory, mà lần lượt chịu trách nhiệm tạo ra một số tài khoản nhất định (hợp đồng ví) có thể được sử dụng cho các mục đích cụ thể.
Để sử dụng trừu tượng tài khoản nhằm tạo ra một tài khoản tùy chỉnh mới, có một số thành phần mà nhà phát triển dApp sẽ phải tạo dựa trên nhu cầu của ứng dụng của họ:
Accountcontract, hợp đồng này thực hiện logic xác thực trong phương thứcvalidateUserOpvà bất kỳ logic thực thi nào mà một hoạt động của người dùng có thể yêu cầu.AccountFactorycontract, như đã nói ở trên, chịu trách nhiệm tạo/triển khai các hợp đồng tài khoản tùy chỉnh mới.- Một số mã khách hàng xây dựng các hoạt động người dùng tương thích với các quy tắc xác minh được thực hiện trong
AccountFactory. - Một paymaster là một phần tùy chọn của kiến trúc AA. IoTeX cung cấp dịch vụ paymaster chỉ cho Testnet tại https://paymaster.testnet.w3bstream.com. Vai trò của paymaster là tài trợ cho gas cần thiết để thực hiện các hoạt động người dùng, hoặc tài trợ hoàn toàn cho chúng hoặc cho phép người dùng thanh toán chúng bằng nhiều token khác nhau.
Ví Dụ: Hợp Đồng P256AccountFactory
Như một ví dụ đầu tiên, chúng tôi đã cung cấp một hợp đồng P256AccountFactory chính thức (Mainnet 0xD98d2B6cBca981c777037c5784721d8179D7030b và Testnet 0x508Db1A73FcBA98594679aD4f5d8D0B880BbdaFB) cho phép các nhà phát triển tạo ra các hợp đồng tài khoản có thể xác minh các hoạt động người dùng được ký bằng mã hóa "p256", thay vì bằng "secp256k1" đường cong elip gốc của Ethereum và IoTeX. Điều này cực kỳ hữu ích, vì nó cung cấp cho các nhà phát triển khả năng tạo ra các ứng dụng mà người dùng có thể, ví dụ, ký giao dịch bằng sinh trắc học của họ, hoặc tránh xa các cụm từ hạt giống, hoặc thậm chí có độ an toàn tốt hơn khi thiết bị của họ hỗ trợ một chip bảo mật chuyên dụng (ví dụ: Thành phần Bảo mật của Android và Enclave Bảo mật của Apple, vv). Mã nguồn của P256AccountFactorycan có thể được tìm thấy tại https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/accounts/secp256r1/P256AccountFactory.sol trong khi các hợp đồng Trừu Tượng Tài Khoản nguồn mở dựa trên thực hiện bởi tác giả gốc của EIP-4337 cho Ethereum tại đây https://github.com/iotexproject/account-abstraction-contracts/tree/main.
Hợp đồng P256AccountFactory cũng hỗ trợ việc quản lý dịch vụ paymaster, được cấu tạo từ hai thành phần, một hợp đồng VerifyingPaymaster (https://github.com/iotexproject/account-abstraction-contracts/blob/main/contracts/paymaster/VerifyingPaymaster.sol) và một điểm dịch vụ off-chain để tạo chứng cứ thanh toán cho hợp đồng paymaster (https://paymaster.testnet.w3bstream.com, chỉ cho Testnet).
Mã dưới đây cho bạn thấy cách tương tác với việc triển khai tài khoản p256 từ một máy khách javascript để tạo một tài khoản:
async function main() {
// load deployed contracts
const factory = (await ethers.getContract("P256AccountFactory")) as P256AccountFactory
const entryPoint = (await ethers.getContract("EntryPoint")) as EntryPoint
// an EOA account for send UserOperations
const bundler = new ethers.Wallet(process.env.BUNDLER!, ethers.provider)
// load secp256r1 keypair
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)
// create create account UserOperation
const initCode = hexConcat([ factory.address, factory.interface.encodeFunctionData("createAccount", [publicKey, index]),
])
const createOp = {
sender: account,
initCode: initCode,
}
const fullCreateOp = await fillUserOp(createOp, entryPoint)
// stake IOTX for gas
const stake = await entryPoint.balanceOf(account)
if (stake.isZero()) {
console.log(`deposit gas for account ${account}`)
const tx = await entryPoint
.connect(bundler)
.depositTo(account, { value: ethers.utils.parseEther("10") })
await tx.wait()
}
// sign UserOperation using secp256r1 curve
const chainId = (await ethers.provider.getNetwork()).chainId
const signedOp = await signOp(
fullCreateOp,
entryPoint.address,
chainId,
new P2565Signer(keyPair)
)
// simulate UserOperation
const err = await entryPoint.callStatic.simulateValidation(signedOp).catch((e) => e)
if (err.errorName === "FailedOp") {
console.error(`simulate op error ${err.errorArgs.at(-1)}`)
return
} else if (err.errorName !== "ValidationResult") {
console.error(`unknow error ${err}`)
return
}
console.log(`simulate op success`)
// send UserOpersion to EntryPoint
const tx = await entryPoint.connect(bundler).handleOps([signedOp], bundler.address)
console.log(`create account tx: ${tx.hash}, account: ${account}`)
}
Trong khi mã sau đây sẽ cho bạn thấy cách chuyển IOTX bằng dịch vụ bundler và 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 // one day
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(`simulate op error ${err.errorArgs.at(-1)}`)
return
} else if (err.errorName !== "ValidationResult") {
console.error(`unknow error ${err}`)
return
}
console.log(`simulate op success`)
const hexifiedUserOp = deepHexlify(await resolveProperties(signedOp))
const result = await bundler.send("eth_sendUserOperation", [hexifiedUserOp, entryPoint.address])
console.log(`transfer use bundler success opHash: ${result}`)
}
Các ví dụ còn lại về cách tương tác với việc triển khai tài khoản p256 từ một máy khách javascript, có thể được tìm thấy tại https://github.com/iotexproject/account-abstraction-contracts/tree/main/scripts/secp256r1