Skip to content

Starter template for working with HybridCustody contract suite

Notifications You must be signed in to change notification settings

Outblock/hybrid-custody-scaffold

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 

Repository files navigation

👋 Welcome Flow Developer!

ℹ️ Be sure to check out the Hybrid Custody docs for more info and the source repo for the full code and contribution history. If you run into any issues with this scaffold, please create an issue here

This scaffold was created to help app developers get started building and exploring a Hybrid Custody project. In this scaffold, you'll find a simplified template of the contents in @onflow/hybrid-custody.

If building a production system on these contracts, you might consider using Git Submodules to ensure your dependencies remain up to date.

🔨 Getting started

Getting started can feel overwhelming, but we are here for you. Depending on how accustomed you are to Flow, here's a list of resources you might find useful:

  • Cadence documentation: here you will find language reference for Cadence, which will be the language in which you develop your smart contracts,
  • Visual Studio Code and Cadence extension: we suggest using Visual Studio Code IDE for writing Cadence with the Cadence extension installed, that will give you nice syntax highlitning and additional smart features,
  • SDKs: here you will find a list of SDKs you can use to ease the interaction with Flow network (sending transactions, fetching accounts etc),
  • Tools: development tools you can use to make your development easier,
  • Account inspection: Account inspector for all networks, including your localnet!

NFT Resources:

  • flow-nft: home of the Flow NFT standards, contains utility contracts, examples, and documentation,
  • nft-storefront: NFT Storefront is an open marketplace contract used by most Flow NFT marketplaces,
  • Flow NFT Catalog: list of NFT contracts on Flow, can be a valuable source to compose new projects or use as example,

📦 Project Structure

Your project comes with some standard folders which have a special purpose:

  • cadence/ inside here is where your Cadence smart contracts code lives
    • contracts/ location for Cadence contracts go in this folder
    • scripts/ location for Cadence scripts goes here
    • transactions/ location for Cadence transactions goes in this folder
  • flow.json configuration file for your project, you can think of it as package.json. We'll dig more into this in a bit.

🤔 What is Hybrid Custody?

📚 Read the full docs here

The Hybrid Custody model on Flow enables developers to provide seamless onboarding and in-app experiences while simultaneously empowering users with real ownership and self-sovereignty. With this new custodial model, developers can deliver the benefits of both app and self-custody in a unified experience.

Hybrid Custody High-Level

Hybrid Custody grants users access to their linked child accounts without needing to interface with the child account's custodial app, and the custodial app can interact with the relevant assets in the child account on behalf of the user in a frictionless UX free from transaction prompts.

🧭 The Path to Hybrid Custody

  1. The app creates, funds, and manages access to a Flow account initialized on user onboarding. This enables the app to abstract away the complexities of interacting with smart contract powered applications, and focus on creating slick user experiences behind familiar Web2 authentication and fiat denominated payments.
  2. Once a user returns to the app with a self-custodial wallet, they can authenticate their wallet-managed account in the app, allowing the app to give the user's main account delegated access to the app managed account (albeit with some developer-defined restrictions).
  3. Upon linking, the user's main account - now the "parent" account - adds the app created account - now the "child" account - to a collection of all linked child accounts. At this point, Hybrid Custody is reached!

👨‍💻 Start Developing

⚠️ Note that the contracts in this scaffold are still under development and may undergo breaking changes. You can stay up to date on advancements and changes by following the source repo.

Install Flow CLI

From your workspace directory, setup your Flow project from a scaffold

flow setup <YOUR_PROJECT_NAME> --scaffold

You'll be asked to select the scaffold number for this scaffold

✗ Enter the scaffold number:

Go ahead and enter the scaffold number for Hybrid Custody Project: [4] Hybrid Custody Project

✔ Enter the scaffold number: 4

Now, to get emulator running and deploy contracts. Change to your project's directory

cd <YOUR_PROJECT_NAME>

Run the emulator

