diff --git a/ARCs/arc-0058.md b/ARCs/arc-0058.md new file mode 100644 index 000000000..1fa92e8c7 --- /dev/null +++ b/ARCs/arc-0058.md @@ -0,0 +1,538 @@ +--- +arc: 58 +title: Plugin-Based Account Abstraction +description: Account abstraction using stateful applciations +author: Joe Polny (@joe-p), Kyle Breeding aka krby.algo (@kylebeee) +discussions-to: https://github.com/algorandfoundation/ARCs/issues/269/files +status: Draft +type: Standards Track +category: ARC +created: 2024-01-08 +requires: 4 +--- + +## Abstract +This ARC proposes a standard for using stateful applications and rekey transactions to enable account abstraction on Algorand. The abstracted account is controlled by a single stateful application which is the auth address of the abstracted account. Other applications can be used as plugin to provide additional functionality to the abstracted account. + +## Motivation +Manually signing transactions for every dApp interaction can be rather fatiguing for the end-user, which results in a frustrating UX. In some cases, it makes specific app designs that require a lot of transactions borderline impossible without delegation or an escrow account. + +Another common point of friction for end-users in the Algorand ecosystem is ASA opt-in transactions. This is a paticularly high point of friction for onboarding new accounts since they must be funded and then initiate a transaction. This standard can be used to allow mass creation of non-custodial accounts and trigger opt-ins on their behalf. + +## Specification + +### Definitions +**External Owned Account (EOA)** - An account that is _not_ controlled by a smart contract. + +**Abstracted Account** - An account that has functionality beyond a typical keypair-based account. + +**Abstracted Account App** - The stateful application used to control the abstracted account. This app's address is the `auth-addr` of the abstracted account. + +**Plugin** - An additional application that adds functionality to the **Abstracted Account App** (and thus the **Abstracted Account**). + +**Admin** - An account, separate from the **Abstracted Account**, that controls the **Abstracted Account App**. In particular, this app can initiate rekeys, add plugins, and transfer admin. + + +### [ARC-4](./arc-0004.md) Methods + +An Abstracted Account App that adheres to this standard **MUST** implement the following methods + +```json + "methods": [ + { + "name": "arc58_changeAdmin", + "desc": "Attempt to change the admin for this app. Some implementations MAY not support this.", + "args": [ + { + "name": "newAdmin", + "type": "account", + "desc": "The new admin" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_getAdmin", + "desc": "Get the admin of this app. This method SHOULD always be used rather than reading directly from statebecause different implementations may have different ways of determining the admin.", + "args": [], + "returns": { + "type": "address" + } + }, + { + "name": "arc58_verifyAuthAddr", + "desc": "Verify the abstracted account is rekeyed to this app", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_rekeyTo", + "desc": "Rekey the abstracted account to another address. Primarily useful for rekeying to an EOA.", + "args": [ + { + "name": "addr", + "type": "address", + "desc": "The address to rekey to" + }, + { + "name": "flash", + "type": "bool", + "desc": "Whether or not this should be a flash rekey. If true, the rekey back to the app address must done in the same txn group as this call" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_rekeyToPlugin", + "desc": "Temporarily rekey to an approved plugin app address", + "args": [ + { + "name": "plugin", + "type": "application", + "desc": "The app to rekey to" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_rekeyToNamedPlugin", + "desc": "Temporarily rekey to a named plugin app address", + "args": [ + { + "name": "name", + "type": "string", + "desc": "The name of the plugin to rekey to" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_addPlugin", + "desc": "Add an app to the list of approved plugins", + "args": [ + { + "name": "app", + "type": "application", + "desc": "The app to add" + }, + { + "name": "allowedCaller", + "type": "address", + "desc": "The address of that's allowed to call the appor the global zero address for all addresses" + }, + { + "name": "end", + "type": "uint64", + "desc": "The timestamp when the permission expires" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_removePlugin", + "desc": "Remove an app from the list of approved plugins", + "args": [ + { + "name": "app", + "type": "application", + "desc": "The app to remove" + }, + { + "name": "allowedCaller", + "type": "address" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_addNamedPlugin", + "desc": "Add a named plugin", + "args": [ + { + "name": "name", + "type": "string", + "desc": "The plugin name" + }, + { + "name": "app", + "type": "application", + "desc": "The plugin app" + }, + { + "name": "allowedCaller", + "type": "address" + }, + { + "name": "end", + "type": "uint64" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "arc58_removeNamedPlugin", + "desc": "Remove a named plugin", + "args": [ + { + "name": "name", + "type": "string", + "desc": "The plugin name" + } + ], + "returns": { + "type": "void" + } + } + ] +``` + +### Plugins + +Plugins are applications that the Abstracted Account App **MUST** rekey to when `rekeyToPlugin` or `rekeyToNamedPlugin` is called. After a plugin has been rekeyed to, the abstracted account **MUST** be rekeyed back to the abstracted account application. When and how this rekey is done does not matter, but it **MUST** be verified by a call `verifyAuthAddr` as the last transaction in the group OR the last transaction in the group must be an explicit rekey transaction. + +### Plugin Permissions + +When adding a plugin, the the admin can specify an end time and a specific address that is allowed to call `rekeyToPlugin` for that specific plugin. Using the zero address will allow anyone to use the plugin. If `global LatestTimeStamp` has past the specified end time, the `rekeyToPlugin` call **MUST** fail. If the permitted address is not the zero address, the `rekeyToPlugin` call **MUST** fail. + +#### Named Plugins + +The admin can optionally add a named plugin to their abstracted account application. Any name that matches the regex `/^ARC\d+$/` **MUST** implement the interface(s) described in the respective ARC. The ARC number **MUST NOT** have any leading 0s. + +### Wallet and Application Support + +#### Adding Plugins + +An application may ask a user to add a plugin to their Abstracted Account App. The wallet **MUST** show the user the requested permitted caller, end time, and plugin app ID. Wallets **MAY** have a database of known plugins to describe the plugin in a more human-friendly way, but the exact details of this implementation is outside the scope of this ARC. + +#### Viewing Plugins + +For a given Abstracted Account, a wallet **SHOULD** allow the user to view all of the plugins added to their Abstracted Account App. Wallets also **SHOULD** provide the user a way to manually add and remove plugins from their Abstracted Account App. + + +#### Supporting EOA Rekeys + +If a user connects to an app with an Abstracted Account the app **SHOULD** allow the user to easily sign transactions with an externally owned account. It is easy for the admin to manually call `rekeyTo` prior to interacting with an app, but this does not gurantee the user will rekey back to the Abstracted Account (this breaking plugin functionality). As such, to improve user experience, applications **SHOULD** be able to send transactions groups that start with a `rekeyTo` call that rekeys to the admin admin account with `flash` set to true. Because `flash` is set to true, the last transaction in the group **MUST** be a rekey from the admin address to the Abstracted Account address. + +This requires wallets to be able to tell apps that the connected account is an Abstracted Account. + +## Rationale + +### App vs Logic Sig +There have similar propsoals for reducing end-user friction, such as [ARC-47](./arc-0047.md) which enables safer usage of delegated logic signatures. The major downside of logic signatures is that they are not usable by smart contracts. This severely limits composability and potential use cases. + +### Plugins +Rather than constantly updating the approval program of the abstracted account application to add functionality, it is safer and easier to simply add additional apps that enable the desired functionality. This also gives the end-user more control over what various dApps can do with their account at any time. + +### Plugin Permisions + +A common use case for plugins will be end-users allowing specific apps to perform actions on their account. As such, implementing this in the Abstracted Account App allows for wallets and other ecosystem tools to easy display permissions to end users. The end time is also useful to ensure a user only enables a plugin for the time that it would be useful for them. The concept of plugins is similar to approvals on EVM chains and there have been cases where old approvals became an attack vector. + +## Backwards Compatibility +Existing Algorand accounts can transition to an abstracted account by creating a new abstracted account application and setting the address to their current address. This requires them to create a new account to act as the admin. + +End-users can use an abstracted account with any dApp provided they rekey the account to an externally owned account. + +## Test Cases + +TODO: Some functional tests are in this repo https://github.com/joe-p/account_abstraction.git + +## Reference Implementation + +### Abstracted App + +```ts +import { Contract } from '@algorandfoundation/tealscript'; + +type PluginsKey = { application: Application; allowedCaller: Address }; + +export class AbstractedAccount extends Contract { + /** Target AVM 10 */ + programVersion = 10; + + /** The admin of the abstracted account */ + admin = GlobalStateKey
({ key: 'a' }); + + /** The address this app controls */ + controlledAddress = GlobalStateKey({ key: 'c' }); + + /** + * The apps and addresses that are authorized to send itxns from the abstracted account, + * The key is the appID + address, the value (referred to as `end`) + * is the timestamp when the permission expires for the address to call the app for your account. + */ + plugins = BoxMap