Developers
7/14/2021
Inside Flow: Cadence, The Language Made For Digital Assets
Flow

Background: Flow is a new blockchain originally designed and developed by Dapper Labs, the makers of CryptoKitties and NBA Top Shot. In this multi-part series, we will explore the different components of Flow blockchain from a technical perspective.

Cadence, the new programming language that makes smart contract development faster, safer
Flow Client Library (FCL), it is analogous to Web3.js on Ethereum, but built for the consumer audience in mind
Flow multi-node architecture, future-proof scaling for the mainstream adoption


An ever-growing stream of value is pouring into blockchain applications amidst ongoing crypto mass-adoption,  but the technical tools that handle these digital assets still lack maturity. Despite lengthy and costly audits, smart contracts often fail to deliver a sustainable security model that does justice to the amount of value stored in them, and oftentimes developers need to recreate the wheel just to achieve some basic functionality. There has been a lack of a programming language that treats digital assets with the importance they deserve, until now. 

Enter Cadence.

Cadence is the first language specifically designed for managing ownership of digital assets of value, like art, collectibles or cryptocurrencies on the blockchain. In Cadence, digital assets are first-class citizens, making their accidental loss or malicious duplication impossible while providing fine-grained and human-readable access control.

Cadence leverages a unique data model for digital assets: resources. Resources make the creation, transfer and storing of digital assets easier and more secure. In combination with capability-based access control this results in a language that is faster to learn, easier to audit, and more productive than any current alternative.

Building on the human-centred account model of Flow, Cadence introduces a direct-ownership model for digital assets that enables higher degrees of decentralisation and attack-resiliency.   Your assets are stored right where they belong: in your account.

Cadence was written with consumer-grade use cases in mind, built for scalability on top of its solid foundation of scientific research. It's been battle-tested at enormous scale as the backbone of NBA Top Shot, one of the fastest growing blockchain applications ever made with over one million registrations.

Beyond that, thousands of users interact daily with an ever growing number of Cadence-powered applications like marketplaces, games or exchanges, proving its durability and reliability. These benefits draw in new developers on a daily basis, allowing them to enjoy the benefits of Cadence’s improved security, productivity and readability.

The fact that other big players of the industry like Diem (Move) start leveraging the power of resources hints that the next generation of smart contract programming might dawn the era of resource-oriented languages, and Cadence is the first resource-oriented language ready for use on the blockchain today.

The challenges of digital assets

The biggest innovation that allowed blockchain technology to open up a new world of possibilities was the enabling of native digital value. On the blockchain, code is not merely a reference to some external asset — the code is the asset. Instead of being easily disposable, arbitrarily duplicable and easy to tamper with, data on the blockchain is valuable, something you can own. No matter if it’s NFTs, collectibles, cryptocurrencies or governance tokens, they’re all part of a new asset class: digital assets.

As exciting the prospects of this new evolution are, it’s equally worrisome how deeply the tools that manage digital assets on a daily basis are rooted in the the old world: Smart contract programming languages like Solidity treat digital assets and the immense value they might carry no different than arbitrarily mutable data structures, while storing them in a central ledger. A few lines of code are enough to illustrate the great risks that these languages bear for the handling of digital assets.

№1: Central ledgers create harmful honeypots

The following code snippet represents a simplified implementation of a fungible token in Solidity. This ERC-20-inspired fungible token smart contract consists of two functions: a mint function that allows for generating new tokens, and a send function, which enables money transfers to different addresses. 

contract AnyCoin {
  mapping (address => uint) public balance;
  
  function mint(address receiver, uint amount) {
    balance[receiver] += amount;
  }
  
  function send(address receiver, uint amount) {
    balance[msg.sender] -= amount;
    balance[receiver] += amount;
  }
}

The crucial question with regards to digital assets is: where and how are the token holdings of each person stored? The snippet shows that these token holdings are being represented by a data structure at the top of the contract: The mapping of balances. This data structure is a central ledger that maps each holder’s address to the specific token balance, effectively storing all digital assets in the smart contract itself.

mapping(address => uint) public balance;