flow emulator --contracts

ℹ️ We use --contracts flag to include additional contracts we can then easily import into our project.

Deploy the scaffold project contracts

flow deploy

You now have a running emulator instance with all project contracts deployed to emulator-account. We know this is the account where contracts were deployed because of our deployments field in our flow.json file:

{
    "deployments": {
        "emulator": {
            "emulator-account": [
                "AddressUtils",
                "StringUtils",
                "ArrayUtils",
                "HybridCustody",
                "CapabilityDelegator",
                "CapabilityFilter",
                "CapabilityFactory",
                "FTProviderFactory",
                "NFTProviderFactory",
                "NFTProviderAndCollectionFactory",
                "NFTCollectionPublicFactory"
            ]
        }
    }
}

ℹ️ These contracts are not deployed to a single contract on testnet/mainnet, but are done so here for the sake of simplicity. Refer to contract aliases for contract deployment addresses.

🏎️ Interacting with HybridCustody

Now that the project contracts are deployed on a locally running emulator instance, we can start to interact with them. Let's check out a progressive onboarding flow.

What is progressive onboarding? It's the process of onboarding a new-to-Web3 user on to your application, abstracting away onchain interactions, and on through to Hybrid Custody once they link their accounts.

ℹ️ Make sure that you've performed the steps above and your emulator is running with contracts deployed.

Create Dev Account

There are a number of ways you can create accounts for the purposes of your application. You could use a custodial service, craft your own backend account creation and custodial service, or simply run a transaction from an account you control. For simplicity and since everything boils down to the fundamentals, we'll continue by simply running an account creation transaction.

In order to create accounts on Flow, you'll need an account to begin with to both run the transaction and fund account creation. So let's do that via flow CLI:

flow accounts create # account name: dev

Name the account dev and select Emulator as your network.

If you take a look at your flow.json, you should see the new account under the accounts field like so:

"dev": {
    "address": "e03daebed8ca0615",
    "key": "5ea518f98a0c4f6341675e0b1596925cb331856b051744850bfeabf3e6ecddb5"
}

ℹ️ Note that your emulator environment tears down after it stops, so even though this dev account remains in your flow.json, it will only work for the length of the session in which is was created. Attempting to access this account in a new emulator instance will result in an error.

Lastly, we'll want to make sure this account has enough Flow balance to fund new account creation. The emulator-account is initialized with a sizable balance which we can transfer to the dev account - let's transfer 1000.0 $FLOW:

fts cadence/transactions/flow-token/transfer_flow.cdc e03daebed8ca0615 1000.0

Configure CapabilityFilter & CapabilityFactory Resources

As noted in the full docs, the HybridCustody contract supports restricted access delegation. This means developers are empowered to define limitations on the level of access a parent account can have on app-managed Hybrid Custody accounts.

Constructs in CapabilityFilter and CapabilityFactory contracts are utilized to define and enforce the Capability Types accessible from linked child accounts. The simplest understanding of their respective roles is that a Filter resource defines the accessible Types and Factory structs define the access pattern to retrieve those Capability Types from an account.

For concrete examples of each, see AllowlistFilter and NFTCollectionPublicFactory

ℹ️ To learn more about all these components, see the Account Model section of the full docs.

Before linking the child & parent accounts, we'll need to first configure the CapabilityFilter.Filter and CapabilityFactory.Manager resources. These will define accessible types and access patterns (respectively) from the child account. We'll configure an AllowlistFilter, but consider AllowAllFilter and DenylistFilter as other pre-built options if you'd like to enforce restrictions on allowable Types, or you can roll you own!

⚠️ Filters are important as they restrict the scope of access a parent account can have on a child account. Sharing account access comes with technical, business and regulatory risks that devs should be careful to mitigate by scoping access to the minimum necessary (Principle of Least Privilege). The types you include in your filter will vary according to your use case, but most apps will be well served by allowing NFT collection access demonstrated in the following transaction.

