Context
With the introduction of account linking on mainnet, several concerns have been raised by the community around shared access to accounts with non-custodial wallets. While the currently proposed linked account standard technically enables Hybrid Custody by enabling and maintaining shared access on accounts, the proposal does not take into account that regulatory obligations are vastly different between users and custodial parties.
The crux of the issue this proposal seeks to address is the risk exposure custodial parties incur by sharing unrestricted access with secondary parties.
The question then is, how do we enable users to have real ownership over their hybrid custody accounts while empowering builders to prevent regulatory obligations that may come with that shared access?
Given the importance of a Hybrid Custody solution that works for everyone, we’ve developed an initial technical implementation that we’re opening up for consideration and feedback early in the development loop.
Problem
First, what’s the problem? Simply, the issue is that many projects have gone through great pains to prevent access to assets (e.g. FungibleTokens) in app-custodied accounts. Opening up unrestricted access to users by linking their accounts allows for another party to circumvent those preventative measures, potentially exposing app developers to the liabilities involved with custody of those assets in linked app accounts. For developers to adopt Hybrid Custody, the ideal approach is to provide a solution which avoids exposure to any regulatory risk at all or, at minimum, empowers developers to build solutions that obviate those risks themselves.
Proposed Solution
Overview
We’re suggesting a technical solution that empowers dapps to maintain self-defined restrictions on their access to linked accounts. This allows dapps to continue interacting with linked app accounts on behalf of a user while eliminating the possibility that they will inadvertently have access to undesirable assets so long as those restrictions are defined where necessary. Again this is a potential technical solution early in development, and there’s plenty of flexibility in the level of restriction - you might adopt this and inadvertently allow Capabilities that still leave you open to regulatory risk if you don’t properly restrict them, so how you implement that restriction is still for you to discern.
TL;DR: Enable dapp developers to restrict their own access on linked accounts
High-Level Design
On the left, you can see that the user has unrestricted access to a linked child account. This link is enabled by an AuthAccount Capability wrapped and stored in the parent account (more details can be found in FLIP#72). Wallet providers would be able to query for a user’s linked accounts as well as the contents of those accounts, and dapps like marketplaces can build seamless experiences such as NFT listings, sales, etc. leveraging those linked accounts.
On the other side, you see the dapp’s backend account maintains access on that child account as well. However, the dapp’s access on that account is restricted. This restriction is self-imposed at the time of account linking. Also of note is that the dapp does not have key access on the child account. To categorically avoid any risk exposure, it’s critical that the dapp must revoke their key on the child account in the same transaction in which they link the parent account if they custodied a key on the account to begin with.
Low-Level Design
As noted above, there will now be two sides to an account link - the user-managed and dapp-managed accounts. We’ll focus on each side of the link before putting them together.
Let’s not dive too deep here since this side of the link is already covered in FLIP#72 with implementation in this PR.
Essentially, the unrestricted AuthAccount Capability on the child account is wrapped in an NFT and placed in the user’s LinkedAccounts.Collection
. The NFT also maintains a private Capability on the child account’s Handler
resource which identifies the child account, its parent, and resolves metadata about the purpose of the account.
On the dapp side of things, we have the restricted AuthAccount Capability. This restricted Capability is wrapped in an AccessPoint
along with allowed Capability types, corresponding paths, and a dapp developer-defined CapabilityValidator
. This validator ensures that any requests for Capabilities from the child account via the AccessPoint
only ever return types that the developer intended to retrieve.
Why are the type guarantees granted by a validator important? Well, if a user were to alter the underlying Capability at a path to say a FungibleToken.Vault
, that would be a problem for a developer attempting to ensure their FungibleToken access was restricted. Since the validator is developer-defined, it enables a) a generic solution and b) customization based on use-case, jurisdiction, and risk tolerances.
The dapp managed account (shown above as custodied by backend KMS) stores an Accessor
on the child account’s AccessPoint
, providing an easy way to retrieve allowed Capabilities from the child account.
As mentioned previously, if the dapp originally custodied a key on the child account, they should ensure that key is revoked in the same transaction as when parent-child account linking occurs. Otherwise, the restrictions pictured above are useless.
An additional note about the developer-defined CapabilityValidator
- any dapp utilizing a solution where the dapp controls the restriction contract (in this case the CapabilityValidator
implementation) and retains the ability to make updates to it in the future should independently evaluate whether any legal and regulatory issues will arise from the retention of such ability.
Putting it all together, the user has a Collection enabling the one-to-many unrestricted access on linked accounts while the dapp has a single accessor enabling restricted access on a single child account.
In the end, the idea is that this will enable users to have real ownership over their Hybrid Custody accounts, and developers have a mechanism to ensure their control and ownership over those accounts is limited and in compliance with their local regulations. All this, of course, while still facilitating the seamless UX we’re all after.
Process
Let’s take a look at this amended account linking & capability retrieval process in practice:
-
CapabilityValidator
Definition & Deployment - Before enabling account linking, the developer would have defined their ownCapabilityValidator
implementation and deployed the defining contract. This definition enforces the desired restrictions that will be placed on the dapp’s access to any future linked child accounts. -
Account Linking - Providing the user unrestricted access to the child account while setting restrictions on the dapp’s access
- Create an AuthAccount Capability for both the parent & dapp accounts
- Link the app-created & custodied account to the user’s main account, configuring a
LinkedAccounts.Collection
if needed - Revoke the app-custodied key on the now user-linked child account
- Create an
AccessPoint
in the child account, passing the child account AuthAccount Capability & previously definedCapabilityValidator
, before linking a public & privateAccessPoint
Capabilities - Publish the private
AccessPoint
Capability for the dapp’s backend account to claim
-
Claim Access Point Capability - Claiming the restricted access provided in the previous transaction
- Claim the published
AccessPoint
Capability to maintain the dapp’s restricted access on the child account - Wrap the claimed Capability in an
Accessor
and saving the resource in storage
- Claim the published
-
Retrieve Allowed Capabilities on the Child Account - Getting a Capability required by the dapp act on behalf of the user with guarantees on the type of Capability retrieved
- Reference the
Accessor
in storage, and retrieve a reference to the child account’sAccessPoint
- Call for the requested Capability
- If the Capability is not allowed or the
CapabilityValidator
in the child account’sAccessPoint
determines the Capability Type at the corresponding path is not the Type intended, nothing is returned - Otherwise, the
AccessPoint
successfully returns the requested Capability to the calling dapp account
- If the Capability is not allowed or the
- Reference the
Resources & Example
Take a look at this branch for an example implementation of the constructs in this diagram. Of note are the following:
- ScopedAccounts Contract
- ExampleValidators Contract
- Account linking transactions with ScopedAccounts implementation
- Example Accessor transaction retrieving a Capability from a child account
- Diagram board with comments
As always, all feedback is welcome and appreciated. Thanks all!