These central mappings are oftentimes responsible for handling the equivalent value of billions of dollars at a time, as one glance at the most popular ERC-20 token contracts on Ethereum shows. Storing these huge amounts of digital assets in such a centralised manner creates central honeypots for hackers. Unfortunately, multi-million dollar hacks have become a sad reality for many teams and users: In 2020 alone, the total value lost through overall crypto crime hit $1.9 billion USD, with hacks amounting to a total of $513 million USD [source]. But this centralised storage is just one problem of languages like Solidity.

№2: Digital assets need reliable data models

Because the central mapping is nothing more than an arbitrarily mutable data structure that does not impose any rules for handling digital assets, there’s nothing stopping developers from mistakingly writing malicious code. To illustrate that point, let’s look at another implementation of a token contract.

contract BugCoin {
  mapping (address => uint) public balance;
  
  function mint(address receiver, uint amount) {
    balance[receiver] += amount;
  }
  
  function send(address receiver, uint amount) {
    balance[msg.sender] -= amount;
    // balance[receiver] += amount;
  }
}

Here, just one line of code was commented out in the send function ,  but it’s an essential one: A transfer of BugCoin would result in the balance of the sender being deducted , but the receiver’s balance would never increase. The value of any transaction of this contract would be lost permanently because of one missing line of code. Shockingly, there is nothing stopping that from happening in Ethereum.  Since the compiler won’t complain, this code would be good to go for deployment on test- or even mainnet, wiping out digital value and destroying users’ assets on every transaction made.

The permanent nature of smart contracts and the immense value they might carry puts huge responsibility and pressure on blockchain developers. As we have seen, the limits of existing smart contract programming languages do not make the programmer’s life easier. In fact, there’s even a lot more low-level code to take care of, like managing over- and underflow problems, or converting between different currency units. These constraints in a place that’s all about value  makes for a delicate mixture. This created a need for a language that treats digital assets with the importance they deserve.

Meet Cadence, the language made for digital assets.

Cadence: Resources to the rescue

Cadence eliminates most of the illustrated risks existing smart programming languages bear by  introducing a custom data model for digital assets: resources. Inspired by linear types, resources act as a supporting net for developers, making them aware of potential errors before they happen at runtime. 

Cadence developers immediately know when they’re dealing with resources, because the creation or transfer of resources necessitate the use of the move operator (<-), while their type is explicitly stated with its own symbol (@). These visual cues are key in eliminating human error in production and make Cadence code easy to read, write, learn and maintain.

But Cadence’s resources are not just syntactic sugar for existing data models,  they enforce strict rules in terms of allowed operations, so no digital assets falls victim to developer errors. Resources can only exist in one exact place at one exact time, making their malicious duplication or accidental deletion impossible, and increasing the overall safety in all processes that touch upon digital assets of value. 

Remember the malicious send function in our BugCoin contract? In Cadence, this kind of code would never have been compiled in the first place. That’s because the token holdings of a fungible token contract in Cadence are implemented with resources that implement strict rules of how they can be handled. Every token holder has their own vault resource for holdings of this token in their account, and this resource also has the withdraw and deposit methods. 

pub resource Vault: Provider, Receiver, Balance {
    // pub = publicly readable, internally writable
    pub var balance: UFix64

    init(balance: UFix64) {
        self.balance = balance
    }

    pub fun withdraw(amount: UFix64): @Vault {
        self.balance = self.balance - amount
        return <-create Vault(balance: amount)
    }
        
    pub fun deposit(from: @Vault) {
        self.balance = self.balance + from.balance
        destroy from
    }
}

The safety of value in transit is always given, because the amount of the transaction is put into a  temporary vault resource after withdrawal. This temporary vault can then be deposited in someone else’s deposit function, or explicitly deleted. But unlike the send function in the Solidity example, it’s impossible to lose the token vault by accident, because the compiler will throw an error if the resource is left without any usage.

transaction {
  var temporaryVault: @ExampleToken.Vault

  prepare(...) {
    ...
    self.temporaryVault <- myVault.withdraw(amount: 10.0)
  }

  execute {
    ...
    // Type-check will not pass if temporaryVault is not explicitly used!
    receiverRef.deposit(from: <-self.temporaryVault)
  }

  post {
    ...
  }

}

This example illustrates two additional advantages of this approach in terms of security: First, if the receiver has no token vault implemented - be it because the address is wrong or the person does not want to receive these kinds of tokens - the transaction will revert. No more value loss because of misspelled addresses! Secondly, the possibility of including pre- and post conditions in transactions and functions makes asserting the correct outcomes much easier to implement, so developers and users can be confident about the correctness of their blockchain-based interactions. 