For our purposes, we'll configure a Filter and Manager to enable access to NFT-related Capabilities, and do so in a single transaction:

flow transactions send cadence/transactions/dev-setup/setup_nft_filter_and_factory_manager.cdc --signer dev \
    f8d6e0586b0a20c7 \
    ExampleNFT \
    --signer dev

This transaction sets up an AllowlistFilter, enabling access to ExampleNFT Collection Capabilities as well as a Manager specifying the access pattern so those Capabilities can be returned as castable types. This means that any parent of a child account using these will only be able to access the ExampleNFT resources in those child accounts.

At the end of this, the dev account has CapabilityFilter.AllowAllFilter and a CapabilityFactory.Manager with a number of Factory implementations configured. We'll use Capabilities on each when we link the child and parent accounts.

ℹ️ You can inspect the developer account storage in FlowView, a super useful tool! Click on the link and search for the address you want to inspect. At this point, you should see the AllowAllFilter and Manager resources at their derived paths.

Walletless Onboarding

Now that we have a funded dev account and we've configured it with the necessary resources & Capabilities, we can create new app-controlled accounts on Flow. Walletless onboarding is really pretty simple - you're creating an account, funding its creation and taking care of custody on behalf of your user.

But first, we'll need to generate the key to add to the account.

flow keys generate

This will output a public/private key pair we'll use for the account we're about to create. Of course, in your app, you could handle custody any number of ways - store locally in secure enclave, key sharding protocols, KMS, etc. For our purposes here, we'll deal with this manually in a minute.

Copy the generated public key and add it as an argument in the following command along with the initial funding amount to add to the account we're going to create:

flow transactions send cadence/transactions/hybrid-custody/onboarding/walletless_onboarding.cdc <PUBLIC_KEY> <INITIAL_FUNDING_AMOUNT> --signer dev

ℹ️ If you need testnet $FLOW, check out the testnet faucet 🚰

At the end of this transaction, a new account will be created with the provided public key added at full weight (1000.0).

Time to figure out the address of the account we just created. A number of events will be emitted, including flow.AccountsCreated. You can either look for this event manually in your terminal, or you can run the following command and refer to the event emitted in the latest block:

flow events get flow.AccountCreated

ℹ️ Your app will want to query for this event from the submitted transaction. Check out this account creation example using FCL for more context on creating a new account & retrieving its address.

Now that we know the new account's address, we can add it to our flow.json. Of course, your app would manage custody much more elegantly. Add the following to your flow.json's accounts field. Recall the private key we generated previously.

"child": {
    "address": "0x045a1763c93006ca",
    "key": "<GENERATED_PRIVATE_KEY>"
}

You did it - you just completed walletless onboarding!

Recall we:

  1. Generated a public/private key pair
  2. Submitted a transaction that:
    1. Created a new account
    2. Added the public key to that account
    3. Added initial funds to the new account
  3. Custodied the corresponding private key

The child account in this instance would be an app-managed account. Next, we'll simulate the process of your app's user creating their own wallet-managed account that will be linked to the account we just created to achieve Hybrid Custody. But first we need to prepare the dev account.

Create Parent Account

This step is pretty easy. Simply run:

flow accounts create # account name: parent

Name the account parent and select Emulator as your network. Again, you'll find this account automatically added to your flow.json.

In the progressive onboarding flow, this step emulates your user going to a wallet provider and creating self-managed account. This is the account that will share access on the app-managed child account.

Link Parent & Child Accounts

Time to link accounts and achieve Hybrid Custody!

There are two ways we could do this. If we have both accounts sign a single transaction, the link can be done in that one transaction. However, we could also utilize the AuthAccount.Inbox to perform an async account link by first publishing a Capability the parent account later claims.

ℹ️ As the app developer, you can configure a Display view when configuring and/or linking a child account. This enables easy identification of the child account's association in the parent account's wallet. However mechanisms to authenticate child account origins are in discussion, and until then wallet providers & users should beware that child account metadata is a matter of convenience. Associated metadata is not authenticated or immutable.

