|
| 1 | +--- |
| 2 | +title: "Account Abstraction" |
| 3 | +--- |
| 4 | + |
| 5 | +import { Steps, Callout } from "nextra/components"; |
| 6 | + |
| 7 | +# Account Abstraction |
| 8 | + |
| 9 | +Account Abstraction (AA) on Aptos **enables custom transaction authentication logic through Move modules**, allowing accounts to define their own rules beyond native cryptographic schemes. |
| 10 | + |
| 11 | +## Core Concepts |
| 12 | + |
| 13 | +### `FunctionInfo` |
| 14 | + |
| 15 | +A struct defining the authentication function to be invoked. |
| 16 | + |
| 17 | +```move |
| 18 | +struct FunctionInfo has copy, drop, store { |
| 19 | + module_address: address, |
| 20 | + module_name: String, |
| 21 | + function_name: String |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +The authentication function is responsible for defining the authentication logic using Move. It should return a signer if authentication is successful, otherwise it aborts the transaction. |
| 26 | +The only accepted authentication function signature that can be added onto an account is the following: |
| 27 | + |
| 28 | +```move |
| 29 | +// The function can return a signer if authentication is successful, otherwise it aborts the transaction. |
| 30 | +public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer; |
| 31 | +``` |
| 32 | + |
| 33 | +**Example (Move)** |
| 34 | + |
| 35 | +```move |
| 36 | +module deployer::authenticator { |
| 37 | + use aptos_framework::auth_data::{AbstractionAuthData}; |
| 38 | +
|
| 39 | + public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer { |
| 40 | + // ... authentication logic ... |
| 41 | + account |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +**Example (Typescript)** |
| 47 | + |
| 48 | +```typescript |
| 49 | +const authenticationFunction = `${deployer}::authenticator:authenticate`; |
| 50 | +``` |
| 51 | + |
| 52 | +### `AbstractionAuthData` |
| 53 | + |
| 54 | +An enum variant defining the authentication data to be passed to the authentication function. It contains: |
| 55 | + |
| 56 | +- `digest`: The sha256 hash of the signing message. |
| 57 | +- `authenticator`: Abstract bytes that will be passed to the authentication function that will be used to verify the transaction. |
| 58 | + |
| 59 | +```move |
| 60 | +enum AbstractionAuthData has copy, drop { |
| 61 | + V1 { |
| 62 | + digest: vector<u8>, // SHA3-256 hash of the signing message |
| 63 | + authenticator: vector<u8> // Custom auth data (e.g., signatures) |
| 64 | + }, |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +**Example (Move)** |
| 69 | + |
| 70 | +This example demonstrates a simple authentication logic that checks if the authenticator is equal to `"hello world"`. |
| 71 | + |
| 72 | +```move |
| 73 | +module deployer::hello_world_authenticator { |
| 74 | + use aptos_framework::auth_data::{Self, AbstractionAuthData}; |
| 75 | +
|
| 76 | + public fun authenticate( |
| 77 | + account: signer, |
| 78 | + auth_data: AbstractionAuthData |
| 79 | + ): signer { |
| 80 | + let authenticator = *auth_data::authenticator(&auth_data); |
| 81 | + assert!(authenticator == b"hello world", 1); |
| 82 | + account |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +**Example (Typescript)** |
| 88 | + |
| 89 | +```typescript |
| 90 | +const abstractedAccount = new AbstractedAccount({ |
| 91 | + /** |
| 92 | + * The result of the signer function will be available as the `authenticator` field in the `AbstractionAuthData` enum variant. |
| 93 | + */ |
| 94 | + signer: () => new TextEncoder().encode("hello world"), |
| 95 | + /** |
| 96 | + * The authentication function to be invoked. |
| 97 | + */ |
| 98 | + authenticationFunction: `${deployer}::hello_world_authenticator:authenticate`, |
| 99 | +}); |
| 100 | +``` |
| 101 | + |
| 102 | +## Step-by-Step Guide |
| 103 | + |
| 104 | +<Steps> |
| 105 | + |
| 106 | +### 1. Deploy Authentication Module |
| 107 | + |
| 108 | +In this example, we will deploy the `hello_world_authenticator` module. The `authenticate` function takes an `AbstractionAuthData` and returns a `signer` |
| 109 | +if the authentication is successful, otherwise it aborts the transaction. The authentication logic will only allow transactions that have an authenticator equal to `"hello world"`. |
| 110 | + |
| 111 | +```move |
| 112 | +module deployer::hello_world_authenticator { |
| 113 | + use aptos_framework::auth_data::{Self, AbstractionAuthData}; |
| 114 | + use std::bcs; |
| 115 | +
|
| 116 | + public fun authenticate( |
| 117 | + account: signer, |
| 118 | + auth_data: AbstractionAuthData |
| 119 | + ): signer { |
| 120 | + let authenticator = *auth_data::authenticator(&auth_data); |
| 121 | + assert!(authenticator == bcs::to_bytes(&b"hello world"), 1); |
| 122 | + account |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +To deploy the module, you can use the following commands from the [Aptos CLI](/en/build/cli). We assume that you already have set up a workspace with `aptos init` and |
| 128 | +declared the named addresses in your `Move.toml` file. |
| 129 | + |
| 130 | +```bash |
| 131 | +aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890 |
| 132 | +``` |
| 133 | + |
| 134 | +### 2. Setup your Environment |
| 135 | + |
| 136 | +Once deployed, you can setup your environment. In this example, we will use Devnet and create an account named `alice` which will act as our user. |
| 137 | + |
| 138 | +```ts |
| 139 | +const DEPLOYER = "0x<hello_world_authenticator_deployer>" |
| 140 | + |
| 141 | +const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); |
| 142 | + |
| 143 | +const alice = Account.generate(); |
| 144 | + |
| 145 | +const authenticationFunctionInfo = `${deployer}::hello_world_authenticator:authenticate`; |
| 146 | +``` |
| 147 | + |
| 148 | +### 3. (Optional) Check if Account Abstraction is Enabled |
| 149 | + |
| 150 | +Before you ask them to enable account abstraction, you can check if the account has account abstraction enabled by calling the `isAccountAbstractionEnabled` function. |
| 151 | +This will return a boolean value indicating if the account has account abstraction enabled. |
| 152 | + |
| 153 | +```ts |
| 154 | +const accountAbstractionStatus = await aptos.account.isAccountAbstractionEnabled({ |
| 155 | + accountAddress: alice.accountAddress, |
| 156 | + authenticationFunction, |
| 157 | +}); |
| 158 | + |
| 159 | +console.log("Account Abstraction status: ", accountAbstractionStatus); |
| 160 | +``` |
| 161 | + |
| 162 | +### 4. Enable the Authentication Function |
| 163 | + |
| 164 | +Assuming that the account does not have account abstraction enabled, you need to enable the authentication function for the account. This can be done by calling |
| 165 | +the `enableAccountAbstractionTransaction` function. This creates a raw transaction that needs to be signed and submitted to the network. In this example, `alice` |
| 166 | +will be the account that will be enabled. |
| 167 | + |
| 168 | +```ts |
| 169 | +const transaction = aptos.abstraction.enableAccountAbstractionTransaction({ |
| 170 | + accountAddress: alice.accountAddress, |
| 171 | + authenticationFunction: `${deployer}::hello_world_authenticator:authenticate`, |
| 172 | +}); |
| 173 | + |
| 174 | +const pendingTransaction = await aptos.signAndSubmitTransaction({ |
| 175 | + transaction, |
| 176 | + signer: alice.signer, |
| 177 | +}); |
| 178 | + |
| 179 | +await aptos.waitForTransaction({ hash: pendingTransaction.hash }); |
| 180 | + |
| 181 | +console.log("Account Abstraction enabled for account: ", alice.accountAddress); |
| 182 | +``` |
| 183 | + |
| 184 | +<details> |
| 185 | +<summary> |
| 186 | + <b>Wallet Adapter Example</b> |
| 187 | +</summary> |
| 188 | + |
| 189 | +<Callout> |
| 190 | + If you are using the wallet adapter, you can use the `signTransaction` function to sign the transaction before submitting it to the network. |
| 191 | +</Callout> |
| 192 | + |
| 193 | +```tsx |
| 194 | +export default function useEnableAbstraction() { |
| 195 | + const { account, signTransaction } = useWallet(); |
| 196 | + |
| 197 | + return { |
| 198 | + enableAbstraction: async () => { |
| 199 | + if (!account) return; |
| 200 | + |
| 201 | + // Note: The Aptos client must be defined somewhere in the application. |
| 202 | + const transaction = aptos.abstraction.enableAccountAbstractionTransaction({ |
| 203 | + accountAddress: account.address, |
| 204 | + authenticationFunction: `${deployer}::hello_world_authenticator:authenticate`, |
| 205 | + }); |
| 206 | + |
| 207 | + const senderAuthenticator = await signTransaction(txn); |
| 208 | + |
| 209 | + const pendingTxn = await aptos.transaction.submit.simple({ |
| 210 | + transaction: txn, |
| 211 | + senderAuthenticator, |
| 212 | + }); |
| 213 | + |
| 214 | + return await aptos.waitForTransaction({ hash: pendingTxn.hash }); |
| 215 | + } |
| 216 | + } |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +</details> |
| 221 | + |
| 222 | +### 5. Create an Abstracted Account |
| 223 | + |
| 224 | +Once the authentication function is enabled, you can create an abstracted account object for signing transactions. You must provide the authentication function that will be used to verify the transaction |
| 225 | +and a `signer` function that will be used to sign the transaction. The `signer` function is responsible for generating the authenticator that will be passed to the authentication function. |
| 226 | + |
| 227 | +```ts |
| 228 | +const abstractedAccount = new AbstractedAccount({ |
| 229 | + accountAddress: alice.accountAddress, |
| 230 | + signer: () => new TextEncoder().encode("hello world"), |
| 231 | + authenticationFunction: `${deployer}::hello_world_authenticator:authenticate`, |
| 232 | +}); |
| 233 | +``` |
| 234 | + |
| 235 | +### 6. Sign and Submit a Transaction using the Abstracted Account |
| 236 | + |
| 237 | +Once you have created the abstracted account, you can use it to sign transactions normally. It is important that the `sender` field in the transaction |
| 238 | +is the same as the abstracted account's address. |
| 239 | + |
| 240 | +```ts |
| 241 | +const coinTransferTransaction = new aptos.transaction.build.simple({ |
| 242 | + sender: abstractedAccount.accountAddress, |
| 243 | + data: { |
| 244 | + function: "0x1::coin::transfer", |
| 245 | + typeArguments: ["0x1::aptos_coin::AptosCoin"], |
| 246 | + functionArguments: [alice.accountAddress, 100], |
| 247 | + }, |
| 248 | +}); |
| 249 | + |
| 250 | +const pendingCoinTransferTransaction = aptos.transaction.signAndSubmitTransaction({ |
| 251 | + transaction: coinTransferTransaction, |
| 252 | + signer: abstractedAccount, |
| 253 | +}); |
| 254 | + |
| 255 | +await aptos.waitForTransaction({ hash: pendingCoinTransferTransaction.hash }); |
| 256 | + |
| 257 | +console.log("Coin transfer transaction submitted! ", pendingCoinTransferTransaction.hash); |
| 258 | +``` |
| 259 | + |
| 260 | +### 7. Conclusion |
| 261 | + |
| 262 | +To verify that you have successfully sign and submitted the transaction using the abstracted account, you can use the explorer to check the transaction. If the |
| 263 | +transaction signature contains a `function_info` and `auth_data` field, it means you succesfully used account abstraction! |
| 264 | + |
| 265 | + |
| 266 | + |
| 267 | +</Steps> |
| 268 | + |
| 269 | +## Management Operations |
| 270 | + |
| 271 | +If you want to disable account abstraction for an account, you can use the `disableAccountAbstractionTransaction`. If you do not specify an authentication function, |
| 272 | +the transaction will disable all authentication functions for the account. |
| 273 | + |
| 274 | +```ts |
| 275 | +const transaction = aptos.abstraction.disableAccountAbstractionTransaction({ |
| 276 | + accountAddress: alice.accountAddress, |
| 277 | + /** |
| 278 | + * The authentication function to be disabled. If left `undefined`, all authentication functions will be disabled. |
| 279 | + */ |
| 280 | + authenticationFunction, |
| 281 | +}); |
| 282 | +``` |
| 283 | + |
| 284 | +## Application User Experience |
| 285 | + |
| 286 | +Applications that want to leverage account abstraction will want to provide a user experience that allows users to check if the account has account abstraction enabled, |
| 287 | +and to enable it, if it is not enabled. |
| 288 | + |
| 289 | + |
| 290 | +Below is a diagram of the UX flow for enabling account abstraction. |
| 291 | + |
| 292 | + |
0 commit comments