Beyond making development with digital assets more readable and secure, resources also allow for advanced use cases like composition and nesting. To group items, you simply create a collection resource as a wrapper around individual resources. This makes batch transfers  —  the transfer of multiple resources at the same time  —  easy to implement, speeding up development processes and letting you ship your products faster and safer.

Direct ownership model: putting assets where they belong 

Another great advantage of Cadence stems from its deep integration with the Flow protocol: Resources are stored where they belong , directly  in the user’s account.

let myCoolAsset <- create CoolAsset()
account.save<@CoolAsset>(<-myAsset, to: /storage/myCoolAsset)

This human-centred account model enables direct ownership of digital assets and has various advantages over the central ledger approach of languages like Solidity:

  • Higher level of decentralisation and security
    Instead of having a central point of failure, digital assets are created by the contract they’re defined in, but stored in the user’s account. That significantly increases security, as it minimises and distributes the vectors for large-scale attacks, making them economically infeasible by spreading out potential targets.

  • Enhanced efficiency
    Gathering information about all the assets one specific user owns can be almost impossible with the central ledger model, because this necessitates roundtrips to every single smart contract the user might hold assets in. With Flow, all of the user’s digital assets are right in their account, making them easy to query and interact with.

  • Easier usability
    Not only developers but also end users will find this human-centred account model much easier to reason about, because it avoids unnecessary complication and builds on existing mental models —while central ledgers are counter-intuitive and hard to wrap one’s head around,  everyone knows what an account is!

But most importantly, Flow’s intuitive account model lays the foundation for capability-based access control, Cadence’s way of managing access in a fine-grained and highly readable way.

Capabilities: human-readable access-control

Capabilities are similar to permissions: they control what actions a user can take on a given resource. If you want to call a resource’s method, you need to have a valid capability.

Capabilities are the gateway to resources. Like a REST API, capabilities have a path. If that path lives in the public domain of an account, the capability can be obtained by anyone; capabilities in the private domain are only accessible by the owner of that account.

Regardless if in public or private domain, capabilities always link to a target. This target can be an entire resource or just a subset of its methods. For the latter, interfaces may act as a target for the capability. This is how capabilities allow fine-grained access control that’s human-readable.

To interact with a resource, you first have to get the specific capability before borrowing its underlying resource. This can easily be done within any transaction.

account.getCapability<...>(/public/MyCapability).borrow()

In opposition to the access list-based model of Solidity that relies on the harmful practice of checking msg.sender, capabilities allow for a flexible control model that allows giving, modifying and revoking access to resources or even just parts of resources in a simple, readable and secure manner.

To summarise, resources, capabilities and Flow’s account model make Cadence a language that is faster to learn, easier to audit, and more productive, letting developers build applications faster while keeping user’s assets safe. Because of these many advantages, an ever growing number of teams are building upon the benefits of the resource-oriented paradigm.

An industry awakening: dawning the era of resource-orientation

“A paradigm shift is an important change that happens when the usual way of thinking about or doing something is replaced by a new and different way,” sociologist Thomas Kuhn concisely outlined in his work The Structure of Scientific Revolutions.

This is equally true for technological revolutions: New challenges trigger new kinds of solutions, and as software moves into new domains, the “right way” of how to build software changes — and just like SQL was the answer to challenges with atomic transactions, the need for graphical user interfaces led to the advent of object-oriented languages like Java.

The challenge we are faced with today are the hardships of dealing with assets of value on the blockchain. The solution that Cadence proposes is a paradigm the whole industry slowly seems to discover: Resource-orientation.

Already today, big players have implemented the resource-oriented paradigm, like the team of Diem with their smart contract language Move. Because the advantages of the resource-oriented paradigm are easily comprehensible, there will be many to follow.

Cadence is the first resource-oriented language you can use for smart contract programming today, setting new standards in terms of smart contract security, readability and composability. Because of these advantages over existing solutions, Cadence might just become the lingua franca of next generation smart contracts.

You can be a part of this new paradigm of smart contract programming by starting to learn Cadence today with our tailor-made learning resources, created and curated by the core team behind Flow and Cadence.

Further readings

Inside Flow: Cadence, The Language Made For Digital Assets

September 17, 2021