Publish & Claim

We can configure any necessary resources (OwnedAccount) in the child account while publishing a Capability for the parent account. In the process, we'll also make sure a MetadataViews.Display is associated with the account. This consolidates a number of steps into a single transaction:

flow transactions send cadence/transactions/hybrid-custody/setup_owned_account_with_display_and_publish_to_parent.cdc \
    120e725050340cab e03daebed8ca0615 e03daebed8ca0615 NAME DESCRIPTION THUMBNAIL_URL \
    --signer child

Once published, we sign with the parent account to claim the Capability. Note that the parent can set a Filter of its own to prevent access to Capabilities it doesn't want. For our purposes, we'll pass nil, but the feature might be useful for custodial wallet providers.

Let's now run the transaction, signing as the parent account to claim the

flow transactions send cadence/transactions/hybrid-custody/redeem_account.cdc \
    045a1763c93006ca nil nil \
    --signer parent

After both transactions have been sent, Hybrid Custody has been achieved, giving the parent account access to the child account according to the rules defined in the Filter and accessible by the Factories.

Multi-Signed

Let's prepare a single multi-signed transaction that will link the child and parent accounts. Note that the parent can set a Filter of its own to prevent access to Capabilities it doesn't want. For our purposes, we'll pass nil, but the feature might be useful for custodial wallet providers that wish to prevent access to fungible tokens.

First step is to build the transaction:

flow transactions build cadence/transactions/hybrid-custody/setup_multi_sig.cdc \
    <PARENT__FILTER_ADDRESS?> <CHILD_ACCOUNT_FACTORY_ADDRESS> <CHILD_ACCOUNT_FILTER_ADDRESS> \
    --proposer child \
    --payer child \
    --authorizer child \
    --authorizer parent \
    --filter payload \
    --save setup_multi_sig

With the transaction built, we need to sign with both signers:

flow transactions sign setup_multi_sig --signer child --signer parent --filter payload --save setup_multi_sig

Lastly, we'll send the signed transaction:

flow transactions send-signed setup_multi_sig

ℹ️ Again, you're encouraged to inspect account storage using FlowView. It can be helpful to understand the components involved in making Hybrid Custody function.

We can validate that the accounts have been linked by running a quick script returning parent's linked Hybrid Custody accounts.

flow scripts execute cadence/scripts/hybrid-custody/get_child_addresses.cdc 120e725050340cab

On the other end of the link, we can get the parent addresses of a child account:

flow scripts execute cadence/scripts/hybrid-custody/get_parents_from_child.cdc 045a1763c93006ca

Blockchain-Native Onboarding

Walletless onboarding lends a fantastic user experience for Web3 newcomers, but what about crypto-native users?

Blockchain-native onboarding enables a user to log in with their wallet-managed account and link with an app-managed account from the get go. This means the user starts your app with a Hybrid Custody account, enabling them to manage accessible in-app assets from their main account while maintaining a seamless in-app experience.

⚠️ Before we can submit the blockchain-native onboarding transaction, we need to make sure the CapabilityFilter and CapabilityFactory resources are configured properly. If you haven't already, check out that step above before continuing.

You'll also want to ensure you've created a parent account to link to.

Now we can run the blockchain-native onboarding transaction, signing as the dev and parent accounts.

First we generate a public/private key pair to assign to the account we'll create in the transaction.

flow keys generate

You'll want to copy the public key as an argument and build the following multi-signed transaction:

flow transactions build cadence/transactions/hybrid-custody/onboarding/blockchain_native.cdc \
    <PUBLIC_KEY> <INITIAL_FUNDING_AMOUNT> <FACTORY_ADDRESS> <FILTER_ADDRESS> \
    --proposer dev \
    --payer dev \
    --authorizer parent \
    --authorizer dev \
    --filter payload \
    --save blockchain_native