Background: Flow is a new blockchain originally designed and developed by Dapper Labs, the makers of CryptoKitties and NBA Top Shot. In this multi-part series, we will explore the different components of Flow blockchain from a technical perspective.

Cadence, the new programming language that makes smart contract development faster, safer
Flow Client Library (FCL), it is analogous to Web3.js on Ethereum, but built for the consumer audience in mind
Flow multi-node architecture, future-proof scaling for the mainstream adoption


An ever-growing stream of value is pouring into blockchain applications amidst ongoing crypto mass-adoption,  but the technical tools that handle these digital assets still lack maturity. Despite lengthy and costly audits, smart contracts often fail to deliver a sustainable security model that does justice to the amount of value stored in them, and oftentimes developers need to recreate the wheel just to achieve some basic functionality. There has been a lack of a programming language that treats digital assets with the importance they deserve, until now. 

Enter Cadence.

Cadence is the first language specifically designed for managing ownership of digital assets of value, like art, collectibles or cryptocurrencies on the blockchain. In Cadence, digital assets are first-class citizens, making their accidental loss or malicious duplication impossible while providing fine-grained and human-readable access control.

Cadence leverages a unique data model for digital assets: resources. Resources make the creation, transfer and storing of digital assets easier and more secure. In combination with capability-based access control this results in a language that is faster to learn, easier to audit, and more productive than any current alternative.

Building on the human-centred account model of Flow, Cadence introduces a direct-ownership model for digital assets that enables higher degrees of decentralisation and attack-resiliency.   Your assets are stored right where they belong: in your account.

Cadence was written with consumer-grade use cases in mind, built for scalability on top of its solid foundation of scientific research. It's been battle-tested at enormous scale as the backbone of NBA Top Shot, one of the fastest growing blockchain applications ever made with over one million registrations.

Beyond that, thousands of users interact daily with an ever growing number of Cadence-powered applications like marketplaces, games or exchanges, proving its durability and reliability. These benefits draw in new developers on a daily basis, allowing them to enjoy the benefits of Cadence’s improved security, productivity and readability.

The fact that other big players of the industry like Diem (Move) start leveraging the power of resources hints that the next generation of smart contract programming might dawn the era of resource-oriented languages, and Cadence is the first resource-oriented language ready for use on the blockchain today.

The challenges of digital assets

The biggest innovation that allowed blockchain technology to open up a new world of possibilities was the enabling of native digital value. On the blockchain, code is not merely a reference to some external asset — the code is the asset. Instead of being easily disposable, arbitrarily duplicable and easy to tamper with, data on the blockchain is valuable, something you can own. No matter if it’s NFTs, collectibles, cryptocurrencies or governance tokens, they’re all part of a new asset class: digital assets.

As exciting the prospects of this new evolution are, it’s equally worrisome how deeply the tools that manage digital assets on a daily basis are rooted in the the old world: Smart contract programming languages like Solidity treat digital assets and the immense value they might carry no different than arbitrarily mutable data structures, while storing them in a central ledger. A few lines of code are enough to illustrate the great risks that these languages bear for the handling of digital assets.

№1: Central ledgers create harmful honeypots

The following code snippet represents a simplified implementation of a fungible token in Solidity. This ERC-20-inspired fungible token smart contract consists of two functions: a mint function that allows for generating new tokens, and a send function, which enables money transfers to different addresses. 

contract AnyCoin {
  mapping (address => uint) public balance;
  
  function mint(address receiver, uint amount) {
    balance[receiver] += amount;
  }
  
  function send(address receiver, uint amount) {
    balance[msg.sender] -= amount;
    balance[receiver] += amount;
  }
}

The crucial question with regards to digital assets is: where and how are the token holdings of each person stored? The snippet shows that these token holdings are being represented by a data structure at the top of the contract: The mapping of balances. This data structure is a central ledger that maps each holder’s address to the specific token balance, effectively storing all digital assets in the smart contract itself.

mapping(address => uint) public balance;

These central mappings are oftentimes responsible for handling the equivalent value of billions of dollars at a time, as one glance at the most popular ERC-20 token contracts on Ethereum shows. Storing these huge amounts of digital assets in such a centralised manner creates central honeypots for hackers. Unfortunately, multi-million dollar hacks have become a sad reality for many teams and users: In 2020 alone, the total value lost through overall crypto crime hit $1.9 billion USD, with hacks amounting to a total of $513 million USD [source]. But this centralised storage is just one problem of languages like Solidity.

№2: Digital assets need reliable data models

Because the central mapping is nothing more than an arbitrarily mutable data structure that does not impose any rules for handling digital assets, there’s nothing stopping developers from mistakingly writing malicious code. To illustrate that point, let’s look at another implementation of a token contract.

contract BugCoin {
  mapping (address => uint) public balance;
  
  function mint(address receiver, uint amount) {
    balance[receiver] += amount;
  }
  
  function send(address receiver, uint amount) {
    balance[msg.sender] -= amount;
    // balance[receiver] += amount;
  }
}

Here, just one line of code was commented out in the send function ,  but it’s an essential one: A transfer of BugCoin would result in the balance of the sender being deducted , but the receiver’s balance would never increase. The value of any transaction of this contract would be lost permanently because of one missing line of code. Shockingly, there is nothing stopping that from happening in Ethereum.  Since the compiler won’t complain, this code would be good to go for deployment on test- or even mainnet, wiping out digital value and destroying users’ assets on every transaction made.

The permanent nature of smart contracts and the immense value they might carry puts huge responsibility and pressure on blockchain developers. As we have seen, the limits of existing smart contract programming languages do not make the programmer’s life easier. In fact, there’s even a lot more low-level code to take care of, like managing over- and underflow problems, or converting between different currency units. These constraints in a place that’s all about value  makes for a delicate mixture. This created a need for a language that treats digital assets with the importance they deserve.

Meet Cadence, the language made for digital assets.

Cadence: Resources to the rescue

Cadence eliminates most of the illustrated risks existing smart programming languages bear by  introducing a custom data model for digital assets: resources. Inspired by linear types, resources act as a supporting net for developers, making them aware of potential errors before they happen at runtime. 

Cadence developers immediately know when they’re dealing with resources, because the creation or transfer of resources necessitate the use of the move operator (<-), while their type is explicitly stated with its own symbol (@). These visual cues are key in eliminating human error in production and make Cadence code easy to read, write, learn and maintain.

But Cadence’s resources are not just syntactic sugar for existing data models,  they enforce strict rules in terms of allowed operations, so no digital assets falls victim to developer errors. Resources can only exist in one exact place at one exact time, making their malicious duplication or accidental deletion impossible, and increasing the overall safety in all processes that touch upon digital assets of value. 

Remember the malicious send function in our BugCoin contract? In Cadence, this kind of code would never have been compiled in the first place. That’s because the token holdings of a fungible token contract in Cadence are implemented with resources that implement strict rules of how they can be handled. Every token holder has their own vault resource for holdings of this token in their account, and this resource also has the withdraw and deposit methods. 

pub resource Vault: Provider, Receiver, Balance {
    // pub = publicly readable, internally writable
    pub var balance: UFix64

    init(balance: UFix64) {
        self.balance = balance
    }

    pub fun withdraw(amount: UFix64): @Vault {
        self.balance = self.balance - amount
        return <-create Vault(balance: amount)
    }
        
    pub fun deposit(from: @Vault) {
        self.balance = self.balance + from.balance
        destroy from
    }
}

The safety of value in transit is always given, because the amount of the transaction is put into a  temporary vault resource after withdrawal. This temporary vault can then be deposited in someone else’s deposit function, or explicitly deleted. But unlike the send function in the Solidity example, it’s impossible to lose the token vault by accident, because the compiler will throw an error if the resource is left without any usage.

transaction {
  var temporaryVault: @ExampleToken.Vault

  prepare(...) {
    ...
    self.temporaryVault <- myVault.withdraw(amount: 10.0)
  }

  execute {
    ...
    // Type-check will not pass if temporaryVault is not explicitly used!
    receiverRef.deposit(from: <-self.temporaryVault)
  }

  post {
    ...
  }

}

This example illustrates two additional advantages of this approach in terms of security: First, if the receiver has no token vault implemented - be it because the address is wrong or the person does not want to receive these kinds of tokens - the transaction will revert. No more value loss because of misspelled addresses! Secondly, the possibility of including pre- and post conditions in transactions and functions makes asserting the correct outcomes much easier to implement, so developers and users can be confident about the correctness of their blockchain-based interactions. 