Then we need to sign that transaction:

flow transactions sign blockchain_native --signer parent --signer dev --filter payload --save blockchain_native

And finally, send the signed transaction

flow transactions send-signed blockchain_native

You'll see a number of events emitted including account creation and account linking events. To validate the parent has the child account added, let's query against the parent account

flow scripts execute cadence/scripts/hybrid-custody/get_child_addresses.cdc <PARENT_ADDRESS>

If you ran through both onboarding tracks, you should see two addresses returned. Otherwise, just the one created and linked in the blockchain-native transaction will be present.

Sharing Arbitrary Capabilities

As we saw above, CapabilityFactory defines access patterns to retrieve castable Capabilities from accounts. However, your app may require sharing arbitrary Capabilities with a parent account that do not need to be casted. In this case, you can leverage the CapabilityDelegator configured in the linking process to share generic Capabilities with a parent account.

You can think of a Delegator as bucket to place generic Capabilities you want to share from child to parent account. It's worth noting that each parent of a ChildAccount has a partitioned Delegator as determined by the paths the underlying Delegator is stored and linked, derived by the parent's address.

First, let's configure an ExampleNFT Collection in the child account so we can later share a Capability to it with the linked parent:

flow transactions send cadence/transactions/example-nft/setup_full.cdc --signer child

We can now run a transaction that will add a private ExampleNFT Provider Capability into the CapabilityDelegator shared with the parent we linked above:

flow transactions send cadence/transactions/delegator/add_private_nft_collection.cdc <PARENT_ADDRESS> --signer child

ℹ️ If this was your app, you'd want to replace the Capability in the transaction we just ran with your app-specific Capability.

Now we can query the private Capability we added to the delegator:

flow scripts execute cadence/scripts/delegator/get_all_private_caps.cdc <PARENT_ADDRESS> <CHILD_ADDRESS>

You should see the ExampleNFT Capability returned, letting you know the Capability was successfully added as a private Capability in the Delegator partitioned for the parent account. In this case, the Capability was already accessible via the CapabilityFactory.Manager, but the same pattern could be used to delegate any other Capability.

Recap

After all of this, you've navigated through creating an app-custodied account, prepared filter rules, linked parent and child accounts to achieve Hybrid Custody, and finally delegated a private Capability!

Hybrid Custody Low-Level

Zooming out, this is the map of all you've configured. If your emulator is still running, you can inspect each account using FlowView to see each of the resources pictured above in their respective accounts.

As an app developer, you really only need to be concerned with the app-custodied account and the linking process (publish & claim or multi-signed). Once linked, the assets you provide access to in the child account may change, but properly installed filter rules will ensure the user's access to underlying storage is restricted.

The great news is that by enabling Hybrid Custody, standard resources used in your app can be leveraged across the whole ecosystem while you can focus on delivering stellar in-app experiences!

📚 Resources

Deployment Details

Network Address
Testnet 0x294e44e1ec6993c6
Mainnet 0xd8a7e05a7ac670c0

Hosted CapabilityFactory & CapabilityFilter Implementations

ℹ️ CapabilityFactory.Manager implementations and CapabilityFilter.AllowAllFilter have been deployed to the accounts below for generalized use cases to make account linking as easy as possible. These generalized implementations likely cover most use cases, but you'll want to weigh the decision to use them according to your risk tolerance and specific scenario.

Use Case Testnet Address Mainnet Address
NFT Capability Factories 0x1055970ee34ef4dc 0xee9ff4f07a2d6dad
FT Capability Factories 0x08bed9e8508ed20e 0x410aa603925923d9
NFT + FT Capability Factories 0x1b7fa5972fcb8af5 0x071d382668250606
AllowAllFilter 0xe2664be06bb0fe62 0x78e93a79b05d0d7d

About

Starter template for working with HybridCustody contract suite

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Cadence 100.0%