Beyond making development with digital assets more readable and secure, resources also allow for advanced use cases like composition and nesting. To group items, you simply create a collection resource as a wrapper around individual resources. This makes batch transfers  —  the transfer of multiple resources at the same time  —  easy to implement, speeding up development processes and letting you ship your products faster and safer.

Direct ownership model: putting assets where they belong 

Another great advantage of Cadence stems from its deep integration with the Flow protocol: Resources are stored where they belong , directly  in the user’s account.

let myCoolAsset <- create CoolAsset()
account.save<@CoolAsset>(<-myAsset, to: /storage/myCoolAsset)

This human-centred account model enables direct ownership of digital assets and has various advantages over the central ledger approach of languages like Solidity:

  • Higher level of decentralisation and security
    Instead of having a central point of failure, digital assets are created by the contract they’re defined in, but stored in the user’s account. That significantly increases security, as it minimises and distributes the vectors for large-scale attacks, making them economically infeasible by spreading out potential targets.

  • Enhanced efficiency
    Gathering information about all the assets one specific user owns can be almost impossible with the central ledger model, because this necessitates roundtrips to every single smart contract the user might hold assets in. With Flow, all of the user’s digital assets are right in their account, making them easy to query and interact with.

  • Easier usability
    Not only developers but also end users will find this human-centred account model much easier to reason about, because it avoids unnecessary complication and builds on existing mental models —while central ledgers are counter-intuitive and hard to wrap one’s head around,  everyone knows what an account is!

But most importantly, Flow’s intuitive account model lays the foundation for capability-based access control, Cadence’s way of managing access in a fine-grained and highly readable way.

Capabilities: human-readable access-control

Capabilities are similar to permissions: they control what actions a user can take on a given resource. If you want to call a resource’s method, you need to have a valid capability.

Capabilities are the gateway to resources. Like a REST API, capabilities have a path. If that path lives in the public domain of an account, the capability can be obtained by anyone; capabilities in the private domain are only accessible by the owner of that account.

Regardless if in public or private domain, capabilities always link to a target. This target can be an entire resource or just a subset of its methods. For the latter, interfaces may act as a target for the capability. This is how capabilities allow fine-grained access control that’s human-readable.

To interact with a resource, you first have to get the specific capability before borrowing its underlying resource. This can easily be done within any transaction.

account.getCapability<...>(/public/MyCapability).borrow()

In opposition to the access list-based model of Solidity that relies on the harmful practice of checking msg.sender, capabilities allow for a flexible control model that allows giving, modifying and revoking access to resources or even just parts of resources in a simple, readable and secure manner.

To summarise, resources, capabilities and Flow’s account model make Cadence a language that is faster to learn, easier to audit, and more productive, letting developers build applications faster while keeping user’s assets safe. Because of these many advantages, an ever growing number of teams are building upon the benefits of the resource-oriented paradigm.

An industry awakening: dawning the era of resource-orientation

“A paradigm shift is an important change that happens when the usual way of thinking about or doing something is replaced by a new and different way,” sociologist Thomas Kuhn concisely outlined in his work The Structure of Scientific Revolutions.

This is equally true for technological revolutions: New challenges trigger new kinds of solutions, and as software moves into new domains, the “right way” of how to build software changes — and just like SQL was the answer to challenges with atomic transactions, the need for graphical user interfaces led to the advent of object-oriented languages like Java.

The challenge we are faced with today are the hardships of dealing with assets of value on the blockchain. The solution that Cadence proposes is a paradigm the whole industry slowly seems to discover: Resource-orientation.

Already today, big players have implemented the resource-oriented paradigm, like the team of Diem with their smart contract language Move. Because the advantages of the resource-oriented paradigm are easily comprehensible, there will be many to follow.

Cadence is the first resource-oriented language you can use for smart contract programming today, setting new standards in terms of smart contract security, readability and composability. Because of these advantages over existing solutions, Cadence might just become the lingua franca of next generation smart contracts.

You can be a part of this new paradigm of smart contract programming by starting to learn Cadence today with our tailor-made learning resources, created and curated by the core team behind Flow and Cadence.

Further readings

Read More

Inside Flow: The Multi-Node Architecture that Scales to Millions

Announcing FLIP Fest, the Flow Buildathon

Partner Spotlight: GigLabs is Building the NFT Bridge for Brands