From d940912261bb5c9cd72cd0c9d2183a79474552dd Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:16:19 -0600 Subject: [PATCH 01/88] refactor contracts for Cadence 1.0 --- cadence/contracts/Burner.cdc | 44 ++ cadence/contracts/FUSD.cdc | 119 ++- cadence/contracts/FlowToken.cdc | 198 +++-- cadence/contracts/FungibleToken.cdc | 204 ++--- .../contracts/FungibleTokenMetadataViews.cdc | 180 +++++ cadence/contracts/MetadataViews.cdc | 706 ++++++++++++++++++ cadence/contracts/NonFungibleToken.cdc | 234 ++++++ cadence/contracts/ViewResolver.cdc | 59 ++ flow.json | 44 +- 9 files changed, 1601 insertions(+), 187 deletions(-) create mode 100644 cadence/contracts/Burner.cdc create mode 100644 cadence/contracts/FungibleTokenMetadataViews.cdc create mode 100644 cadence/contracts/MetadataViews.cdc create mode 100644 cadence/contracts/NonFungibleToken.cdc create mode 100644 cadence/contracts/ViewResolver.cdc diff --git a/cadence/contracts/Burner.cdc b/cadence/contracts/Burner.cdc new file mode 100644 index 0000000..0a3795e --- /dev/null +++ b/cadence/contracts/Burner.cdc @@ -0,0 +1,44 @@ +/// Burner is a contract that can facilitate the destruction of any resource on flow. +/// +/// Contributors +/// - Austin Kline - https://twitter.com/austin_flowty +/// - Deniz Edincik - https://twitter.com/bluesign +/// - Bastian Müller - https://twitter.com/turbolent +access(all) contract Burner { + /// When Crescendo (Cadence 1.0) is released, custom destructors will be removed from cadece. + /// Burnable is an interface meant to replace this lost feature, allowing anyone to add a callback + /// method to ensure they do not destroy something which is not meant to be, + /// or to add logic based on destruction such as tracking the supply of a FT Collection + /// + /// NOTE: The only way to see benefit from this interface + /// is to always use the burn method in this contract. Anyone who owns a resource can always elect **not** + /// to destroy a resource this way + access(all) resource interface Burnable { + access(contract) fun burnCallback() + } + + /// burn is a global method which will destroy any resource it is given. + /// If the provided resource implements the Burnable interface, + /// it will call the burnCallback method and then destroy afterwards. + access(all) fun burn(_ r: @AnyResource) { + if let s <- r as? @{Burnable} { + s.burnCallback() + destroy s + } else if let arr <- r as? @[AnyResource] { + while arr.length > 0 { + let item <- arr.removeFirst() + self.burn(<-item) + } + destroy arr + } else if let dict <- r as? @{HashableStruct: AnyResource} { + let keys = dict.keys + while keys.length > 0 { + let item <- dict.remove(key: keys.removeFirst())! + self.burn(<-item) + } + destroy dict + } else { + destroy r + } + } +} \ No newline at end of file diff --git a/cadence/contracts/FUSD.cdc b/cadence/contracts/FUSD.cdc index ef45ba3..3bd2e29 100644 --- a/cadence/contracts/FUSD.cdc +++ b/cadence/contracts/FUSD.cdc @@ -1,33 +1,57 @@ -import FungibleToken from "./FungibleToken.cdc" +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" +import ViewResolver from "ViewResolver" -pub contract FUSD: FungibleToken { +access(all) contract FUSD: FungibleToken { + + access(all) entitlement ProxyOwner // Event that is emitted when the contract is created - pub event TokensInitialized(initialSupply: UFix64) + access(all) event TokensInitialized(initialSupply: UFix64) // Event that is emitted when tokens are withdrawn from a Vault - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event TokensWithdrawn(amount: UFix64, from: Address?) // Event that is emitted when tokens are deposited to a Vault - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) event TokensDeposited(amount: UFix64, to: Address?) // Event that is emitted when new tokens are minted - pub event TokensMinted(amount: UFix64) + access(all) event TokensMinted(amount: UFix64) // The storage path for the admin resource - pub let AdminStoragePath: StoragePath + access(all) let AdminStoragePath: StoragePath // The storage Path for minters' MinterProxy - pub let MinterProxyStoragePath: StoragePath + access(all) let MinterProxyStoragePath: StoragePath // The public path for minters' MinterProxy capability - pub let MinterProxyPublicPath: PublicPath + access(all) let MinterProxyPublicPath: PublicPath // Event that is emitted when a new minter resource is created - pub event MinterCreated() + access(all) event MinterCreated() // Total supply of fusd in existence - pub var totalSupply: UFix64 + access(all) var totalSupply: UFix64 + + /// Function that returns all the Metadata Views implemented by this contract. + /// @param resourceType: An optional resource type to return views for + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [] + } + + /// Function that resolves a metadata view for this token. + /// + /// @param resourceType: An optional resource type to return views for + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + return nil + } // Vault // @@ -41,16 +65,48 @@ pub contract FUSD: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens - pub var balance: UFix64 + access(all) var balance: UFix64 // initialize the balance at resource creation time init(balance: UFix64) { self.balance = balance } + /// Called when a fungible token is burned via the `Burner.burn()` method + access(contract) fun burnCallback() { + if self.balance > 0.0 { + FUSD.totalSupply = FUSD.totalSupply - self.balance + } + self.balance = 0.0 + } + + access(all) view fun getViews(): [Type] { + return FUSD.getContractViews(resourceType: nil) + } + + access(all) fun resolveView(_ view: Type): AnyStruct? { + return FUSD.resolveContractView(resourceType: nil, viewType: view) + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + let supportedTypes: {Type: Bool} = {} + supportedTypes[self.getType()] = true + return supportedTypes + } + + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } + + /// Asks if the amount can be withdrawn from this vault + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool { + return amount <= self.balance + } + // withdraw // // Function that takes an integer amount as an argument @@ -60,7 +116,7 @@ pub contract FUSD: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - pub fun withdraw(amount: UFix64): @FungibleToken.Vault { + access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) @@ -73,7 +129,7 @@ pub contract FUSD: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - pub fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FUSD.Vault self.balance = self.balance + vault.balance emit TokensDeposited(amount: vault.balance, to: self.owner?.address) @@ -81,8 +137,10 @@ pub contract FUSD: FungibleToken { destroy vault } - destroy() { - FUSD.totalSupply = FUSD.totalSupply - self.balance + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <- FUSD.createEmptyVault(vaultType: Type<@FUSD.Vault>()) } } @@ -93,7 +151,10 @@ pub contract FUSD: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @FUSD.Vault { + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { + pre { + vaultType == Type<@FUSD.Vault>(): "Unsupported vault type requested" + } return <-create Vault(balance: 0.0) } @@ -102,14 +163,14 @@ pub contract FUSD: FungibleToken { // Resource object that can mint new tokens. // The admin stores this and passes it to the minter account as a capability wrapper resource. // - pub resource Minter { + access(all) resource Minter { // mintTokens // // Function that mints new tokens, adds them to the total supply, // and returns them to the calling context. // - pub fun mintTokens(amount: UFix64): @FUSD.Vault { + access(all) fun mintTokens(amount: UFix64): @FUSD.Vault { pre { amount > 0.0: "Amount minted must be greater than zero" } @@ -120,8 +181,8 @@ pub contract FUSD: FungibleToken { } - pub resource interface MinterProxyPublic { - pub fun setMinterCapability(cap: Capability<&Minter>) + access(all) resource interface MinterProxyPublic { + access(all) fun setMinterCapability(cap: Capability<&Minter>) } // MinterProxy @@ -130,18 +191,18 @@ pub contract FUSD: FungibleToken { // The resource that this capability represents can be deleted by the admin // in order to unilaterally revoke minting capability if needed. - pub resource MinterProxy: MinterProxyPublic { + access(all) resource MinterProxy: MinterProxyPublic { // access(self) so nobody else can copy the capability and use it. access(self) var minterCapability: Capability<&Minter>? // Anyone can call this, but only the admin can create Minter capabilities, // so the type system constrains this to being called by the admin. - pub fun setMinterCapability(cap: Capability<&Minter>) { + access(all) fun setMinterCapability(cap: Capability<&Minter>) { self.minterCapability = cap } - pub fun mintTokens(amount: UFix64): @FUSD.Vault { + access(ProxyOwner) fun mintTokens(amount: UFix64): @FUSD.Vault { return <- self.minterCapability! .borrow()! .mintTokens(amount:amount) @@ -159,7 +220,7 @@ pub contract FUSD: FungibleToken { // Anyone can call this, but the MinterProxy cannot mint without a Minter capability, // and only the admin can provide that. // - pub fun createMinterProxy(): @MinterProxy { + access(all) fun createMinterProxy(): @MinterProxy { return <- create MinterProxy() } @@ -172,7 +233,7 @@ pub contract FUSD: FungibleToken { // Ideally we would create this structure in a single function, generate the paths from the address // and cache all of this information to enable easy revocation but String/Path comversion isn't yet supported. // - pub resource Administrator { + access(all) resource Administrator { // createNewMinter // @@ -184,7 +245,7 @@ pub contract FUSD: FungibleToken { // then the admin account running: // transactions/fusd/admin/deposit_fusd_minter.cdc // - pub fun createNewMinter(): @Minter { + access(all) fun createNewMinter(): @Minter { emit MinterCreated() return <- create Minter() } @@ -199,7 +260,7 @@ pub contract FUSD: FungibleToken { self.totalSupply = 0.0 let admin <- create Administrator() - self.account.save(<-admin, to: self.AdminStoragePath) + self.account.storage.save(<-admin, to: self.AdminStoragePath) // Emit an event that shows that the contract was initialized emit TokensInitialized(initialSupply: 0.0) diff --git a/cadence/contracts/FlowToken.cdc b/cadence/contracts/FlowToken.cdc index a69a715..7892899 100644 --- a/cadence/contracts/FlowToken.cdc +++ b/cadence/contracts/FlowToken.cdc @@ -1,30 +1,27 @@ -import FungibleToken from "./FungibleToken.cdc" +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" +import Burner from "Burner" -pub contract FlowToken: FungibleToken { +access(all) contract FlowToken: FungibleToken { // Total supply of Flow tokens in existence - pub var totalSupply: UFix64 - - // Event that is emitted when the contract is created - pub event TokensInitialized(initialSupply: UFix64) + access(all) var totalSupply: UFix64 // Event that is emitted when tokens are withdrawn from a Vault - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event TokensWithdrawn(amount: UFix64, from: Address?) // Event that is emitted when tokens are deposited to a Vault - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) event TokensDeposited(amount: UFix64, to: Address?) // Event that is emitted when new tokens are minted - pub event TokensMinted(amount: UFix64) - - // Event that is emitted when tokens are destroyed - pub event TokensBurned(amount: UFix64) + access(all) event TokensMinted(amount: UFix64) // Event that is emitted when a new minter resource is created - pub event MinterCreated(allowedAmount: UFix64) + access(all) event MinterCreated(allowedAmount: UFix64) // Event that is emitted when a new burner resource is created - pub event BurnerCreated() + access(all) event BurnerCreated() // Vault // @@ -38,16 +35,38 @@ pub contract FlowToken: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens - pub var balance: UFix64 + access(all) var balance: UFix64 // initialize the balance at resource creation time init(balance: UFix64) { self.balance = balance } + /// Called when a fungible token is burned via the `Burner.burn()` method + access(contract) fun burnCallback() { + if self.balance > 0.0 { + FlowToken.totalSupply = FlowToken.totalSupply - self.balance + } + self.balance = 0.0 + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + return {self.getType(): true} + } + + access(all) view fun isSupportedVaultType(type: Type): Bool { + if (type == self.getType()) { return true } else { return false } + } + + /// Asks if the amount can be withdrawn from this vault + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool { + return amount <= self.balance + } + // withdraw // // Function that takes an integer amount as an argument @@ -57,7 +76,7 @@ pub contract FlowToken: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - pub fun withdraw(amount: UFix64): @FungibleToken.Vault { + access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) @@ -70,7 +89,7 @@ pub contract FlowToken: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - pub fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault self.balance = self.balance + vault.balance emit TokensDeposited(amount: vault.balance, to: self.owner?.address) @@ -78,8 +97,26 @@ pub contract FlowToken: FungibleToken { destroy vault } - destroy() { - FlowToken.totalSupply = FlowToken.totalSupply - self.balance + /// Get all the Metadata Views implemented by FlowToken + /// + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getViews(): [Type]{ + return FlowToken.getContractViews(resourceType: nil) + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveView(_ view: Type): AnyStruct? { + return FlowToken.resolveContractView(resourceType: nil, viewType: view) + } + + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <-create Vault(balance: 0.0) } } @@ -90,45 +127,93 @@ pub contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @FungibleToken.Vault { + access(all) fun createEmptyVault(vaultType: Type): @FlowToken.Vault { return <-create Vault(balance: 0.0) } - pub resource Administrator { + /// Gets a list of the metadata views that this contract supports + access(all) view fun getContractViews(resourceType: Type?): [Type] { + return [Type(), + Type(), + Type(), + Type()] + } + + /// Get a Metadata View from FlowToken + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { + switch viewType { + case Type(): + return FungibleTokenMetadataViews.FTView( + ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTDisplay?, + ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? + ) + case Type(): + let media = MetadataViews.Media( + file: MetadataViews.HTTPFile( + url: FlowToken.getLogoURI() + ), + mediaType: "image/svg+xml" + ) + let medias = MetadataViews.Medias([media]) + return FungibleTokenMetadataViews.FTDisplay( + name: "FLOW Network Token", + symbol: "FLOW", + description: "FLOW is the native token for the Flow blockchain. It is required for securing the network, transaction fees, storage fees, staking, FLIP voting and may be used by applications built on the Flow Blockchain", + externalURL: MetadataViews.ExternalURL("https://flow.com"), + logos: medias, + socials: { + "twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain") + } + ) + case Type(): + let vaultRef = FlowToken.account.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the contract's Vault!") + return FungibleTokenMetadataViews.FTVaultData( + storagePath: /storage/flowTokenVault, + receiverPath: /public/flowTokenReceiver, + metadataPath: /public/flowTokenBalance, + receiverLinkedType: Type<&{FungibleToken.Receiver, FungibleToken.Vault}>(), + metadataLinkedType: Type<&{FungibleToken.Balance, FungibleToken.Vault}>(), + createEmptyVaultFunction: (fun (): @{FungibleToken.Vault} { + return <-vaultRef.createEmptyVault() + }) + ) + case Type(): + return FungibleTokenMetadataViews.TotalSupply(totalSupply: FlowToken.totalSupply) + } + return nil + } + + access(all) resource Administrator { // createNewMinter // // Function that creates and returns a new minter resource // - pub fun createNewMinter(allowedAmount: UFix64): @Minter { + access(all) fun createNewMinter(allowedAmount: UFix64): @Minter { emit MinterCreated(allowedAmount: allowedAmount) return <-create Minter(allowedAmount: allowedAmount) } - - // createNewBurner - // - // Function that creates and returns a new burner resource - // - pub fun createNewBurner(): @Burner { - emit BurnerCreated() - return <-create Burner() - } } // Minter // // Resource object that token admin accounts can hold to mint new tokens. // - pub resource Minter { + access(all) resource Minter { // the amount of tokens that the minter is allowed to mint - pub var allowedAmount: UFix64 + access(all) var allowedAmount: UFix64 // mintTokens // // Function that mints new tokens, adds them to the total supply, // and returns them to the calling context. // - pub fun mintTokens(amount: UFix64): @FlowToken.Vault { + access(all) fun mintTokens(amount: UFix64): @FlowToken.Vault { pre { amount > UFix64(0): "Amount minted must be greater than zero" amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" @@ -144,55 +229,34 @@ pub contract FlowToken: FungibleToken { } } - // Burner - // - // Resource object that token admin accounts can hold to burn tokens. - // - pub resource Burner { - - // burnTokens - // - // Function that destroys a Vault instance, effectively burning the tokens. - // - // Note: the burned tokens are automatically subtracted from the - // total supply in the Vault destructor. - // - pub fun burnTokens(from: @FungibleToken.Vault) { - let vault <- from as! @FlowToken.Vault - let amount = vault.balance - destroy vault - emit TokensBurned(amount: amount) - } + /// Gets the Flow Logo XML URI from storage + access(all) fun getLogoURI(): String { + return FlowToken.account.storage.copy(from: /storage/flowTokenLogoURI) ?? "" } - init(adminAccount: AuthAccount) { + init() { self.totalSupply = 0.0 // Create the Vault with the total supply of tokens and save it in storage // let vault <- create Vault(balance: self.totalSupply) - adminAccount.save(<-vault, to: /storage/flowTokenVault) + + self.account.storage.save(<-vault, to: /storage/flowTokenVault) // Create a public capability to the stored Vault that only exposes // the `deposit` method through the `Receiver` interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Receiver}>( - /public/flowTokenReceiver, - target: /storage/flowTokenVault - ) + let receiverCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) // Create a public capability to the stored Vault that only exposes // the `balance` field through the `Balance` interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Balance}>( - /public/flowTokenBalance, - target: /storage/flowTokenVault - ) + let balanceCapability = self.account.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + self.account.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) let admin <- create Administrator() - adminAccount.save(<-admin, to: /storage/flowTokenAdmin) + self.account.storage.save(<-admin, to: /storage/flowTokenAdmin) - // Emit an event that shows that the contract was initialized - emit TokensInitialized(initialSupply: self.totalSupply) } } \ No newline at end of file diff --git a/cadence/contracts/FungibleToken.cdc b/cadence/contracts/FungibleToken.cdc index eca3737..c731911 100644 --- a/cadence/contracts/FungibleToken.cdc +++ b/cadence/contracts/FungibleToken.cdc @@ -2,21 +2,18 @@ # The Flow Fungible Token standard -## `FungibleToken` contract interface +## `FungibleToken` contract -The interface that all fungible token contracts would have to conform to. -If a users wants to deploy a new token contract, their contract -would need to implement the FungibleToken interface. - -Their contract would have to follow all the rules and naming -that the interface specifies. +The Fungible Token standard is no longer an interface +that all fungible token contracts would have to conform to. -## `Vault` resource +If a users wants to deploy a new token contract, their contract +does not need to implement the FungibleToken interface, but their tokens +do need to implement the interfaces defined in this contract. -Each account that owns tokens would need to have an instance -of the Vault resource stored in their account storage. +## `Vault` resource interface -The Vault resource has methods that the owner and other users can call. +Each fungible token resource type needs to implement the `Vault` resource interface. ## `Provider`, `Receiver`, and `Balance` resource interfaces @@ -32,43 +29,40 @@ these interfaces to do various things with the tokens. For example, a faucet can be implemented by conforming to the Provider interface. -By using resources and interfaces, users of FungibleToken contracts -can send and receive tokens peer-to-peer, without having to interact -with a central ledger smart contract. To send tokens to another user, -a user would simply withdraw the tokens from their Vault, then call -the deposit function on another user's Vault to complete the transfer. - */ +import ViewResolver from "ViewResolver" +import Burner from "Burner" + /// FungibleToken /// -/// The interface that fungible token contracts implement. -/// -pub contract interface FungibleToken { +/// Fungible Token implementations are no longer required to implement the fungible token +/// interface. We still have it as an interface here because there are some useful +/// utility methods that many projects will still want to have on their contracts, +/// but they are by no means required. all that is required is that the token +/// implements the `Vault` interface +access(all) contract interface FungibleToken: ViewResolver { - /// The total number of tokens in existence. - /// It is up to the implementer to ensure that the total supply - /// stays accurate and up to date - /// - pub var totalSupply: UFix64 + // An entitlement for allowing the withdrawal of tokens from a Vault + access(all) entitlement Withdraw - /// TokensInitialized - /// - /// The event that is emitted when the contract is created - /// - pub event TokensInitialized(initialSupply: UFix64) - - /// TokensWithdrawn - /// /// The event that is emitted when tokens are withdrawn from a Vault - /// - pub event TokensWithdrawn(amount: UFix64, from: Address?) + access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64) + + /// The event that is emitted when tokens are deposited to a Vault + access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64) + + /// Event that is emitted when the global burn method is called with a non-zero balance + access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64) - /// TokensDeposited + /// Balance /// - /// The event that is emitted when tokens are deposited into a Vault + /// The interface that provides standard functions\ + /// for getting balance information /// - pub event TokensDeposited(amount: UFix64, to: Address?) + access(all) resource interface Balance { + access(all) var balance: UFix64 + } /// Provider /// @@ -79,28 +73,31 @@ pub contract interface FungibleToken { /// because it leaves open the possibility of creating custom providers /// that do not necessarily need their own balance. /// - pub resource interface Provider { + access(all) resource interface Provider { + + /// Function to ask a provider if a specific amount of tokens + /// is available to be withdrawn + /// This could be useful to avoid panicing when calling withdraw + /// when the balance is unknown + /// Additionally, if the provider is pulling from multiple vaults + /// it only needs to check some of the vaults until the desired amount + /// is reached, potentially helping with performance. + /// + access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool - /// withdraw subtracts tokens from the owner's Vault + /// withdraw subtracts tokens from the implementing resource /// and returns a Vault with the removed tokens. /// - /// The function's access level is public, but this is not a problem - /// because only the owner storing the resource in their account - /// can initially call this function. + /// The function's access level is `access(Withdraw)` + /// So in order to access it, one would either need the object itself + /// or an entitled reference with `Withdraw`. /// - /// The owner may grant other accounts access by creating a private - /// capability that allows specific other users to access - /// the provider resource through a reference. - /// - /// The owner may also grant all accounts access by creating a public - /// capability that allows all users to access the provider - /// resource through a reference. - /// - pub fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { post { // `result` refers to the return value result.balance == amount: "Withdrawal amount must be the same as the balance of the withdrawn Vault" + emit Withdrawn(type: self.getType().identifier, amount: amount, from: self.owner?.address, fromUUID: self.uuid, withdrawnUUID: result.uuid) } } } @@ -115,84 +112,115 @@ pub contract interface FungibleToken { /// can do custom things with the tokens, like split them up and /// send them to different places. /// - pub resource interface Receiver { + access(all) resource interface Receiver { /// deposit takes a Vault and deposits it into the implementing resource type /// - pub fun deposit(from: @Vault) + access(all) fun deposit(from: @{Vault}) + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} + + /// Returns whether or not the given type is accepted by the Receiver + /// A vault that can accept any type should just return true by default + access(all) view fun isSupportedVaultType(type: Type): Bool } - /// Balance + /// Vault /// - /// The interface that contains the `balance` field of the Vault - /// and enforces that when new Vaults are created, the balance - /// is initialized correctly. + /// Ideally, this interface would also conform to Receiver, Balance, Transferor, Provider, and Resolver + /// but that is not supported yet /// - pub resource interface Balance { + access(all) resource interface Vault: Receiver, Provider, Balance, ViewResolver.Resolver, Burner.Burnable { - /// The total balance of a vault - /// - pub var balance: UFix64 + /// Field that tracks the balance of a vault + access(all) var balance: UFix64 - init(balance: UFix64) { + /// Called when a fungible token is burned via the `Burner.burn()` method + /// Implementations can do any bookkeeping or emit any events + /// that should be emitted when a vault is destroyed. + /// Many implementations will want to update the token's total supply + /// to reflect that the tokens have been burned and removed from the supply. + /// Implementations also need to set the balance to zero before the end of the function + /// This is to prevent vault owners from spamming fake Burned events. + access(contract) fun burnCallback() { + pre { + emit Burned(type: self.getType().identifier, amount: self.balance, fromUUID: self.uuid) + } post { - self.balance == balance: - "Balance must be initialized to the initial balance" + self.balance == 0.0: "The balance must be set to zero during the burnCallback method so that it cannot be spammed" } } - } - /// Vault - /// - /// The resource that contains the functions to send and receive tokens. - /// - pub resource Vault: Provider, Receiver, Balance { - - // The declaration of a concrete type in a contract interface means that - // every Fungible Token contract that implements the FungibleToken interface - // must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`, - // and `Balance` interfaces, and declares their required fields and functions - - /// The total balance of the vault - /// - pub var balance: UFix64 + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + /// The default implementation is included here because vaults are expected + /// to only accepted their own type, so they have no need to provide an implementation + /// for this function + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + // Below check is implemented to make sure that run-time type would + // only get returned when the parent resource conforms with `FungibleToken.Vault`. + if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) { + return {self.getType(): true} + } else { + // Return an empty dictionary as the default value for resource who don't + // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. + return {} + } + } - // The conforming type must declare an initializer - // that allows prioviding the initial balance of the Vault - // - init(balance: UFix64) + /// Checks if the given type is supported by this Vault + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } /// withdraw subtracts `amount` from the Vault's balance /// and returns a new Vault with the subtracted balance /// - pub fun withdraw(amount: UFix64): @Vault { + access(Withdraw) fun withdraw(amount: UFix64): @{Vault} { pre { self.balance >= amount: "Amount withdrawn must be less than or equal than the balance of the Vault" } post { + result.getType() == self.getType(): "Must return the same vault type as self" // use the special function `before` to get the value of the `balance` field // at the beginning of the function execution // self.balance == before(self.balance) - amount: - "New Vault balance must be the difference of the previous balance and the withdrawn Vault" + "New Vault balance must be the difference of the previous balance and the withdrawn Vault balance" } } /// deposit takes a Vault and adds its balance to the balance of this Vault /// - pub fun deposit(from: @Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { + // Assert that the concrete type of the deposited vault is the same + // as the vault that is accepting the deposit + pre { + from.isInstance(self.getType()): + "Cannot deposit an incompatible token type" + emit Deposited(type: from.getType().identifier, amount: from.balance, to: self.owner?.address, toUUID: self.uuid, depositedUUID: from.uuid) + } post { self.balance == before(self.balance) + before(from.balance): "New Vault balance must be the sum of the previous balance and the deposited Vault" } } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{Vault} { + post { + result.balance == 0.0: "The newly created Vault must have zero balance" + } + } } /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - pub fun createEmptyVault(): @Vault { + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { post { + result.getType() == vaultType: "The returned vault does not match the desired type" result.balance == 0.0: "The newly created Vault must have zero balance" } } diff --git a/cadence/contracts/FungibleTokenMetadataViews.cdc b/cadence/contracts/FungibleTokenMetadataViews.cdc new file mode 100644 index 0000000..789d779 --- /dev/null +++ b/cadence/contracts/FungibleTokenMetadataViews.cdc @@ -0,0 +1,180 @@ +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" +import ViewResolver from "ViewResolver" + +/// This contract implements the metadata standard proposed +/// in FLIP-1087. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20220811-fungible-tokens-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata. +/// +access(all) contract FungibleTokenMetadataViews { + + /// FTView wraps FTDisplay and FTVaultData, and is used to give a complete + /// picture of a Fungible Token. Most Fungible Token contracts should + /// implement this view. + /// + access(all) struct FTView { + access(all) let ftDisplay: FTDisplay? + access(all) let ftVaultData: FTVaultData? + view init( + ftDisplay: FTDisplay?, + ftVaultData: FTVaultData? + ) { + self.ftDisplay = ftDisplay + self.ftVaultData = ftVaultData + } + } + + /// Helper to get a FT view. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A FTView struct + /// + access(all) fun getFTView(viewResolver: &{ViewResolver.Resolver}): FTView { + let maybeFTView = viewResolver.resolveView(Type()) + if let ftView = maybeFTView { + return ftView as! FTView + } + return FTView( + ftDisplay: self.getFTDisplay(viewResolver), + ftVaultData: self.getFTVaultData(viewResolver) + ) + } + + /// View to expose the information needed to showcase this FT. + /// This can be used by applications to give an overview and + /// graphics of the FT. + /// + access(all) struct FTDisplay { + /// The display name for this token. + /// + /// Example: "Flow" + /// + access(all) let name: String + + /// The abbreviated symbol for this token. + /// + /// Example: "FLOW" + access(all) let symbol: String + + /// A description the provides an overview of this token. + /// + /// Example: "The FLOW token is the native currency of the Flow network." + access(all) let description: String + + /// External link to a URL to view more information about the fungible token. + access(all) let externalURL: MetadataViews.ExternalURL + + /// One or more versions of the fungible token logo. + access(all) let logos: MetadataViews.Medias + + /// Social links to reach the fungible token's social homepages. + /// Possible keys may be "instagram", "twitter", "discord", etc. + access(all) let socials: {String: MetadataViews.ExternalURL} + + view init( + name: String, + symbol: String, + description: String, + externalURL: MetadataViews.ExternalURL, + logos: MetadataViews.Medias, + socials: {String: MetadataViews.ExternalURL} + ) { + self.name = name + self.symbol = symbol + self.description = description + self.externalURL = externalURL + self.logos = logos + self.socials = socials + } + } + + /// Helper to get FTDisplay in a way that will return a typed optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional FTDisplay struct + /// + access(all) fun getFTDisplay(_ viewResolver: &{ViewResolver.Resolver}): FTDisplay? { + if let maybeDisplayView = viewResolver.resolveView(Type()) { + if let displayView = maybeDisplayView as? FTDisplay { + return displayView + } + } + return nil + } + + /// View to expose the information needed store and interact with a FT vault. + /// This can be used by applications to setup a FT vault with proper + /// storage and public capabilities. + /// + access(all) struct FTVaultData { + /// Path in storage where this FT vault is recommended to be stored. + access(all) let storagePath: StoragePath + + /// Public path which must be linked to expose the public receiver capability. + access(all) let receiverPath: PublicPath + + /// Public path which must be linked to expose the balance and resolver public capabilities. + access(all) let metadataPath: PublicPath + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `FungibleToken.Receiver` interface. + access(all) let receiverLinkedType: Type + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `ViewResolver.Resolver` interfaces. + access(all) let metadataLinkedType: Type + + /// Function that allows creation of an empty FT vault that is intended + /// to store the funds. + access(all) let createEmptyVault: fun(): @{FungibleToken.Vault} + + view init( + storagePath: StoragePath, + receiverPath: PublicPath, + metadataPath: PublicPath, + receiverLinkedType: Type, + metadataLinkedType: Type, + createEmptyVaultFunction: fun(): @{FungibleToken.Vault} + ) { + pre { + receiverLinkedType.isSubtype(of: Type<&{FungibleToken.Receiver}>()): "Receiver public type must include FungibleToken.Receiver." + metadataLinkedType.isSubtype(of: Type<&{FungibleToken.Vault}>()): "Metadata linked type must be a fungible token vault" + } + self.storagePath = storagePath + self.receiverPath = receiverPath + self.metadataPath = metadataPath + self.receiverLinkedType = receiverLinkedType + self.metadataLinkedType = metadataLinkedType + self.createEmptyVault = createEmptyVaultFunction + } + } + + /// Helper to get FTVaultData in a way that will return a typed Optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional FTVaultData struct + /// + access(all) fun getFTVaultData(_ viewResolver: &{ViewResolver.Resolver}): FTVaultData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? FTVaultData { + return v + } + } + return nil + } + + /// View to expose the total supply of the Vault's token + access(all) struct TotalSupply { + access(all) let supply: UFix64 + + view init(totalSupply: UFix64) { + self.supply = totalSupply + } + } +} + \ No newline at end of file diff --git a/cadence/contracts/MetadataViews.cdc b/cadence/contracts/MetadataViews.cdc new file mode 100644 index 0000000..6b151d3 --- /dev/null +++ b/cadence/contracts/MetadataViews.cdc @@ -0,0 +1,706 @@ +import FungibleToken from "FungibleToken" +import NonFungibleToken from "NonFungibleToken" +import ViewResolver from "ViewResolver" + +/// This contract implements the metadata standard proposed +/// in FLIP-0636. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20210916-nft-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata, such as a creator biography +/// or a JPEG image file. +/// +access(all) contract MetadataViews { + + /// Display is a basic view that includes the name, description and + /// thumbnail for an object. Most objects should implement this view. + /// + access(all) struct Display { + + /// The name of the object. + /// + /// This field will be displayed in lists and therefore should + /// be short an concise. + /// + access(all) let name: String + + /// A written description of the object. + /// + /// This field will be displayed in a detailed view of the object, + /// so can be more verbose (e.g. a paragraph instead of a single line). + /// + access(all) let description: String + + /// A small thumbnail representation of the object. + /// + /// This field should be a web-friendly file (i.e JPEG, PNG) + /// that can be displayed in lists, link previews, etc. + /// + access(all) let thumbnail: {File} + + view init( + name: String, + description: String, + thumbnail: {File} + ) { + self.name = name + self.description = description + self.thumbnail = thumbnail + } + } + + /// Helper to get Display in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Display struct + /// + access(all) fun getDisplay(_ viewResolver: &{ViewResolver.Resolver}) : Display? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Display { + return v + } + } + return nil + } + + /// Generic interface that represents a file stored on or off chain. Files + /// can be used to references images, videos and other media. + /// + access(all) struct interface File { + access(all) view fun uri(): String + } + + /// View to expose a file that is accessible at an HTTP (or HTTPS) URL. + /// + access(all) struct HTTPFile: File { + access(all) let url: String + + view init(url: String) { + self.url = url + } + + access(all) view fun uri(): String { + return self.url + } + } + + /// View to expose a file stored on IPFS. + /// IPFS images are referenced by their content identifier (CID) + /// rather than a direct URI. A client application can use this CID + /// to find and load the image via an IPFS gateway. + /// + access(all) struct IPFSFile: File { + + /// CID is the content identifier for this IPFS file. + /// + /// Ref: https://docs.ipfs.io/concepts/content-addressing/ + /// + access(all) let cid: String + + /// Path is an optional path to the file resource in an IPFS directory. + /// + /// This field is only needed if the file is inside a directory. + /// + /// Ref: https://docs.ipfs.io/concepts/file-systems/ + /// + access(all) let path: String? + + view init(cid: String, path: String?) { + self.cid = cid + self.path = path + } + + /// This function returns the IPFS native URL for this file. + /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls + /// + /// @return The string containing the file uri + /// + access(all) view fun uri(): String { + if let path = self.path { + return "ipfs://".concat(self.cid).concat("/").concat(path) + } + + return "ipfs://".concat(self.cid) + } + } + + /// View to represent a file with an correspoiding mediaType. + /// + access(all) struct Media { + + /// File for the media + /// + access(all) let file: {File} + + /// media-type comes on the form of type/subtype as described here + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types + /// + access(all) let mediaType: String + + view init(file: {File}, mediaType: String) { + self.file=file + self.mediaType=mediaType + } + } + + /// Wrapper view for multiple media views + /// + access(all) struct Medias { + + /// An arbitrary-sized list for any number of Media items + access(all) let items: [Media] + + view init(_ items: [Media]) { + self.items = items + } + } + + /// Helper to get Medias in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Medias struct + /// + access(all) fun getMedias(_ viewResolver: &{ViewResolver.Resolver}) : Medias? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Medias { + return v + } + } + return nil + } + + /// View to represent a license according to https://spdx.org/licenses/ + /// This view can be used if the content of an NFT is licensed. + /// + access(all) struct License { + access(all) let spdxIdentifier: String + + view init(_ identifier: String) { + self.spdxIdentifier = identifier + } + } + + /// Helper to get License in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional License struct + /// + access(all) fun getLicense(_ viewResolver: &{ViewResolver.Resolver}) : License? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? License { + return v + } + } + return nil + } + + /// View to expose a URL to this item on an external site. + /// This can be used by applications like .find and Blocto to direct users + /// to the original link for an NFT or a project page that describes the NFT collection. + /// eg https://www.my-nft-project.com/overview-of-nft-collection + /// + access(all) struct ExternalURL { + access(all) let url: String + + view init(_ url: String) { + self.url=url + } + } + + /// Helper to get ExternalURL in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional ExternalURL struct + /// + access(all) fun getExternalURL(_ viewResolver: &{ViewResolver.Resolver}) : ExternalURL? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? ExternalURL { + return v + } + } + return nil + } + + /// View that defines the composable royalty standard that gives marketplaces a + /// unified interface to support NFT royalties. + /// + access(all) struct Royalty { + + /// Generic FungibleToken Receiver for the beneficiary of the royalty + /// Can get the concrete type of the receiver with receiver.getType() + /// Recommendation - Users should create a new link for a FlowToken + /// receiver for this using `getRoyaltyReceiverPublicPath()`, and not + /// use the default FlowToken receiver. This will allow users to update + /// the capability in the future to use a more generic capability + access(all) let receiver: Capability<&{FungibleToken.Receiver}> + + /// Multiplier used to calculate the amount of sale value transferred to + /// royalty receiver. Note - It should be between 0.0 and 1.0 + /// Ex - If the sale value is x and multiplier is 0.56 then the royalty + /// value would be 0.56 * x. + /// Generally percentage get represented in terms of basis points + /// in solidity based smart contracts while cadence offers `UFix64` + /// that already supports the basis points use case because its + /// operations are entirely deterministic integer operations and support + /// up to 8 points of precision. + access(all) let cut: UFix64 + + /// Optional description: This can be the cause of paying the royalty, + /// the relationship between the `wallet` and the NFT, or anything else + /// that the owner might want to specify. + access(all) let description: String + + view init(receiver: Capability<&{FungibleToken.Receiver}>, cut: UFix64, description: String) { + pre { + cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]" + } + self.receiver = receiver + self.cut = cut + self.description = description + } + } + + /// Wrapper view for multiple Royalty views. + /// Marketplaces can query this `Royalties` struct from NFTs + /// and are expected to pay royalties based on these specifications. + /// + access(all) struct Royalties { + + /// Array that tracks the individual royalties + access(self) let cutInfos: [Royalty] + + access(all) view init(_ cutInfos: [Royalty]) { + // Validate that sum of all cut multipliers should not be greater than 1.0 + var totalCut = 0.0 + for royalty in cutInfos { + totalCut = totalCut + royalty.cut + } + assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0") + // Assign the cutInfos + self.cutInfos = cutInfos + } + + /// Return the cutInfos list + /// + /// @return An array containing all the royalties structs + /// + access(all) view fun getRoyalties(): [Royalty] { + return self.cutInfos + } + } + + /// Helper to get Royalties in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Royalties struct + /// + access(all) fun getRoyalties(_ viewResolver: &{ViewResolver.Resolver}) : Royalties? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Royalties { + return v + } + } + return nil + } + + /// Get the path that should be used for receiving royalties + /// This is a path that will eventually be used for a generic switchboard receiver, + /// hence the name but will only be used for royalties for now. + /// + /// @return The PublicPath for the generic FT receiver + /// + access(all) view fun getRoyaltyReceiverPublicPath(): PublicPath { + return /public/GenericFTReceiver + } + + /// View to represent a single field of metadata on an NFT. + /// This is used to get traits of individual key/value pairs along with some + /// contextualized data about the trait + /// + access(all) struct Trait { + // The name of the trait. Like Background, Eyes, Hair, etc. + access(all) let name: String + + // The underlying value of the trait, the rest of the fields of a trait provide context to the value. + access(all) let value: AnyStruct + + // displayType is used to show some context about what this name and value represent + // for instance, you could set value to a unix timestamp, and specify displayType as "Date" to tell + // platforms to consume this trait as a date and not a number + access(all) let displayType: String? + + // Rarity can also be used directly on an attribute. + // + // This is optional because not all attributes need to contribute to the NFT's rarity. + access(all) let rarity: Rarity? + + view init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) { + self.name = name + self.value = value + self.displayType = displayType + self.rarity = rarity + } + } + + /// Wrapper view to return all the traits on an NFT. + /// This is used to return traits as individual key/value pairs along with + /// some contextualized data about each trait. + access(all) struct Traits { + access(all) let traits: [Trait] + + view init(_ traits: [Trait]) { + self.traits = traits + } + + /// Adds a single Trait to the Traits view + /// + /// @param Trait: The trait struct to be added + /// + access(all) fun addTrait(_ t: Trait) { + self.traits.append(t) + } + } + + /// Helper to get Traits view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Traits struct + /// + access(all) fun getTraits(_ viewResolver: &{ViewResolver.Resolver}) : Traits? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Traits { + return v + } + } + return nil + } + + /// Helper function to easily convert a dictionary to traits. For NFT + /// collections that do not need either of the optional values of a Trait, + /// this method should suffice to give them an array of valid traits. + /// + /// @param dict: The dictionary to be converted to Traits + /// @param excludedNames: An optional String array specifying the `dict` + /// keys that are not wanted to become `Traits` + /// @return The generated Traits view + /// + access(all) fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits { + // Collection owners might not want all the fields in their metadata included. + // They might want to handle some specially, or they might just not want them included at all. + if excludedNames != nil { + for k in excludedNames! { + dict.remove(key: k) + } + } + + let traits: [Trait] = [] + for k in dict.keys { + let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil) + traits.append(trait) + } + + return Traits(traits) + } + + /// Optional view for collections that issue multiple objects + /// with the same or similar metadata, for example an X of 100 set. This + /// information is useful for wallets and marketplaces. + /// An NFT might be part of multiple editions, which is why the edition + /// information is returned as an arbitrary sized array + /// + access(all) struct Edition { + + /// The name of the edition + /// For example, this could be Set, Play, Series, + /// or any other way a project could classify its editions + access(all) let name: String? + + /// The edition number of the object. + /// For an "24 of 100 (#24/100)" item, the number is 24. + access(all) let number: UInt64 + + /// The max edition number of this type of objects. + /// This field should only be provided for limited-editioned objects. + /// For an "24 of 100 (#24/100)" item, max is 100. + /// For an item with unlimited edition, max should be set to nil. + /// + access(all) let max: UInt64? + + view init(name: String?, number: UInt64, max: UInt64?) { + if max != nil { + assert(number <= max!, message: "The number cannot be greater than the max number!") + } + self.name = name + self.number = number + self.max = max + } + } + + /// Wrapper view for multiple Edition views + /// + access(all) struct Editions { + + /// An arbitrary-sized list for any number of editions + /// that the NFT might be a part of + access(all) let infoList: [Edition] + + view init(_ infoList: [Edition]) { + self.infoList = infoList + } + } + + /// Helper to get Editions in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Editions struct + /// + access(all) fun getEditions(_ viewResolver: &{ViewResolver.Resolver}) : Editions? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Editions { + return v + } + } + return nil + } + + /// View representing a project-defined serial number for a specific NFT + /// Projects have different definitions for what a serial number should be + /// Some may use the NFTs regular ID and some may use a different + /// classification system. The serial number is expected to be unique among + /// other NFTs within that project + /// + access(all) struct Serial { + access(all) let number: UInt64 + + view init(_ number: UInt64) { + self.number = number + } + } + + /// Helper to get Serial in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Serial struct + /// + access(all) fun getSerial(_ viewResolver: &{ViewResolver.Resolver}) : Serial? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Serial { + return v + } + } + return nil + } + + /// View to expose rarity information for a single rarity + /// Note that a rarity needs to have either score or description but it can + /// have both + /// + access(all) struct Rarity { + /// The score of the rarity as a number + access(all) let score: UFix64? + + /// The maximum value of score + access(all) let max: UFix64? + + /// The description of the rarity as a string. + /// + /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value + access(all) let description: String? + + view init(score: UFix64?, max: UFix64?, description: String?) { + if score == nil && description == nil { + panic("A Rarity needs to set score, description or both") + } + + self.score = score + self.max = max + self.description = description + } + } + + /// Helper to get Rarity view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Rarity struct + /// + access(all) fun getRarity(_ viewResolver: &{ViewResolver.Resolver}) : Rarity? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Rarity { + return v + } + } + return nil + } + + /// NFTView wraps all Core views along `id` and `uuid` fields, and is used + /// to give a complete picture of an NFT. Most NFTs should implement this + /// view. + /// + access(all) struct NFTView { + access(all) let id: UInt64 + access(all) let uuid: UInt64 + access(all) let display: MetadataViews.Display? + access(all) let externalURL: MetadataViews.ExternalURL? + access(all) let collectionData: NFTCollectionData? + access(all) let collectionDisplay: NFTCollectionDisplay? + access(all) let royalties: Royalties? + access(all) let traits: Traits? + + view init( + id : UInt64, + uuid : UInt64, + display : MetadataViews.Display?, + externalURL : MetadataViews.ExternalURL?, + collectionData : NFTCollectionData?, + collectionDisplay : NFTCollectionDisplay?, + royalties : Royalties?, + traits: Traits? + ) { + self.id = id + self.uuid = uuid + self.display = display + self.externalURL = externalURL + self.collectionData = collectionData + self.collectionDisplay = collectionDisplay + self.royalties = royalties + self.traits = traits + } + } + + /// Helper to get an NFT view + /// + /// @param id: The NFT id + /// @param viewResolver: A reference to the resolver resource + /// @return A NFTView struct + /// + access(all) fun getNFTView(id: UInt64, viewResolver: &{ViewResolver.Resolver}) : NFTView { + let nftView = viewResolver.resolveView(Type()) + if nftView != nil { + return nftView! as! NFTView + } + + return NFTView( + id : id, + uuid: viewResolver.uuid, + display: MetadataViews.getDisplay(viewResolver), + externalURL : MetadataViews.getExternalURL(viewResolver), + collectionData : self.getNFTCollectionData(viewResolver), + collectionDisplay : self.getNFTCollectionDisplay(viewResolver), + royalties : self.getRoyalties(viewResolver), + traits : self.getTraits(viewResolver) + ) + } + + /// View to expose the information needed store and retrieve an NFT. + /// This can be used by applications to setup a NFT collection with proper + /// storage and public capabilities. + /// + access(all) struct NFTCollectionData { + /// Path in storage where this NFT is recommended to be stored. + access(all) let storagePath: StoragePath + + /// Public path which must be linked to expose public capabilities of this NFT + /// including standard NFT interfaces and metadataviews interfaces + access(all) let publicPath: PublicPath + + /// The concrete type of the collection that is exposed to the public + /// now that entitlements exist, it no longer needs to be restricted to a specific interface + access(all) let publicCollection: Type + + /// Type that should be linked at the aforementioned public path + access(all) let publicLinkedType: Type + + /// Function that allows creation of an empty NFT collection that is intended to store + /// this NFT. + access(all) let createEmptyCollection: fun(): @{NonFungibleToken.Collection} + + view init( + storagePath: StoragePath, + publicPath: PublicPath, + publicCollection: Type, + publicLinkedType: Type, + createEmptyCollectionFunction: fun(): @{NonFungibleToken.Collection} + ) { + pre { + publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.Collection}>()): "Public type must be a subtype of NonFungibleToken.Collection interface." + } + self.storagePath=storagePath + self.publicPath=publicPath + self.publicCollection=publicCollection + self.publicLinkedType=publicLinkedType + self.createEmptyCollection=createEmptyCollectionFunction + } + } + + /// Helper to get NFTCollectionData in a way that will return an typed Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollectionData struct + /// + access(all) fun getNFTCollectionData(_ viewResolver: &{ViewResolver.Resolver}) : NFTCollectionData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionData { + return v + } + } + return nil + } + + /// View to expose the information needed to showcase this NFT's + /// collection. This can be used by applications to give an overview and + /// graphics of the NFT collection this NFT belongs to. + /// + access(all) struct NFTCollectionDisplay { + // Name that should be used when displaying this NFT collection. + access(all) let name: String + + // Description that should be used to give an overview of this collection. + access(all) let description: String + + // External link to a URL to view more information about this collection. + access(all) let externalURL: MetadataViews.ExternalURL + + // Square-sized image to represent this collection. + access(all) let squareImage: MetadataViews.Media + + // Banner-sized image for this collection, recommended to have a size near 1200x630. + access(all) let bannerImage: MetadataViews.Media + + // Social links to reach this collection's social homepages. + // Possible keys may be "instagram", "twitter", "discord", etc. + access(all) let socials: {String: MetadataViews.ExternalURL} + + view init( + name: String, + description: String, + externalURL: MetadataViews.ExternalURL, + squareImage: MetadataViews.Media, + bannerImage: MetadataViews.Media, + socials: {String: MetadataViews.ExternalURL} + ) { + self.name = name + self.description = description + self.externalURL = externalURL + self.squareImage = squareImage + self.bannerImage = bannerImage + self.socials = socials + } + } + + /// Helper to get NFTCollectionDisplay in a way that will return a typed + /// Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollection struct + /// + access(all) fun getNFTCollectionDisplay(_ viewResolver: &{ViewResolver.Resolver}) : NFTCollectionDisplay? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionDisplay { + return v + } + } + return nil + } +} \ No newline at end of file diff --git a/cadence/contracts/NonFungibleToken.cdc b/cadence/contracts/NonFungibleToken.cdc new file mode 100644 index 0000000..ab12116 --- /dev/null +++ b/cadence/contracts/NonFungibleToken.cdc @@ -0,0 +1,234 @@ +/** + +## The Flow Non-Fungible Token standard + +## `NonFungibleToken` contract + +The interface that all Non-Fungible Token contracts should conform to. +If a user wants to deploy a new NFT contract, their contract should implement +The types defined here + +## `NFT` resource interface + +The core resource type that represents an NFT in the smart contract. + +## `Collection` Resource interface + +The resource that stores a user's NFT collection. +It includes a few functions to allow the owner to easily +move tokens in and out of the collection. + +## `Provider` and `Receiver` resource interfaces + +These interfaces declare functions with some pre and post conditions +that require the Collection to follow certain naming and behavior standards. + +They are separate because it gives developers the ability to define functions +that can use any type that implements these interfaces + +By using resources and interfaces, users of NFT smart contracts can send +and receive tokens peer-to-peer, without having to interact with a central ledger +smart contract. + +To send an NFT to another user, a user would simply withdraw the NFT +from their Collection, then call the deposit function on another user's +Collection to complete the transfer. + +*/ + +import ViewResolver from "ViewResolver" + +/// The main NFT contract. Other NFT contracts will +/// import and implement the interfaces defined in this contract +/// +access(all) contract interface NonFungibleToken: ViewResolver { + + /// An entitlement for allowing the withdrawal of tokens from a Vault + access(all) entitlement Withdraw + + /// An entitlement for allowing updates and update events for an NFT + access(all) entitlement Update + + /// entitlement for owner that grants Withdraw and Update + access(all) entitlement Owner + + /// Event that contracts should emit when the metadata of an NFT is updated + /// It can only be emitted by calling the `emitNFTUpdated` function + /// with an `Updatable` entitled reference to the NFT that was updated + /// The entitlement prevents spammers from calling this from other users' collections + /// because only code within a collection or that has special entitled access + /// to the collections methods will be able to get the entitled reference + /// + /// The event makes it so that third-party indexers can monitor the events + /// and query the updated metadata from the owners' collections. + /// + access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?) + access(contract) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT}) + { + emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address) + } + + + /// Event that is emitted when a token is withdrawn, + /// indicating the type, id, uuid, the owner of the collection that it was withdrawn from, + /// and the UUID of the resource it was withdrawn from, usually a collection. + /// + /// If the collection is not in an account's storage, `from` will be `nil`. + /// + access(all) event Withdrawn(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64) + + /// Event that emitted when a token is deposited to a collection. + /// Indicates the type, id, uuid, the owner of the collection that it was deposited to, + /// and the UUID of the collection it was deposited to + /// + /// If the collection is not in an account's storage, `from`, will be `nil`. + /// + access(all) event Deposited(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64) + + /// Included for backwards-compatibility + access(all) resource interface INFT: NFT {} + + /// Interface that the NFTs must conform to + /// + access(all) resource interface NFT: ViewResolver.Resolver { + + /// unique ID for the NFT + access(all) let id: UInt64 + + /// Event that is emitted automatically every time a resource is destroyed + /// The type information is included in the metadata event so it is not needed as an argument + access(all) event ResourceDestroyed(id: UInt64 = self.id, uuid: UInt64 = self.uuid) + + /// createEmptyCollection creates an empty Collection that is able to store the NFT + /// and returns it to the caller so that they can own NFTs + /// @return A an empty collection that can store this NFT + access(all) fun createEmptyCollection(): @{Collection} { + post { + result.getLength() == 0: "The created collection must be empty!" + } + } + + /// Gets all the NFTs that this NFT directly owns + /// @return A dictionary of all subNFTS keyed by type + access(all) view fun getAvailableSubNFTS(): {Type: UInt64} { + return {} + } + + /// Get a reference to an NFT that this NFT owns + /// Both arguments are optional to allow the NFT to choose + /// how it returns sub NFTs depending on what arguments are provided + /// For example, if `type` has a value, but `id` doesn't, the NFT + /// can choose which NFT of that type to return if there is a "default" + /// If both are `nil`, then NFTs that only store a single NFT can just return + /// that. This helps callers who aren't sure what they are looking for + /// + /// @param type: The Type of the desired NFT + /// @param id: The id of the NFT to borrow + /// + /// @return A structure representing the requested view. + access(all) fun getSubNFT(type: Type, id: UInt64) : &{NonFungibleToken.NFT}? { + return nil + } + } + + /// Interface to mediate withdrawals from a resource, usually a Collection + /// + access(all) resource interface Provider { + + // We emit withdraw events from the provider interface because conficting withdraw + // events aren't as confusing to event listeners as conflicting deposit events + + /// withdraw removes an NFT from the collection and moves it to the caller + /// It does not specify whether the ID is UUID or not + /// @param withdrawID: The id of the NFT to withdraw from the collection + access(Withdraw | Owner) fun withdraw(withdrawID: UInt64): @{NFT} { + post { + result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" + emit Withdrawn(type: result.getType().identifier, id: result.id, uuid: result.uuid, from: self.owner?.address, providerUUID: self.uuid) + } + } + } + + /// Interface to mediate deposits to the Collection + /// + access(all) resource interface Receiver { + + /// deposit takes an NFT as an argument and adds it to the Collection + /// @param token: The NFT to deposit + access(all) fun deposit(token: @{NFT}) + + /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts + /// @return A dictionary of types mapped to booleans indicating if this + /// reciever supports it + access(all) view fun getSupportedNFTTypes(): {Type: Bool} + + /// Returns whether or not the given type is accepted by the collection + /// A collection that can accept any type should just return true by default + /// @param type: An NFT type + /// @return A boolean indicating if this receiver can recieve the desired NFT type + access(all) view fun isSupportedNFTType(type: Type): Bool + } + + /// Kept for backwards-compatibility reasons + access(all) resource interface CollectionPublic { + access(all) fun deposit(token: @{NFT}) + access(all) view fun getLength(): Int + access(all) view fun getIDs(): [UInt64] + access(all) view fun borrowNFT(_ id: UInt64): &{NFT}? + } + + /// Requirement for the concrete resource type + /// to be declared in the implementing contract + /// + access(all) resource interface Collection: Provider, Receiver, CollectionPublic, ViewResolver.ResolverCollection { + + /// deposit takes a NFT as an argument and stores it in the collection + /// @param token: The NFT to deposit into the collection + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { + pre { + // We emit the deposit event in the `Collection` interface + // because the `Collection` interface is almost always the final destination + // of tokens and deposit emissions from custom receivers could be confusing + // and hard to reconcile to event listeners + emit Deposited(type: token.getType().identifier, id: token.id, uuid: token.uuid, to: self.owner?.address, collectionUUID: self.uuid) + } + } + + /// Gets the amount of NFTs stored in the collection + /// @return An integer indicating the size of the collection + access(all) view fun getLength(): Int + + /// Borrows a reference to an NFT stored in the collection + /// If the NFT with the specified ID is not in the collection, + /// the function should return `nil` and not panic. + /// + /// @param id: The desired nft id in the collection to return a referece for. + /// @return An optional reference to the NFT + access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? { + post { + (result == nil) || (result?.id == id): + "Cannot borrow NFT reference: The ID of the returned reference does not match the ID that was specified" + } + } + + /// createEmptyCollection creates an empty Collection of the same type + /// and returns it to the caller + /// @return A an empty collection of the same type + access(all) fun createEmptyCollection(): @{Collection} { + post { + result.getType() == self.getType(): "The created collection does not have the same type as this collection" + result.getLength() == 0: "The created collection must be empty!" + } + } + } + + /// createEmptyCollection creates an empty Collection for the specified NFT type + /// and returns it to the caller so that they can own NFTs + /// @param nftType: The desired nft type to return a collection for. + /// @return An array of NFT Types that the implementing contract defines. + access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} { + post { + result.getIDs().length == 0: "The created collection must be empty!" + } + } +} \ No newline at end of file diff --git a/cadence/contracts/ViewResolver.cdc b/cadence/contracts/ViewResolver.cdc new file mode 100644 index 0000000..862a4e8 --- /dev/null +++ b/cadence/contracts/ViewResolver.cdc @@ -0,0 +1,59 @@ +// Taken from the NFT Metadata standard, this contract exposes an interface to let +// anyone borrow a contract and resolve views on it. +// +// This will allow you to obtain information about a contract without necessarily knowing anything about it. +// All you need is its address and name and you're good to go! +access(all) contract interface ViewResolver { + + /// Function that returns all the Metadata Views implemented by the resolving contract. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @return An array of Types defining the implemented views. This value will be used by + /// developers to know which parameter to pass to the resolveView() method. + /// + access(all) view fun getContractViews(resourceType: Type?): [Type] + + /// Function that resolves a metadata view for this token. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? + + /// Provides access to a set of metadata views. A struct or + /// resource (e.g. an NFT) can implement this interface to provide access to + /// the views that it supports. + /// + access(all) resource interface Resolver { + + /// Same as getViews above, but on a specific NFT instead of a contract + access(all) view fun getViews(): [Type] + + /// Same as resolveView above, but on a specific NFT instead of a contract + access(all) fun resolveView(_ view: Type): AnyStruct? + } + + /// A group of view resolvers indexed by ID. + /// + access(all) resource interface ResolverCollection { + access(all) view fun borrowViewResolver(id: UInt64): &{Resolver}? { + return nil + } + + access(all) view fun getIDs(): [UInt64] { + return [] + } + } +} + \ No newline at end of file diff --git a/flow.json b/flow.json index d81dbf6..36ba579 100644 --- a/flow.json +++ b/flow.json @@ -6,6 +6,12 @@ } }, "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "0xee82856bf20e2aa6" + } + }, "FUSD": { "source": "./cadence/contracts/FUSD.cdc", "aliases": { @@ -18,12 +24,40 @@ "emulator": "0xee82856bf20e2aa6", "testnet": "0x9a0766d93b6608b7" } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "0xf233dcee88fe0abe" + } } }, "networks": { "emulator": "127.0.0.1:3569", "testnet": "access.devnet.nodes.onflow.org:9000", - "crescendo": "access.crescendo.nodes.onflow.org:9000" + "crescendo": "access.crescendo.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { @@ -37,7 +71,11 @@ }, "deployments": { "emulator": { - "emulator-account": ["FUSD"] + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD" + ] } } -} +} \ No newline at end of file From ae508ed4eae085817e331ddf2c0f4e08201c5b26 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:17:46 -0600 Subject: [PATCH 02/88] add EVM contract --- cadence/contracts/EVM.cdc | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 cadence/contracts/EVM.cdc diff --git a/cadence/contracts/EVM.cdc b/cadence/contracts/EVM.cdc new file mode 100644 index 0000000..32bb949 --- /dev/null +++ b/cadence/contracts/EVM.cdc @@ -0,0 +1,160 @@ +import "FlowToken" + +access(all) +contract EVM { + + /// EVMAddress is an EVM-compatible address + access(all) + struct EVMAddress { + + /// Bytes of the address + access(all) + let bytes: [UInt8; 20] + + /// Constructs a new EVM address from the given byte representation + init(bytes: [UInt8; 20]) { + self.bytes = bytes + } + + /// Deposits the given vault into the EVM account with the given address + access(all) + fun deposit(from: @FlowToken.Vault) { + InternalEVM.deposit( + from: <-from, + to: self.bytes + ) + } + + /// Balance of the address + access(all) + fun balance(): Balance { + let balance = InternalEVM.balance( + address: self.bytes + ) + + return Balance(flow: balance) + } + } + + access(all) + struct Balance { + + /// The balance in FLOW + access(all) + let flow: UFix64 + + /// Constructs a new balance, given the balance in FLOW + init(flow: UFix64) { + self.flow = flow + } + + // TODO: + // /// Returns the balance in terms of atto-FLOW. + // /// Atto-FLOW is the smallest denomination of FLOW inside EVM + // access(all) + // fun toAttoFlow(): UInt64 + } + + access(all) + resource BridgedAccount { + + access(self) + let addressBytes: [UInt8; 20] + + init(addressBytes: [UInt8; 20]) { + self.addressBytes = addressBytes + } + + /// The EVM address of the bridged account + access(all) + fun address(): EVMAddress { + // Always create a new EVMAddress instance + return EVMAddress(bytes: self.addressBytes) + } + + /// Get balance of the bridged account + access(all) + fun balance(): Balance { + return self.address().balance() + } + + /// Deposits the given vault into the bridged account's balance + access(all) + fun deposit(from: @FlowToken.Vault) { + self.address().deposit(from: <-from) + } + + /// Withdraws the balance from the bridged account's balance + access(all) + fun withdraw(balance: Balance): @FlowToken.Vault { + let vault <- InternalEVM.withdraw( + from: self.addressBytes, + amount: balance.flow + ) as! @FlowToken.Vault + return <-vault + } + + /// Deploys a contract to the EVM environment. + /// Returns the address of the newly deployed contract + access(all) + fun deploy( + code: [UInt8], + gasLimit: UInt64, + value: Balance + ): EVMAddress { + let addressBytes = InternalEVM.deploy( + from: self.addressBytes, + code: code, + gasLimit: gasLimit, + value: value.flow + ) + return EVMAddress(bytes: addressBytes) + } + + /// Calls a function with the given data. + /// The execution is limited by the given amount of gas + access(all) + fun call( + to: EVMAddress, + data: [UInt8], + gasLimit: UInt64, + value: Balance + ): [UInt8] { + return InternalEVM.call( + from: self.addressBytes, + to: to.bytes, + data: data, + gasLimit: gasLimit, + value: value.flow + ) + } + } + + /// Creates a new bridged account + access(all) + fun createBridgedAccount(): @BridgedAccount { + return <-create BridgedAccount( + addressBytes: InternalEVM.createBridgedAccount() + ) + } + + /// Runs an a RLP-encoded EVM transaction, deducts the gas fees, + /// and deposits the gas fees into the provided coinbase address. + /// + /// Returns true if the transaction was successful, + /// and returns false otherwise + access(all) + fun run(tx: [UInt8], coinbase: EVMAddress) { + InternalEVM.run(tx: tx, coinbase: coinbase.bytes) + } + + access(all) + fun encodeABI(_ values: [AnyStruct]): [UInt8] { + return InternalEVM.encodeABI(values) + } + + access(all) + fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { + return InternalEVM.decodeABI(types: types, data: data) + } +} \ No newline at end of file From 42be8c3f4caf3390062732b1a19a2e734b0d0445 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:42:41 -0600 Subject: [PATCH 03/88] update FUSD for emulator FungibleToken conformance --- cadence/contracts/FUSD.cdc | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FUSD.cdc b/cadence/contracts/FUSD.cdc index 3bd2e29..31e8874 100644 --- a/cadence/contracts/FUSD.cdc +++ b/cadence/contracts/FUSD.cdc @@ -3,7 +3,7 @@ import MetadataViews from "MetadataViews" import FungibleTokenMetadataViews from "FungibleTokenMetadataViews" import ViewResolver from "ViewResolver" -access(all) contract FUSD: FungibleToken { +access(all) contract FUSD { access(all) entitlement ProxyOwner @@ -75,6 +75,19 @@ access(all) contract FUSD: FungibleToken { self.balance = balance } + // TODO: REMOVE BELOW + // BEGIN OLD INTERFACE METHODS + access(all) view fun getBalance(): UFix64 { + return self.balance + } + access(all) view fun getDefaultStoragePath(): StoragePath? { + return /storage/fusdVault + } + access(all) view fun getDefaultPublicPath(): PublicPath? { + return /public/fusdReceiver + } + // END OLD INTERFACE METHODS + /// Called when a fungible token is burned via the `Burner.burn()` method access(contract) fun burnCallback() { if self.balance > 0.0 { @@ -116,7 +129,7 @@ access(all) contract FUSD: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { + access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) From 12fb1c7133a0f655a933e86d47875ff1106356a1 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:06:50 -0600 Subject: [PATCH 04/88] add FlowPassthrough.sol contract --- solidity/src/FlowPassthrough.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 solidity/src/FlowPassthrough.sol diff --git a/solidity/src/FlowPassthrough.sol b/solidity/src/FlowPassthrough.sol new file mode 100644 index 0000000..ef6799c --- /dev/null +++ b/solidity/src/FlowPassthrough.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract FlowPassthrough { + + event Tranfered(address from, address to, uint256 amount); + + function transferFlow(address payable _to) public payable { + (bool sent, bytes memory data) = _to.call{value: msg.value}(""); + require(sent, "Failed to send Flow"); + emit Tranfered(msg.sender, _to, msg.value); + } +} \ No newline at end of file From fa585cf2f31118af12028f0aece3f49f739217e0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:07:52 -0600 Subject: [PATCH 05/88] add FlowEVMPassthrough.cdc contract & update flow.json --- cadence/contracts/FlowEVMPassthrough.cdc | 153 ++++++++++++++++++++ flow.json | 177 +++++++++++++---------- 2 files changed, 255 insertions(+), 75 deletions(-) create mode 100644 cadence/contracts/FlowEVMPassthrough.cdc diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc new file mode 100644 index 0000000..8ca2352 --- /dev/null +++ b/cadence/contracts/FlowEVMPassthrough.cdc @@ -0,0 +1,153 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// This contract defines a resource implementing FungibleToken Receiver and Provider interfaces that enables deposits +/// to an encapsulated COA and transfers to EVM addresses. Recipients in Flow EVM will receive the transfer from the +/// FlowPassthrough.sol contract address, but an event is emitted in both the Cadence & EVM environments denoting the +/// source and target addresses involved in the passthrough transfer. +/// +access(all) contract FlowEVMPassthrough { + + /// The address of the passthrough Solidity contract + access(all) let passthroughEVMAddress: EVM.EVMAddress + + /// Default storage path for the Passthrough resource + access(all) let StoragePath: StoragePath + /// Default public path for the Passthrough resource + access(all) let PublicPath: PublicPath + + /// Event emitted when a transfer occurs, denoting the transferor, recipient and amount transfered via passthrough + access(all) event Transfered(from: EVM.EVMAddress, to: EVM.EVMAddress, amount: UFix64) + + /* --- Public Methods --- */ + /// + access(all) fun createPassthrough(with coa: Capability<&EVM.BridgedAccount>): @Passthrough { + return <-create Passthrough(coa) + } + + /* --- Passthrough --- */ + // + /// Encapsulates a COA and provides a FungibleToken Receiver and Provider interface through which to deposit & + /// withdraw to/from the COA. Also enables transfers to EVM addresses using a call to a passthrough Solidity + /// contract. + /// + access(all) resource Passthrough : FungibleToken.Receiver, FungibleToken.Provider { + /// Capability to owner's COA + access(self) let coaCapability: Capability<&EVM.BridgedAccount> + + init(_ coaCapability: Capability<&EVM.BridgedAccount>) { + pre { + coaCapability.check(): "Invalid COA Capability provided" + } + self.coaCapability = coaCapability + } + + /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts + access(all) view fun getSupportedVaultTypes(): {Type: Bool} { + return { Type<@FlowToken.Vault>(): true } + } + + /// Returns whether or not the given type is accepted by the Receiver + /// A vault that can accept any type should just return true by default + access(all) view fun isSupportedVaultType(type: Type): Bool { + return self.getSupportedVaultTypes()[type] ?? false + } + + /// Deposits Flow to encapsulated COA according to Receiver interface + access(all) fun deposit(from: @{FungibleToken.Vault}) { + pre { + from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + } + let flowVault <- from as! @FlowToken.Vault + self.borrowCOA().deposit(from: <-flowVault) + } + + access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { + return <-self.borrowCOA().withdraw( + balance: EVM.Balance(flow: amount) + ) + } + + /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract + access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { + pre { + from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + } + let amount = from.getBalance() + // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) + let amountInAttoFlow = FlowEVMPassthrough.ufix64ToUInt256(value: from.getBalance(), decimals: 8) + // TODO: Replace with EVM.encodeABIWithSignature("transferFlow(address)", [to]) + let calldata = FlowEVMPassthrough.encodeABIWithSignature( + "transferFlow(address)", + [to] + ) + let coa = self.borrowCOA() + let flowVault <- from as! @FlowToken.Vault + coa.deposit(from: <-flowVault) + coa.call( + to: FlowEVMPassthrough.passthroughEVMAddress, + data: calldata, + gasLimit: gasLimit, + value: EVM.Balance(flow: amount) + ) + + emit Transfered(from: self.borrowCOA().address(), to: to, amount: amount) + } + + access(self) fun borrowCOA(): &EVM.BridgedAccount { + return self.coaCapability.borrow() ?? panic("COA Capability has been revoked and is no longer valid") + } + } + + /* --- Internal Helpers --- */ + // TODO: Remove helpers once EVM.Balance.toAttoFlow() is implemented + + /// Raises the base to the power of the exponent + access(self) view fun pow(base: UInt256, exponent: UInt8): UInt256 { + if exponent == 0 { + return 1 + } + var r = base + var exp: UInt8 = 1 + while exp < exponent { + r = r * base + exp = exp + 1 + } + return r + } + + /// Converts a UFix64 to a UInt256 + access(self) view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 { + let integerPart: UInt64 = UInt64(value) + var r = UInt256(integerPart) + var multiplier: UInt256 = self.pow(base:10, exponent: decimals) + return r * multiplier + } + + access(self) fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = EVM.encodeABI(values) + + return methodID.concat(arguments) + } + + init(passthroughBytecode: String) { + let coa = self.account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("COA not found") + let passthroughEVMAddress = coa.deploy( + code: passthroughBytecode.decodeHex(), + gasLimit: 12000000, + value: EVM.Balance(flow: 0.0) + ) + self.passthroughEVMAddress = passthroughEVMAddress + self.StoragePath = /storage/flowEVMPassthrough + self.PublicPath = /public/flowEVMPassthroughPublic + } +} diff --git a/flow.json b/flow.json index 36ba579..0a218e6 100644 --- a/flow.json +++ b/flow.json @@ -1,81 +1,108 @@ { - "emulators": { - "default": { - "port": 3569, - "serviceAccount": "emulator-account" - } - }, - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "0xee82856bf20e2aa6" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "testnet": "0xe223d8a629e49c68" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "emulator": "0xee82856bf20e2aa6", - "testnet": "0x9a0766d93b6608b7" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "0xf233dcee88fe0abe" - } - } - }, - "networks": { - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000", - "crescendo": "access.crescendo.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowEVMPassthrough": "./cadence/contracts/FlowEVMPassthrough.cdc", + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD" - ] - } - } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } } \ No newline at end of file From 6b58373adf59179566dcfa4aba5707d51e6e2afa Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:08:29 -0600 Subject: [PATCH 06/88] add evm scripts --- cadence/scripts/evm/get_balance.cdc | 14 ++++++++++++++ cadence/scripts/evm/get_evm_address_string.cdc | 15 +++++++++++++++ .../scripts/evm/get_passthrough_evm_address.cdc | 8 ++++++++ 3 files changed, 37 insertions(+) create mode 100644 cadence/scripts/evm/get_balance.cdc create mode 100644 cadence/scripts/evm/get_evm_address_string.cdc create mode 100644 cadence/scripts/evm/get_passthrough_evm_address.cdc diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc new file mode 100644 index 0000000..ea03aca --- /dev/null +++ b/cadence/scripts/evm/get_balance.cdc @@ -0,0 +1,14 @@ +import "EVM" + +/// Returns the Flow balance of of a given EVM address in FlowEVM +/// +access(all) fun main(address: String): UFix64 { + let bytes = address.decodeHex() + let addressBytes: [UInt8; 20] = [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], + bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], + bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ] + return EVM.EVMAddress(bytes: addressBytes).balance().flow +} diff --git a/cadence/scripts/evm/get_evm_address_string.cdc b/cadence/scripts/evm/get_evm_address_string.cdc new file mode 100644 index 0000000..a481d16 --- /dev/null +++ b/cadence/scripts/evm/get_evm_address_string.cdc @@ -0,0 +1,15 @@ +import "EVM" + +/// Returns the hex encoded address of the COA in the given Flow address +/// +access(all) fun main(flowAddress: Address): String? { + let account = getAuthAccount(flowAddress) + if let address = account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm)?.address() { + let bytes: [UInt8] = [] + for byte in address.bytes { + bytes.append(byte) + } + return String.encodeHex(bytes) + } + return nil +} diff --git a/cadence/scripts/evm/get_passthrough_evm_address.cdc b/cadence/scripts/evm/get_passthrough_evm_address.cdc new file mode 100644 index 0000000..ad103d0 --- /dev/null +++ b/cadence/scripts/evm/get_passthrough_evm_address.cdc @@ -0,0 +1,8 @@ +import "EVM" + +import "FlowEVMPassthrough" + +/// Returns the EVM address of the FlowEVM passthrough contract. +access(all) fun main(): EVM.EVMAddress { + return FlowEVMPassthrough.passthroughEVMAddress +} From 35ac42413949bca999fbca986a7f7c62f45e0837 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:08:51 -0600 Subject: [PATCH 07/88] add evm transactions --- cadence/transactions/evm/create_account.cdc | 28 ++++++++++++++++ .../evm/passthrough_evm_transfer.cdc | 33 +++++++++++++++++++ .../transactions/evm/setup_passthrough.cdc | 23 +++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 cadence/transactions/evm/create_account.cdc create mode 100644 cadence/transactions/evm/passthrough_evm_transfer.cdc create mode 100644 cadence/transactions/evm/setup_passthrough.cdc diff --git a/cadence/transactions/evm/create_account.cdc b/cadence/transactions/evm/create_account.cdc new file mode 100644 index 0000000..a153a81 --- /dev/null +++ b/cadence/transactions/evm/create_account.cdc @@ -0,0 +1,28 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// Creates a COA and saves it in the signer's Flow account & passing the given value of Flow into FlowEVM +/// +transaction(amount: UFix64) { + let sentVault: @FlowToken.Vault + let auth: auth(Storage) &Account + + prepare(signer: auth(Storage) &Account) { + let vaultRef = signer.storage.borrow( + from: /storage/flowTokenVault + ) ?? panic("Could not borrow reference to the owner's Vault!") + + self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault + self.auth = signer + } + + execute { + let account <- EVM.createBridgedAccount() + account.address().deposit(from: <-self.sentVault) + + log(account.balance()) + self.auth.storage.save<@EVM.BridgedAccount>(<-account, to: StoragePath(identifier: "evm")!) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc new file mode 100644 index 0000000..1f674d2 --- /dev/null +++ b/cadence/transactions/evm/passthrough_evm_transfer.cdc @@ -0,0 +1,33 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "FlowEVMPassthrough" + +/// Withdraws tokens from the signer's FlowToken Vault and transfers them to the given EVM address via the saved +/// Passthrough resource. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let sentVault: @{FungibleToken.Vault} + let passthrough: &FlowEVMPassthrough.Passthrough + + prepare(signer: auth(BorrowValue) &Account) { + let sourceVault = signer.storage.borrow(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + self.sentVault <- sourceVault.withdraw(amount: amount) + self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) + ?? panic("Could not borrow reference to the owner's Passthrough!") + } + + execute { + let bytes = toAsHex.decodeHex() + let to = EVM.EVMAddress( + bytes: [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ]) + self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) + } +} diff --git a/cadence/transactions/evm/setup_passthrough.cdc b/cadence/transactions/evm/setup_passthrough.cdc new file mode 100644 index 0000000..5dd39b8 --- /dev/null +++ b/cadence/transactions/evm/setup_passthrough.cdc @@ -0,0 +1,23 @@ +import "EVM" + +import "FlowEVMPassthrough" + +/// Configures a Passthrough resource in the signer's storage +/// +transaction { + + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + if signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) != nil { + return + } + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + let coaCapability = signer.capabilities.storage.issue<&EVM.BridgedAccount>(/storage/evm) + signer.storage.save(<-FlowEVMPassthrough.createPassthrough(with: coaCapability), to: FlowEVMPassthrough.StoragePath) + let passthroughCapability = signer.capabilities.storage.issue<&FlowEVMPassthrough.Passthrough>( + FlowEVMPassthrough.StoragePath + ) + signer.capabilities.publish(passthroughCapability, at: FlowEVMPassthrough.PublicPath) + } +} From f2ac15a4c247ba969ce367ecb64f3c59fc35c2c6 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:17:17 -0600 Subject: [PATCH 08/88] update all transactions for Cadence 1.0 compatibility --- cadence/transactions/deposit_fusd_minter.cdc | 16 ++++++---------- cadence/transactions/setup_fusd_minter.cdc | 10 ++++------ cadence/transactions/transfer_flow.cdc | 8 ++++---- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/cadence/transactions/deposit_fusd_minter.cdc b/cadence/transactions/deposit_fusd_minter.cdc index 43f9b44..03db622 100644 --- a/cadence/transactions/deposit_fusd_minter.cdc +++ b/cadence/transactions/deposit_fusd_minter.cdc @@ -19,23 +19,21 @@ transaction(minterAddress: Address) { let capabilityPrivatePath: CapabilityPath let minterCapability: Capability<&FUSD.Minter> - prepare(adminAccount: AuthAccount) { + prepare(adminAccount: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { // These paths must be unique within the FUSD contract account's storage self.resourceStoragePath = /storage/fusdAdminMinter self.capabilityPrivatePath = /private/fusdAdminMinter // Create a reference to the admin resource in storage. - let tokenAdmin = adminAccount.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath) + let tokenAdmin = adminAccount.storage.borrow<&FUSD.Administrator>(from: FUSD.AdminStoragePath) ?? panic("Could not borrow a reference to the admin resource") // Create a new minter resource and a private link to a capability for it in the admin's storage. let minter <- tokenAdmin.createNewMinter() adminAccount.save(<- minter, to: self.resourceStoragePath) - self.minterCapability = adminAccount.link<&FUSD.Minter>( - self.capabilityPrivatePath, - target: self.resourceStoragePath - ) ?? panic("Could not link minter") + self.minterCapability = adminAccount.capabilities.storage.issue<&FUSD.Minter>(self.resourceStoragePath) + ?? panic("Could not link minter") } @@ -43,10 +41,8 @@ transaction(minterAddress: Address) { // This is the account that the capability will be given to let minterAccount = getAccount(minterAddress) - let capabilityReceiver = minterAccount.getCapability - <&FUSD.MinterProxy{FUSD.MinterProxyPublic}> - (FUSD.MinterProxyPublicPath)! - .borrow() ?? panic("Could not borrow capability receiver reference") + let capabilityReceiver = minterAccount.capabilities.borrow<&FUSD.MinterProxy>(FUSD.MinterProxyPublicPath) + ?? panic("Could not borrow capability receiver reference") capabilityReceiver.setMinterCapability(cap: self.minterCapability) } diff --git a/cadence/transactions/setup_fusd_minter.cdc b/cadence/transactions/setup_fusd_minter.cdc index 8a01a65..5402e39 100644 --- a/cadence/transactions/setup_fusd_minter.cdc +++ b/cadence/transactions/setup_fusd_minter.cdc @@ -8,18 +8,16 @@ import FUSD from 0xFUSDADDRESS transaction { - prepare(minter: AuthAccount) { + prepare(minter: auth(IssueStorageCapabilityController, SaveValue) &Account) { let minterProxy <- FUSD.createMinterProxy() - minter.save( + minter.storage.save( <- minterProxy, to: FUSD.MinterProxyStoragePath, ) - minter.link<&FUSD.MinterProxy{FUSD.MinterProxyPublic}>( - FUSD.MinterProxyPublicPath, - target: FUSD.MinterProxyStoragePath - ) + let minterProxyCap = minter.capabilities.storage.issue<&FUSD.MinterProxy>(FUSD.MinterProxyStoragePath) + minter.capabilities.publish(minterProxyCap, at: FUSD.MinterProxyPublicPath) } } \ No newline at end of file diff --git a/cadence/transactions/transfer_flow.cdc b/cadence/transactions/transfer_flow.cdc index f637236..9d186a5 100644 --- a/cadence/transactions/transfer_flow.cdc +++ b/cadence/transactions/transfer_flow.cdc @@ -4,12 +4,12 @@ import FlowToken from "../contracts/FlowToken.cdc" transaction (amount: UFix64, to: Address) { // The Vault resource that holds the tokens that are being transferred - let sentVault: @FungibleToken.Vault + let sentVault: @{FungibleToken.Vault} - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault - let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") // Withdraw tokens from the signer's stored vault @@ -22,7 +22,7 @@ transaction (amount: UFix64, to: Address) { let recipient = getAccount(to) // Get a reference to the recipient's Receiver - let receiverRef = recipient.getCapability(/public/flowTokenReceiver)!.borrow<&{FungibleToken.Receiver}>() + let receiverRef = recipient.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Could not borrow receiver reference to the recipient's Vault") // Deposit the withdrawn tokens in the recipient's receiver From 67a13d80ae33ebc284bbd19fc9cca1238397b8db Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:05:21 -0600 Subject: [PATCH 09/88] update flow.json with crescendo aliases --- flow.json | 216 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 102 deletions(-) diff --git a/flow.json b/flow.json index 0a218e6..ed593c1 100644 --- a/flow.json +++ b/flow.json @@ -1,108 +1,120 @@ { - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6" - } - }, - "EVM": { - "source": "./cadence/contracts/EVM.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "emulator": "0ae53cb6e3f42a79", - "testnet": "e223d8a629e49c68" - } - }, - "FlowEVMPassthrough": "./cadence/contracts/FlowEVMPassthrough.cdc", - "FlowToken": { - "source": "./cadence/contracts/FlowToken.cdc", - "aliases": { - "emulator": "0ae53cb6e3f42a79", - "testnet": "7e60df042a9c0868" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6", - "testnet": "9a0766d93b6608b7" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "f233dcee88fe0abe" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - } - }, - "networks": { - "crescendo": "access.crescendo.nodes.onflow.org:9000", - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, - "emulator-flow": { - "address": "0ae53cb6e3f42a79", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "emulator-ft": { - "address": "ee82856bf20e2aa6", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "crescendo": "e223d8a629e49c68", + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowEVMPassthrough": { + "source": "./cadence/contracts/FlowEVMPassthrough.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "crescendo": "7e60df042a9c0868", + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "crescendo": "9a0766d93b6608b7", + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "crescendo": "f233dcee88fe0abe", + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "crescendo": "0x631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } } \ No newline at end of file From 5901ff18e4cd495676687961fae94b594be54c27 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:08:28 -0600 Subject: [PATCH 10/88] update FlowEVMPassthrough pre-condition syntax --- cadence/contracts/FlowEVMPassthrough.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc index 8ca2352..6df409c 100644 --- a/cadence/contracts/FlowEVMPassthrough.cdc +++ b/cadence/contracts/FlowEVMPassthrough.cdc @@ -58,7 +58,7 @@ access(all) contract FlowEVMPassthrough { /// Deposits Flow to encapsulated COA according to Receiver interface access(all) fun deposit(from: @{FungibleToken.Vault}) { pre { - from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" } let flowVault <- from as! @FlowToken.Vault self.borrowCOA().deposit(from: <-flowVault) @@ -73,7 +73,7 @@ access(all) contract FlowEVMPassthrough { /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { pre { - from.getType() == Type<@FlowToken.Vault>(): "Passthrough only supports FlowToken.Vaults" + self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" } let amount = from.getBalance() // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) From 12fa01b2bf0962df08625e328c8dfe8877fed075 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:55:09 -0600 Subject: [PATCH 11/88] add CryptoUtils contract & name in flow.json & env.example --- cadence/contracts/CryptoUtils.cdc | 34 +++++++++++++++++++++++++++++++ env.example | 3 +++ flow.json | 8 +++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 cadence/contracts/CryptoUtils.cdc diff --git a/cadence/contracts/CryptoUtils.cdc b/cadence/contracts/CryptoUtils.cdc new file mode 100644 index 0000000..c464d80 --- /dev/null +++ b/cadence/contracts/CryptoUtils.cdc @@ -0,0 +1,34 @@ +/// Util methods to reconstruct HashAlgorithm and SignatureAlgorithm from their raw enum values +/// +access(all) contract CryptoUtils { + + access(all) fun getHashAlgo(fromRawValue: UInt8): HashAlgorithm? { + switch fromRawValue { + case HashAlgorithm.SHA2_256.rawValue: + return HashAlgorithm.SHA2_256 + case HashAlgorithm.SHA2_384.rawValue: + return HashAlgorithm.SHA2_384 + case HashAlgorithm.SHA3_256.rawValue: + return HashAlgorithm.SHA3_256 + case HashAlgorithm.SHA3_384.rawValue: + return HashAlgorithm.SHA3_384 + case HashAlgorithm.KMAC128_BLS_BLS12_381.rawValue: + return HashAlgorithm.KMAC128_BLS_BLS12_381 + case HashAlgorithm.KECCAK_256.rawValue: + return HashAlgorithm.KECCAK_256 + default: + return nil + } + } + + access(all) fun getSigAlgo(fromRawValue: UInt8): SignatureAlgorithm? { + switch fromRawValue { + case SignatureAlgorithm.ECDSA_P256.rawValue: + return SignatureAlgorithm.ECDSA_P256 + case SignatureAlgorithm.ECDSA_secp256k1.rawValue: + return SignatureAlgorithm.ECDSA_secp256k1 + default: + return nil + } + } +} \ No newline at end of file diff --git a/env.example b/env.example index 8e67d40..2d138c6 100644 --- a/env.example +++ b/env.example @@ -11,6 +11,9 @@ NEXT_PUBLIC_IS_LOCAL=true NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN=0xee82856bf20e2aa6 NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 +NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index ed593c1..aea2532 100644 --- a/flow.json +++ b/flow.json @@ -6,6 +6,12 @@ "emulator": "ee82856bf20e2aa6" } }, + "CryptoUtils": { + "source": "./cadence/contracts/CryptoUtils.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, "EVM": { "source": "./cadence/contracts/EVM.cdc", "aliases": { @@ -105,7 +111,7 @@ "Burner", "FUSD", "ViewResolver", - { + "CryptoUtils", { "name": "FlowEVMPassthrough", "args": [ { From 9a9aed5f7824f81f97b478329a48f46310ad64a2 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:57:27 -0600 Subject: [PATCH 12/88] add env vars to publicConfig.ts --- lib/publicConfig.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 66cce18..95feab2 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -1,4 +1,4 @@ -import {Networks, TokenTypes} from "./constants" +import { Networks, TokenTypes } from "./constants" const network = process.env.NEXT_PUBLIC_NETWORK as Networks | undefined if (!network) throw "Missing NEXT_PUBLIC_NETWORK" @@ -27,6 +27,15 @@ if (!contractFlowToken) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_TOKEN" const contractFUSD = process.env.NEXT_PUBLIC_CONTRACT_FUSD if (!contractFUSD) throw "Missing NEXT_PUBLIC_CONTRACT_FUSD" +const contractEVM = process.env.NEXT_PUBLIC_CONTRACT_EVM +if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" + +const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS +if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" + +const contractFlowEVMPassthrough = process.env.NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH +if (!contractFlowEVMPassthrough) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH" + const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" From 7ccb0cb373e8e603a733462eab87a90bf36cf5b6 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:29:27 -0600 Subject: [PATCH 13/88] update lib cadence to 1.0 --- lib/flow/account.ts | 32 ++++++++++++++++++++++---------- lib/flow/fund.ts | 20 ++++++++------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 9a43b2e..3ba6a97 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -7,25 +7,36 @@ import {sendTransaction} from "./send" const accountCreatedEventType = "flow.AccountCreated" const txCreateAccount = ` +import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} -transaction(publicKey: String, flowTokenAmount: UFix64) { +transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureAlgorithm, hashAlgorithm: HashAlgorithm) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue) &Account) { let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) + let signatureAlgorithm: UInt8 = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm: UInt8 = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid HashAlgorithm") - self.tokenAdmin = signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + account.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) + + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = account - .getCapability(/public/flowTokenReceiver)! - .borrow<&{FungibleToken.Receiver}>() + self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)! ?? panic("Unable to borrow receiver reference") } @@ -46,12 +57,13 @@ export async function createAccount( hashAlgo: number, authorization: fcl.Authorization ) { - const encodedPublicKey = encodeKey(publicKey, sigAlgo, hashAlgo, 1000) const result = await sendTransaction({ transaction: txCreateAccount, args: [ - fcl.arg(encodedPublicKey, t.String), + fcl.arg(publicKey, t.String), fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), ], authorizations: [authorization], payer: authorization, diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 78b59a8..d4cdc55 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -13,14 +13,12 @@ transaction(address: Address, amount: UFix64) { let tokenReceiver: &{FungibleToken.Receiver} prepare(signer: AuthAccount) { - self.tokenAdmin = signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = getAccount(address) - .getCapability(/public/flowTokenReceiver)! - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Unable to borrow receiver reference") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/flowTokenReceiver + ) ?? panic("Could not borrow receiver reference to the recipient's Vault") } execute { @@ -43,14 +41,12 @@ transaction(address: Address, amount: UFix64) { let tokenReceiver: &{FungibleToken.Receiver} prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount - .borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) + self.tokenMinter = minterAccount.storage.borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) ?? panic("No minter available") - self.tokenReceiver = getAccount(address) - .getCapability(/public/fusdReceiver)! - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Unable to borrow receiver reference") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/fusdReceiver + ) ?? panic("Unable to borrow receiver reference") } execute { From 8cfa6b00dfd0d0e0822da987496437289106fe59 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:34:03 -0600 Subject: [PATCH 14/88] fix fund.ts Cadence --- lib/flow/fund.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index d4cdc55..b23d59b 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -37,12 +37,13 @@ import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenMinter: &FUSD.MinterProxy + let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy let tokenReceiver: &{FungibleToken.Receiver} prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount.storage.borrow<&FUSD.MinterProxy>(from: FUSD.MinterProxyStoragePath) - ?? panic("No minter available") + self.tokenMinter = minterAccount.storage.borrow( + from: FUSD.MinterProxyStoragePath + ) ?? panic("No minter available") self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( /public/fusdReceiver From 7e008868176752366847ae3348a7b2147df9284c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:11:52 -0600 Subject: [PATCH 15/88] update flow.json --- flow.json | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flow.json b/flow.json index aea2532..7f66e0f 100644 --- a/flow.json +++ b/flow.json @@ -102,25 +102,25 @@ "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } -} \ No newline at end of file + } \ No newline at end of file From fd30f592d1c84b942ee7a6bd103459271cd3d302 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:12:17 -0600 Subject: [PATCH 16/88] add contract alias info to fclConfig.ts --- lib/fclConfig.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index b938d96..0d0db0d 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -11,3 +11,6 @@ config() .put("0xFUNGIBLETOKENADDRESS", publicConfig.contractFungibleToken) .put("0xFLOWTOKENADDRESS", publicConfig.contractFlowToken) .put("0xFUSDADDRESS", publicConfig.contractFUSD) + .put("0xEVMADDRESS", publicConfig.contractEVM) + .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) + .put("0xFLOWEVMPASSTHROUGHADDRESS", publicConfig.contractFlowEVMPassthrough) From 1464d87e013ac7c60ef0840b0a27224206e2678c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:15:47 -0600 Subject: [PATCH 17/88] fix flow.json --- flow.json | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/flow.json b/flow.json index 7f66e0f..a40bc0d 100644 --- a/flow.json +++ b/flow.json @@ -102,25 +102,26 @@ "testnet-account": { "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", "keys": "${SIGNER_PRIVATE_KEY}" - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } } - } \ No newline at end of file + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils", + { + "name": "FlowEVMPassthrough", + "args": [ + { + "type": "String", + "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" + } + ] + } + ] + } + } +} \ No newline at end of file From 1210192e701cf819b40533c74e7a36e5b5ee223c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:19:31 -0600 Subject: [PATCH 18/88] rename coa creation transaction --- .../evm/{create_account.cdc => create_coa_and_fund.cdc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cadence/transactions/evm/{create_account.cdc => create_coa_and_fund.cdc} (100%) diff --git a/cadence/transactions/evm/create_account.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc similarity index 100% rename from cadence/transactions/evm/create_account.cdc rename to cadence/transactions/evm/create_coa_and_fund.cdc From 28296e1ae33a25b02aa06c7f21ca1e8194afaba4 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:01:42 -0600 Subject: [PATCH 19/88] fix account.ts Cadence --- lib/flow/account.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 3ba6a97..8557458 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,8 +1,8 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" -import {encodeKey} from "@onflow/util-encode-key" +import { encodeKey } from "@onflow/util-encode-key" import publicConfig from "../publicConfig" -import {sendTransaction} from "./send" +import { sendTransaction } from "./send" const accountCreatedEventType = "flow.AccountCreated" @@ -11,16 +11,16 @@ import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} -transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureAlgorithm, hashAlgorithm: HashAlgorithm) { +transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, hashAlgorithm: UInt8) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} prepare(signer: auth(BorrowValue) &Account) { - let account = AuthAccount(payer: signer) + let account = Account(payer: signer) - let signatureAlgorithm: UInt8 = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm: UInt8 = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) ?? panic("Invalid HashAlgorithm") let key = PublicKey( @@ -36,7 +36,7 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: SignatureA self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)! + self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") } From c4779d266009f72891afbdec780793f313915ab0 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:02:02 -0600 Subject: [PATCH 20/88] update evm transactions --- .../transactions/evm/create_coa_and_fund.cdc | 2 +- .../create_flow_account_with_coa_and_fund.cdc | 64 +++++++++++++++++++ .../transactions/evm/passthrough_evm_mint.cdc | 34 ++++++++++ .../evm/passthrough_evm_transfer.cdc | 6 -- 4 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc create mode 100644 cadence/transactions/evm/passthrough_evm_mint.cdc diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index a153a81..edcd415 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -23,6 +23,6 @@ transaction(amount: UFix64) { account.address().deposit(from: <-self.sentVault) log(account.balance()) - self.auth.storage.save<@EVM.BridgedAccount>(<-account, to: StoragePath(identifier: "evm")!) + self.auth.storage.save(<-account, to: StoragePath(identifier: "evm")!) } } diff --git a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc new file mode 100644 index 0000000..5df9e16 --- /dev/null +++ b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc @@ -0,0 +1,64 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "CryptoUtils" + +/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then deposited into +/// the Flow account at the ratio provided and the remaining amount is deposited into the newly created COA. +/// +transaction( + publicKey: String, + flowTokenAmount: UFix64, + sigAlgorithm: UInt8, + hashAlgorithm: UInt8, + fundingRatio: UFix64 +) { + let tokenAdmin: &FlowToken.Administrator + let newAccount: auth(Keys, Storage) &Account + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: auth(Storage) &Account) { + self.newAccount = Account(payer: signer) + + let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + ?? panic("Invalid HashAlgorithm") + + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + self.newAccount.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) + + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = self.newAccount.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + } + + pre { + fundingRatio <= 1.0: "Funding mix must be less than or equal to 1.0" + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: flowTokenAmount) + let flowVault <- minter.mintTokens(amount: flowTokenAmount * fundingRatio) + let evmVault <- minter.mintTokens(amount: flowTokenAmount * (1.0 - fundingRatio)) + destroy minter + + self.tokenReceiver.deposit(from: <-flowVault) + + let coa <- EVM.createBridgedAccount() + coa.address().deposit(from: <-evmVault) + + self.newAccount.storage.save(<-coa, to: StoragePath(identifier: "evm")!) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_mint.cdc b/cadence/transactions/evm/passthrough_evm_mint.cdc new file mode 100644 index 0000000..87fe279 --- /dev/null +++ b/cadence/transactions/evm/passthrough_evm_mint.cdc @@ -0,0 +1,34 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +import "FlowEVMPassthrough" + +/// Mints Flow and transfers it to the given EVM address via the saved Passthrough resource. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let auth: auth(Storage) &Account + let tokenReceiver: &{FungibleToken.Receiver} + let passthrough: &FlowEVMPassthrough.Passthrough + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) + ?? panic("Could not borrow reference to the owner's Passthrough!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + self.passthrough.evmTransfer(from: <-mintedVault, to: to, gasLimit: gasLimit) + } +} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc index 1f674d2..fa7498e 100644 --- a/cadence/transactions/evm/passthrough_evm_transfer.cdc +++ b/cadence/transactions/evm/passthrough_evm_transfer.cdc @@ -22,12 +22,6 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { } execute { - let bytes = toAsHex.decodeHex() - let to = EVM.EVMAddress( - bytes: [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] - ]) self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) } } From 10b0256b117adf9d757559a5a8210696fb927e6e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:29:42 -0600 Subject: [PATCH 21/88] update dev-deploy-contracts command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d30781..e950720 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "db-migrate-deploy": "prisma migrate deploy --preview-feature", "db-seed": "node prisma/seed.mjs", "deploy": "npm run build && npm run db-migrate-deploy && npm run db-seed", - "dev-deploy-contracts": "flow project deploy --network=emulator --update", + "dev-deploy-contracts": "flow evm create-account 1000.0 && flow project deploy --network=emulator --update", "initial-transactions": "ts-node lib/initialTransactions.ts --loader ts-node/esm", "lint": "eslint .", "lint-fix": "eslint . --fix", From 24c4f826324a912c81ad9512eacc6e2edf1e340f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:25:30 -0600 Subject: [PATCH 22/88] remove passthrough Cadence & solidity contracts --- cadence/contracts/FlowEVMPassthrough.cdc | 153 ----------- .../evm/get_passthrough_evm_address.cdc | 8 - .../transactions/evm/passthrough_evm_mint.cdc | 34 --- .../evm/passthrough_evm_transfer.cdc | 27 -- .../transactions/evm/setup_passthrough.cdc | 23 -- env.example | 1 - flow.json | 239 +++++++++--------- lib/fclConfig.ts | 1 - lib/publicConfig.ts | 3 - solidity/src/FlowPassthrough.sol | 14 - 10 files changed, 114 insertions(+), 389 deletions(-) delete mode 100644 cadence/contracts/FlowEVMPassthrough.cdc delete mode 100644 cadence/scripts/evm/get_passthrough_evm_address.cdc delete mode 100644 cadence/transactions/evm/passthrough_evm_mint.cdc delete mode 100644 cadence/transactions/evm/passthrough_evm_transfer.cdc delete mode 100644 cadence/transactions/evm/setup_passthrough.cdc delete mode 100644 solidity/src/FlowPassthrough.sol diff --git a/cadence/contracts/FlowEVMPassthrough.cdc b/cadence/contracts/FlowEVMPassthrough.cdc deleted file mode 100644 index 6df409c..0000000 --- a/cadence/contracts/FlowEVMPassthrough.cdc +++ /dev/null @@ -1,153 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -/// This contract defines a resource implementing FungibleToken Receiver and Provider interfaces that enables deposits -/// to an encapsulated COA and transfers to EVM addresses. Recipients in Flow EVM will receive the transfer from the -/// FlowPassthrough.sol contract address, but an event is emitted in both the Cadence & EVM environments denoting the -/// source and target addresses involved in the passthrough transfer. -/// -access(all) contract FlowEVMPassthrough { - - /// The address of the passthrough Solidity contract - access(all) let passthroughEVMAddress: EVM.EVMAddress - - /// Default storage path for the Passthrough resource - access(all) let StoragePath: StoragePath - /// Default public path for the Passthrough resource - access(all) let PublicPath: PublicPath - - /// Event emitted when a transfer occurs, denoting the transferor, recipient and amount transfered via passthrough - access(all) event Transfered(from: EVM.EVMAddress, to: EVM.EVMAddress, amount: UFix64) - - /* --- Public Methods --- */ - /// - access(all) fun createPassthrough(with coa: Capability<&EVM.BridgedAccount>): @Passthrough { - return <-create Passthrough(coa) - } - - /* --- Passthrough --- */ - // - /// Encapsulates a COA and provides a FungibleToken Receiver and Provider interface through which to deposit & - /// withdraw to/from the COA. Also enables transfers to EVM addresses using a call to a passthrough Solidity - /// contract. - /// - access(all) resource Passthrough : FungibleToken.Receiver, FungibleToken.Provider { - /// Capability to owner's COA - access(self) let coaCapability: Capability<&EVM.BridgedAccount> - - init(_ coaCapability: Capability<&EVM.BridgedAccount>) { - pre { - coaCapability.check(): "Invalid COA Capability provided" - } - self.coaCapability = coaCapability - } - - /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts - access(all) view fun getSupportedVaultTypes(): {Type: Bool} { - return { Type<@FlowToken.Vault>(): true } - } - - /// Returns whether or not the given type is accepted by the Receiver - /// A vault that can accept any type should just return true by default - access(all) view fun isSupportedVaultType(type: Type): Bool { - return self.getSupportedVaultTypes()[type] ?? false - } - - /// Deposits Flow to encapsulated COA according to Receiver interface - access(all) fun deposit(from: @{FungibleToken.Vault}) { - pre { - self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" - } - let flowVault <- from as! @FlowToken.Vault - self.borrowCOA().deposit(from: <-flowVault) - } - - access(FungibleToken.Withdrawable) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { - return <-self.borrowCOA().withdraw( - balance: EVM.Balance(flow: amount) - ) - } - - /// Deposits Flow to defined EVM Address using a call to passthrough Solidity contract - access(all) fun evmTransfer(from: @{FungibleToken.Vault}, to: EVM.EVMAddress, gasLimit: UInt64) { - pre { - self.isSupportedVaultType(type: from.getType()): "Passthrough only supports FlowToken.Vaults" - } - let amount = from.getBalance() - // TODO: Replace with UInt256(EVM.Balance(flow: from.balance).toAttoFlow()) - let amountInAttoFlow = FlowEVMPassthrough.ufix64ToUInt256(value: from.getBalance(), decimals: 8) - // TODO: Replace with EVM.encodeABIWithSignature("transferFlow(address)", [to]) - let calldata = FlowEVMPassthrough.encodeABIWithSignature( - "transferFlow(address)", - [to] - ) - let coa = self.borrowCOA() - let flowVault <- from as! @FlowToken.Vault - coa.deposit(from: <-flowVault) - coa.call( - to: FlowEVMPassthrough.passthroughEVMAddress, - data: calldata, - gasLimit: gasLimit, - value: EVM.Balance(flow: amount) - ) - - emit Transfered(from: self.borrowCOA().address(), to: to, amount: amount) - } - - access(self) fun borrowCOA(): &EVM.BridgedAccount { - return self.coaCapability.borrow() ?? panic("COA Capability has been revoked and is no longer valid") - } - } - - /* --- Internal Helpers --- */ - // TODO: Remove helpers once EVM.Balance.toAttoFlow() is implemented - - /// Raises the base to the power of the exponent - access(self) view fun pow(base: UInt256, exponent: UInt8): UInt256 { - if exponent == 0 { - return 1 - } - var r = base - var exp: UInt8 = 1 - while exp < exponent { - r = r * base - exp = exp + 1 - } - return r - } - - /// Converts a UFix64 to a UInt256 - access(self) view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 { - let integerPart: UInt64 = UInt64(value) - var r = UInt256(integerPart) - var multiplier: UInt256 = self.pow(base:10, exponent: decimals) - return r * multiplier - } - - access(self) fun encodeABIWithSignature( - _ signature: String, - _ values: [AnyStruct] - ): [UInt8] { - let methodID = HashAlgorithm.KECCAK_256.hash( - signature.utf8 - ).slice(from: 0, upTo: 4) - let arguments = EVM.encodeABI(values) - - return methodID.concat(arguments) - } - - init(passthroughBytecode: String) { - let coa = self.account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) - ?? panic("COA not found") - let passthroughEVMAddress = coa.deploy( - code: passthroughBytecode.decodeHex(), - gasLimit: 12000000, - value: EVM.Balance(flow: 0.0) - ) - self.passthroughEVMAddress = passthroughEVMAddress - self.StoragePath = /storage/flowEVMPassthrough - self.PublicPath = /public/flowEVMPassthroughPublic - } -} diff --git a/cadence/scripts/evm/get_passthrough_evm_address.cdc b/cadence/scripts/evm/get_passthrough_evm_address.cdc deleted file mode 100644 index ad103d0..0000000 --- a/cadence/scripts/evm/get_passthrough_evm_address.cdc +++ /dev/null @@ -1,8 +0,0 @@ -import "EVM" - -import "FlowEVMPassthrough" - -/// Returns the EVM address of the FlowEVM passthrough contract. -access(all) fun main(): EVM.EVMAddress { - return FlowEVMPassthrough.passthroughEVMAddress -} diff --git a/cadence/transactions/evm/passthrough_evm_mint.cdc b/cadence/transactions/evm/passthrough_evm_mint.cdc deleted file mode 100644 index 87fe279..0000000 --- a/cadence/transactions/evm/passthrough_evm_mint.cdc +++ /dev/null @@ -1,34 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -import "FlowEVMPassthrough" - -/// Mints Flow and transfers it to the given EVM address via the saved Passthrough resource. -/// -transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { - - let tokenAdmin: &FlowToken.Administrator - let auth: auth(Storage) &Account - let tokenReceiver: &{FungibleToken.Receiver} - let passthrough: &FlowEVMPassthrough.Passthrough - - prepare(signer: auth(BorrowValue) &Account) { - self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") - - self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") - self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) - ?? panic("Could not borrow reference to the owner's Passthrough!") - } - - execute { - let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) - let mintedVault <- minter.mintTokens(amount: amount) - destroy minter - - self.passthrough.evmTransfer(from: <-mintedVault, to: to, gasLimit: gasLimit) - } -} diff --git a/cadence/transactions/evm/passthrough_evm_transfer.cdc b/cadence/transactions/evm/passthrough_evm_transfer.cdc deleted file mode 100644 index fa7498e..0000000 --- a/cadence/transactions/evm/passthrough_evm_transfer.cdc +++ /dev/null @@ -1,27 +0,0 @@ -import "FungibleToken" -import "FlowToken" - -import "EVM" - -import "FlowEVMPassthrough" - -/// Withdraws tokens from the signer's FlowToken Vault and transfers them to the given EVM address via the saved -/// Passthrough resource. -/// -transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { - - let sentVault: @{FungibleToken.Vault} - let passthrough: &FlowEVMPassthrough.Passthrough - - prepare(signer: auth(BorrowValue) &Account) { - let sourceVault = signer.storage.borrow(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") - self.sentVault <- sourceVault.withdraw(amount: amount) - self.passthrough = signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) - ?? panic("Could not borrow reference to the owner's Passthrough!") - } - - execute { - self.passthrough.evmTransfer(from: <-self.sentVault, to: to, gasLimit: gasLimit) - } -} diff --git a/cadence/transactions/evm/setup_passthrough.cdc b/cadence/transactions/evm/setup_passthrough.cdc deleted file mode 100644 index 5dd39b8..0000000 --- a/cadence/transactions/evm/setup_passthrough.cdc +++ /dev/null @@ -1,23 +0,0 @@ -import "EVM" - -import "FlowEVMPassthrough" - -/// Configures a Passthrough resource in the signer's storage -/// -transaction { - - prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { - if signer.storage.borrow<&FlowEVMPassthrough.Passthrough>(from: FlowEVMPassthrough.StoragePath) != nil { - return - } - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) - } - let coaCapability = signer.capabilities.storage.issue<&EVM.BridgedAccount>(/storage/evm) - signer.storage.save(<-FlowEVMPassthrough.createPassthrough(with: coaCapability), to: FlowEVMPassthrough.StoragePath) - let passthroughCapability = signer.capabilities.storage.issue<&FlowEVMPassthrough.Passthrough>( - FlowEVMPassthrough.StoragePath - ) - signer.capabilities.publish(passthroughCapability, at: FlowEVMPassthrough.PublicPath) - } -} diff --git a/env.example b/env.example index 2d138c6..f491c60 100644 --- a/env.example +++ b/env.example @@ -13,7 +13,6 @@ NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 -NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index a40bc0d..d0b1e21 100644 --- a/flow.json +++ b/flow.json @@ -1,127 +1,116 @@ { - "contracts": { - "Burner": { - "source": "./cadence/contracts/Burner.cdc", - "aliases": { - "emulator": "ee82856bf20e2aa6" - } - }, - "CryptoUtils": { - "source": "./cadence/contracts/CryptoUtils.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "EVM": { - "source": "./cadence/contracts/EVM.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "crescendo": "e223d8a629e49c68", - "emulator": "0ae53cb6e3f42a79", - "testnet": "e223d8a629e49c68" - } - }, - "FlowEVMPassthrough": { - "source": "./cadence/contracts/FlowEVMPassthrough.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, - "FlowToken": { - "source": "./cadence/contracts/FlowToken.cdc", - "aliases": { - "crescendo": "7e60df042a9c0868", - "emulator": "0ae53cb6e3f42a79", - "testnet": "7e60df042a9c0868" - } - }, - "FungibleToken": { - "source": "./cadence/contracts/FungibleToken.cdc", - "aliases": { - "crescendo": "9a0766d93b6608b7", - "emulator": "ee82856bf20e2aa6", - "testnet": "9a0766d93b6608b7" - } - }, - "FungibleTokenMetadataViews": { - "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", - "aliases": { - "crescendo": "f233dcee88fe0abe", - "emulator": "f8d6e0586b0a20c7", - "testnet": "f233dcee88fe0abe" - } - }, - "MetadataViews": { - "source": "./cadence/contracts/MetadataViews.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "NonFungibleToken": { - "source": "./cadence/contracts/NonFungibleToken.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - }, - "ViewResolver": { - "source": "./cadence/contracts/ViewResolver.cdc", - "aliases": { - "crescendo": "0x631e88ae7f1d7c20", - "emulator": "f8d6e0586b0a20c7", - "testnet": "631e88ae7f1d7c20" - } - } - }, - "networks": { - "crescendo": "access.crescendo.nodes.onflow.org:9000", - "emulator": "127.0.0.1:3569", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" - }, - "emulator-flow": { - "address": "0ae53cb6e3f42a79", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "emulator-ft": { - "address": "ee82856bf20e2aa6", - "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" - }, - "testnet-account": { - "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", - "keys": "${SIGNER_PRIVATE_KEY}" - } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "FungibleTokenMetadataViews", - "Burner", - "FUSD", - "ViewResolver", - "CryptoUtils", - { - "name": "FlowEVMPassthrough", - "args": [ - { - "type": "String", - "value": "608060405234801561000f575f80fd5b506103678061001d5f395ff3fe60806040526004361061001d575f3560e01c80637ebbc23914610021575b5f80fd5b61003b60048036038101906100369190610185565b61003d565b005b5f808273ffffffffffffffffffffffffffffffffffffffff1634604051610063906101dd565b5f6040518083038185875af1925050503d805f811461009d576040519150601f19603f3d011682016040523d82523d5f602084013e6100a2565b606091505b5091509150816100e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100de9061024b565b60405180910390fd5b7fe4fc403eef87e48e40029827def82ae478cfa60ca7726e7f52e09d5384076e6633843460405161011a939291906102fc565b60405180910390a1505050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101548261012b565b9050919050565b6101648161014a565b811461016e575f80fd5b50565b5f8135905061017f8161015b565b92915050565b5f6020828403121561019a57610199610127565b5b5f6101a784828501610171565b91505092915050565b5f81905092915050565b50565b5f6101c85f836101b0565b91506101d3826101ba565b5f82019050919050565b5f6101e7826101bd565b9150819050919050565b5f82825260208201905092915050565b7f4661696c656420746f2073656e6420466c6f77000000000000000000000000005f82015250565b5f6102356013836101f1565b915061024082610201565b602082019050919050565b5f6020820190508181035f83015261026281610229565b9050919050565b5f6102738261012b565b9050919050565b61028381610269565b82525050565b5f819050919050565b5f6102ac6102a76102a28461012b565b610289565b61012b565b9050919050565b5f6102bd82610292565b9050919050565b5f6102ce826102b3565b9050919050565b6102de816102c4565b82525050565b5f819050919050565b6102f6816102e4565b82525050565b5f60608201905061030f5f83018661027a565b61031c60208301856102d5565b61032960408301846102ed565b94935050505056fea264697066735822122098caf1372532fe479c7df9f46e6fde68a0389c81b4a013a70088c2d701b1409864736f6c63430008180033" - } - ] - } - ] - } - } + "contracts": { + "Burner": { + "source": "./cadence/contracts/Burner.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6" + } + }, + "CryptoUtils": { + "source": "./cadence/contracts/CryptoUtils.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "EVM": { + "source": "./cadence/contracts/EVM.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7" + } + }, + "FUSD": { + "source": "./cadence/contracts/FUSD.cdc", + "aliases": { + "crescendo": "e223d8a629e49c68", + "emulator": "0ae53cb6e3f42a79", + "testnet": "e223d8a629e49c68" + } + }, + "FlowToken": { + "source": "./cadence/contracts/FlowToken.cdc", + "aliases": { + "crescendo": "7e60df042a9c0868", + "emulator": "0ae53cb6e3f42a79", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "./cadence/contracts/FungibleToken.cdc", + "aliases": { + "crescendo": "9a0766d93b6608b7", + "emulator": "ee82856bf20e2aa6", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "./cadence/contracts/FungibleTokenMetadataViews.cdc", + "aliases": { + "crescendo": "f233dcee88fe0abe", + "emulator": "f8d6e0586b0a20c7", + "testnet": "f233dcee88fe0abe" + } + }, + "MetadataViews": { + "source": "./cadence/contracts/MetadataViews.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "./cadence/contracts/NonFungibleToken.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "./cadence/contracts/ViewResolver.cdc", + "aliases": { + "crescendo": "631e88ae7f1d7c20", + "emulator": "f8d6e0586b0a20c7", + "testnet": "631e88ae7f1d7c20" + } + } + }, + "networks": { + "crescendo": "access.crescendo.nodes.onflow.org:9000", + "emulator": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" + }, + "emulator-flow": { + "address": "0ae53cb6e3f42a79", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "emulator-ft": { + "address": "ee82856bf20e2aa6", + "key": "686779d775e5fcbf8d2f4a85cb4c53525d02b7ef53230d180fc16f35d9b7d025" + }, + "test": { + "address": "179b6b1cb6755e31", + "key": "6d8452af4ffac3d09276ce58358b0fb7f959b48437bf368c1d4b9c02588cabda" + }, + "testnet-account": { + "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", + "keys": "${SIGNER_PRIVATE_KEY}" + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "FungibleTokenMetadataViews", + "Burner", + "FUSD", + "ViewResolver", + "CryptoUtils" + ] + } + } } \ No newline at end of file diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index 0d0db0d..ddccd7f 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -13,4 +13,3 @@ config() .put("0xFUSDADDRESS", publicConfig.contractFUSD) .put("0xEVMADDRESS", publicConfig.contractEVM) .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) - .put("0xFLOWEVMPASSTHROUGHADDRESS", publicConfig.contractFlowEVMPassthrough) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 95feab2..c3dd4b8 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -33,9 +33,6 @@ if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" -const contractFlowEVMPassthrough = process.env.NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH -if (!contractFlowEVMPassthrough) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_EVM_PASSTHROUGH" - const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" diff --git a/solidity/src/FlowPassthrough.sol b/solidity/src/FlowPassthrough.sol deleted file mode 100644 index ef6799c..0000000 --- a/solidity/src/FlowPassthrough.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -contract FlowPassthrough { - - event Tranfered(address from, address to, uint256 amount); - - function transferFlow(address payable _to) public payable { - (bool sent, bytes memory data) = _to.call{value: msg.value}(""); - require(sent, "Failed to send Flow"); - emit Tranfered(msg.sender, _to, msg.value); - } -} \ No newline at end of file From 6450b226530b88c2441e09c4a9f108112a093df1 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:26:07 -0600 Subject: [PATCH 23/88] add EVM funding & COA transactions --- .../transactions/evm/create_coa_and_fund.cdc | 15 ++++--- .../evm/mint_and_fund_evm_address.cdc | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 cadence/transactions/evm/mint_and_fund_evm_address.cdc diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index edcd415..7976805 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -7,7 +7,7 @@ import "EVM" /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault - let auth: auth(Storage) &Account + let coa: &EVM.BridgedAccount prepare(signer: auth(Storage) &Account) { let vaultRef = signer.storage.borrow( @@ -15,14 +15,15 @@ transaction(amount: UFix64) { ) ?? panic("Could not borrow reference to the owner's Vault!") self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault - self.auth = signer + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) != nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) + ?? panic("Could not borrow reference to the signer's COA!") + } execute { - let account <- EVM.createBridgedAccount() - account.address().deposit(from: <-self.sentVault) - - log(account.balance()) - self.auth.storage.save(<-account, to: StoragePath(identifier: "evm")!) + self.coa.deposit(from: <-self.sentVault) } } diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc new file mode 100644 index 0000000..e471f95 --- /dev/null +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -0,0 +1,43 @@ +import "FungibleToken" +import "FlowToken" + +import "EVM" + +/// Mints Flow and transfers it to the given EVM address via the signer's CadenceOwnedAccount. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} + let coa: &EVM.BridgedAccount + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + ?? panic("Unable to borrow receiver reference") + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("Could not borrow reference to the signer's COA!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + // TODO: REMOVE + let bytes = toStr.decodeHex() + let to = EVM.EVMAddress(bytes: [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ]) + self.coa.deposit(from: <-mintedVault) + self.coa.call( + to: to, + data: [], + gasLimit: gasLimit, + value: EVM.Balance(flow: amount), + ) + } +} From 352fec45d13690e2ddfd969bd483b07565302e80 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:57:55 -0600 Subject: [PATCH 24/88] remove CryptoUtils --- cadence/contracts/CryptoUtils.cdc | 34 ------------------- .../transactions/evm/create_coa_and_fund.cdc | 8 ++--- .../create_flow_account_with_coa_and_fund.cdc | 13 +++---- .../evm/mint_and_fund_evm_address.cdc | 11 +++--- env.example | 1 - flow.json | 9 +---- lib/fclConfig.ts | 1 - lib/flow/account.ts | 5 ++- lib/publicConfig.ts | 3 -- 9 files changed, 18 insertions(+), 67 deletions(-) delete mode 100644 cadence/contracts/CryptoUtils.cdc diff --git a/cadence/contracts/CryptoUtils.cdc b/cadence/contracts/CryptoUtils.cdc deleted file mode 100644 index c464d80..0000000 --- a/cadence/contracts/CryptoUtils.cdc +++ /dev/null @@ -1,34 +0,0 @@ -/// Util methods to reconstruct HashAlgorithm and SignatureAlgorithm from their raw enum values -/// -access(all) contract CryptoUtils { - - access(all) fun getHashAlgo(fromRawValue: UInt8): HashAlgorithm? { - switch fromRawValue { - case HashAlgorithm.SHA2_256.rawValue: - return HashAlgorithm.SHA2_256 - case HashAlgorithm.SHA2_384.rawValue: - return HashAlgorithm.SHA2_384 - case HashAlgorithm.SHA3_256.rawValue: - return HashAlgorithm.SHA3_256 - case HashAlgorithm.SHA3_384.rawValue: - return HashAlgorithm.SHA3_384 - case HashAlgorithm.KMAC128_BLS_BLS12_381.rawValue: - return HashAlgorithm.KMAC128_BLS_BLS12_381 - case HashAlgorithm.KECCAK_256.rawValue: - return HashAlgorithm.KECCAK_256 - default: - return nil - } - } - - access(all) fun getSigAlgo(fromRawValue: UInt8): SignatureAlgorithm? { - switch fromRawValue { - case SignatureAlgorithm.ECDSA_P256.rawValue: - return SignatureAlgorithm.ECDSA_P256 - case SignatureAlgorithm.ECDSA_secp256k1.rawValue: - return SignatureAlgorithm.ECDSA_secp256k1 - default: - return nil - } - } -} \ No newline at end of file diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index 7976805..316073d 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -3,7 +3,8 @@ import "FlowToken" import "EVM" -/// Creates a COA and saves it in the signer's Flow account & passing the given value of Flow into FlowEVM +/// Creates a COA and saves it in the signer's Flow account (if one doesn't already exist at the expected path) and +/// transfers the given value of Flow into FlowEVM, funding with the signer's Flow Vault. /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault @@ -15,12 +16,11 @@ transaction(amount: UFix64) { ) ?? panic("Could not borrow reference to the owner's Vault!") self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) != nil { + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/EVM) + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") - } execute { diff --git a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc index 5df9e16..8de4f8b 100644 --- a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc @@ -3,10 +3,9 @@ import "FlowToken" import "EVM" -import "CryptoUtils" - -/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then deposited into -/// the Flow account at the ratio provided and the remaining amount is deposited into the newly created COA. +/// Creates a Flow account with a COA and saves it in the created account. The provided amount is then minted, +/// deposited into the Flow account at the specified ratio and the remaining amount is deposited into the newly +/// created COA. /// transaction( publicKey: String, @@ -22,10 +21,12 @@ transaction( prepare(signer: auth(Storage) &Account) { self.newAccount = Account(payer: signer) - let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + log(signatureAlgorithm) + let hashAlgorithm = HashAlgorithm(hashAlgorithm) ?? panic("Invalid HashAlgorithm") + log(hashAlgorithm) let key = PublicKey( publicKey: publicKey.decodeHex(), diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index e471f95..696bece 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -11,12 +11,15 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenReceiver: &{FungibleToken.Receiver} let coa: &EVM.BridgedAccount - prepare(signer: auth(BorrowValue) &Account) { + prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } @@ -26,12 +29,6 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let mintedVault <- minter.mintTokens(amount: amount) destroy minter - // TODO: REMOVE - let bytes = toStr.decodeHex() - let to = EVM.EVMAddress(bytes: [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] - ]) self.coa.deposit(from: <-mintedVault) self.coa.call( to: to, diff --git a/env.example b/env.example index f491c60..fd34221 100644 --- a/env.example +++ b/env.example @@ -12,7 +12,6 @@ NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN=0xee82856bf20e2aa6 NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 -NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= SIGNER_HASH_ALGO=SHA2_256 diff --git a/flow.json b/flow.json index d0b1e21..a7c0259 100644 --- a/flow.json +++ b/flow.json @@ -6,12 +6,6 @@ "emulator": "ee82856bf20e2aa6" } }, - "CryptoUtils": { - "source": "./cadence/contracts/CryptoUtils.cdc", - "aliases": { - "emulator": "f8d6e0586b0a20c7" - } - }, "EVM": { "source": "./cadence/contracts/EVM.cdc", "aliases": { @@ -108,8 +102,7 @@ "FungibleTokenMetadataViews", "Burner", "FUSD", - "ViewResolver", - "CryptoUtils" + "ViewResolver" ] } } diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index ddccd7f..5a63ca2 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -12,4 +12,3 @@ config() .put("0xFLOWTOKENADDRESS", publicConfig.contractFlowToken) .put("0xFUSDADDRESS", publicConfig.contractFUSD) .put("0xEVMADDRESS", publicConfig.contractEVM) - .put("0xCRYPTOUTILSADDRESS", publicConfig.contractCryptoUtils) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 8557458..9a819ff 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -7,7 +7,6 @@ import { sendTransaction } from "./send" const accountCreatedEventType = "flow.AccountCreated" const txCreateAccount = ` -import CryptoUtils from ${publicConfig.contractFlowToken} import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} @@ -18,9 +17,9 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has prepare(signer: auth(BorrowValue) &Account) { let account = Account(payer: signer) - let signatureAlgorithm = CryptoUtils.getSigAlgo(fromRawValue: sigAlgorithm) + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = CryptoUtils.getHashAlgo(fromRawValue: sigAlgorithm) + let hashAlgorithm = HashAlgorithm(hashAlgorithm) ?? panic("Invalid HashAlgorithm") let key = PublicKey( diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index c3dd4b8..a07339d 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -30,9 +30,6 @@ if (!contractFUSD) throw "Missing NEXT_PUBLIC_CONTRACT_FUSD" const contractEVM = process.env.NEXT_PUBLIC_CONTRACT_EVM if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" -const contractCryptoUtils = process.env.NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS -if (!contractCryptoUtils) throw "Missing NEXT_PUBLIC_CONTRACT_CRYPTO_UTILS" - const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY // TODO: Integrate FCL wallets // if (!walletDiscovery) throw "Missing NEXT_PUBLIC_WALLET_DISCOVERY" From a037f9edb8f24bfdb82cf74a54c9dbb4ce6e7ba9 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:23:32 -0600 Subject: [PATCH 25/88] update npm run dev-deploy-contracts command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e950720..7d30781 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "db-migrate-deploy": "prisma migrate deploy --preview-feature", "db-seed": "node prisma/seed.mjs", "deploy": "npm run build && npm run db-migrate-deploy && npm run db-seed", - "dev-deploy-contracts": "flow evm create-account 1000.0 && flow project deploy --network=emulator --update", + "dev-deploy-contracts": "flow project deploy --network=emulator --update", "initial-transactions": "ts-node lib/initialTransactions.ts --loader ts-node/esm", "lint": "eslint .", "lint-fix": "eslint . --fix", From 399f0d595526d410c513f568724026d31e20a244 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:41:37 -0600 Subject: [PATCH 26/88] update ts formatting --- lib/flow/account.ts | 109 ++++++++++++++++++++++---------------------- lib/flow/fund.ts | 82 ++++++++++++++++----------------- lib/publicConfig.ts | 2 +- 3 files changed, 96 insertions(+), 97 deletions(-) diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 9a819ff..4229bfe 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,6 +1,5 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" -import { encodeKey } from "@onflow/util-encode-key" import publicConfig from "../publicConfig" import { sendTransaction } from "./send" @@ -11,32 +10,32 @@ import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, hashAlgorithm: UInt8) { - let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(signer: auth(BorrowValue) &Account) { - let account = Account(payer: signer) - - let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) - ?? panic("Invalid SignatureAlgorithm") - let hashAlgorithm = HashAlgorithm(hashAlgorithm) - ?? panic("Invalid HashAlgorithm") - - let key = PublicKey( - publicKey: publicKey.decodeHex(), - signatureAlgorithm: signatureAlgorithm - ) - account.keys.add( - publicKey: key, - hashAlgorithm: hashAlgorithm, - weight: 1000.0 - ) + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: auth(BorrowValue) &Account) { + let account = Account(payer: signer) + + let signatureAlgorithm = SignatureAlgorithm(sigAlgorithm) + ?? panic("Invalid SignatureAlgorithm") + let hashAlgorithm = HashAlgorithm(hashAlgorithm) + ?? panic("Invalid HashAlgorithm") + + let key = PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: signatureAlgorithm + ) + account.keys.add( + publicKey: key, + hashAlgorithm: hashAlgorithm, + weight: 1000.0 + ) self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") + ?? panic("Signer is not the token admin") self.tokenReceiver = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") + ?? panic("Unable to borrow receiver reference") } execute { @@ -51,37 +50,37 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has ` export async function createAccount( - publicKey: string, - sigAlgo: number, - hashAlgo: number, - authorization: fcl.Authorization + publicKey: string, + sigAlgo: number, + hashAlgo: number, + authorization: fcl.Authorization ) { - const result = await sendTransaction({ - transaction: txCreateAccount, - args: [ - fcl.arg(publicKey, t.String), - fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), - fcl.arg(sigAlgo.toString(), t.UInt8), - fcl.arg(hashAlgo.toString(), t.UInt8), - ], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) - - const accountCreatedEvent = result.events.find( - (event: fcl.Event) => event.type === accountCreatedEventType - ) - - if (!accountCreatedEvent) { - throw "Transaction did not emit account creation event" - } - - const address = accountCreatedEvent.data.address - const transactionId = accountCreatedEvent.transactionId - - return { - address, - transactionId, - } + const result = await sendTransaction({ + transaction: txCreateAccount, + args: [ + fcl.arg(publicKey, t.String), + fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), + ], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + + const accountCreatedEvent = result.events.find( + (event: fcl.Event) => event.type === accountCreatedEventType + ) + + if (!accountCreatedEvent) { + throw "Transaction did not emit account creation event" + } + + const address = accountCreatedEvent.data.address + const transactionId = accountCreatedEvent.transactionId + + return { + address, + transactionId, + } } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index b23d59b..526d1b7 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -9,16 +9,16 @@ import FlowToken from ${publicConfig.contractFlowToken} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: AuthAccount) { - self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") + prepare(signer: AuthAccount) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") - self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( - /public/flowTokenReceiver - ) ?? panic("Could not borrow receiver reference to the recipient's Vault") + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/flowTokenReceiver + ) ?? panic("Could not borrow receiver reference to the recipient's Vault") } execute { @@ -37,52 +37,52 @@ import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} transaction(address: Address, amount: UFix64) { - let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount.storage.borrow( - from: FUSD.MinterProxyStoragePath - ) ?? panic("No minter available") - - self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( - /public/fusdReceiver - ) ?? panic("Unable to borrow receiver reference") - } - - execute { - let mintedVault <- self.tokenMinter.mintTokens(amount: amount) - self.tokenReceiver.deposit(from: <-mintedVault) - } + let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(minterAccount: AuthAccount) { + self.tokenMinter = minterAccount.storage.borrow( + from: FUSD.MinterProxyStoragePath + ) ?? panic("No minter available") + + self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( + /public/fusdReceiver + ) ?? panic("Unable to borrow receiver reference") + } + + execute { + let mintedVault <- self.tokenMinter.mintTokens(amount: amount) + self.tokenReceiver.deposit(from: <-mintedVault) + } } ` type TokenType = "FLOW" | "FUSD" type Token = { - tx: string - amount: string + tx: string + amount: string } type Tokens = Record export const tokens: Tokens = { - FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, - FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, + FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } export async function fundAccount( - address: string, - token: TokenType, - authorization: fcl.Authorization + address: string, + token: TokenType, + authorization: fcl.Authorization ) { - const {tx, amount} = tokens[token] + const {tx, amount} = tokens[token] - await sendTransaction({ - transaction: tx, - args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) + await sendTransaction({ + transaction: tx, + args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) - return amount + return amount } diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index a07339d..6dbc192 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -1,4 +1,4 @@ -import { Networks, TokenTypes } from "./constants" +import {Networks, TokenTypes} from "./constants" const network = process.env.NEXT_PUBLIC_NETWORK as Networks | undefined if (!network) throw "Missing NEXT_PUBLIC_NETWORK" From d94652a7144d645aa53791da5aabc02e24a164ab Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:58:54 -0600 Subject: [PATCH 27/88] update ts formatting & add ci EVM contract env alias --- .github/workflows/ci.yml | 1 + lib/flow/account.ts | 66 ++++++++++++++++++++-------------------- lib/flow/fund.ts | 32 +++++++++---------- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4600b59..5ef3648 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN: "0xee82856bf20e2aa6" NEXT_PUBLIC_CONTRACT_FLOW_TOKEN: "0x0ae53cb6e3f42a79" NEXT_PUBLIC_CONTRACT_FUSD: "0xf8d6e0586b0a20c7" + NEXT_PUBLIC_CONTRACT_EVM: "0xf8d6e0586b0a20c7" NEXT_PUBLIC_NETWORK: "testnet" strategy: diff --git a/lib/flow/account.ts b/lib/flow/account.ts index 4229bfe..bf20c48 100644 --- a/lib/flow/account.ts +++ b/lib/flow/account.ts @@ -1,7 +1,7 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" import publicConfig from "../publicConfig" -import { sendTransaction } from "./send" +import {sendTransaction} from "./send" const accountCreatedEventType = "flow.AccountCreated" @@ -50,37 +50,37 @@ transaction(publicKey: String, flowTokenAmount: UFix64, sigAlgorithm: UInt8, has ` export async function createAccount( - publicKey: string, - sigAlgo: number, - hashAlgo: number, - authorization: fcl.Authorization + publicKey: string, + sigAlgo: number, + hashAlgo: number, + authorization: fcl.Authorization ) { - const result = await sendTransaction({ - transaction: txCreateAccount, - args: [ - fcl.arg(publicKey, t.String), - fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), - fcl.arg(sigAlgo.toString(), t.UInt8), - fcl.arg(hashAlgo.toString(), t.UInt8), - ], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) - - const accountCreatedEvent = result.events.find( - (event: fcl.Event) => event.type === accountCreatedEventType - ) - - if (!accountCreatedEvent) { - throw "Transaction did not emit account creation event" - } - - const address = accountCreatedEvent.data.address - const transactionId = accountCreatedEvent.transactionId - - return { - address, - transactionId, - } + const result = await sendTransaction({ + transaction: txCreateAccount, + args: [ + fcl.arg(publicKey, t.String), + fcl.arg(publicConfig.tokenAmountFlow, t.UFix64), + fcl.arg(sigAlgo.toString(), t.UInt8), + fcl.arg(hashAlgo.toString(), t.UInt8), + ], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + + const accountCreatedEvent = result.events.find( + (event: fcl.Event) => event.type === accountCreatedEventType + ) + + if (!accountCreatedEvent) { + throw "Transaction did not emit account creation event" + } + + const address = accountCreatedEvent.data.address + const transactionId = accountCreatedEvent.transactionId + + return { + address, + transactionId, + } } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 526d1b7..af5e2b7 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -59,30 +59,30 @@ transaction(address: Address, amount: UFix64) { type TokenType = "FLOW" | "FUSD" type Token = { - tx: string - amount: string + tx: string + amount: string } type Tokens = Record export const tokens: Tokens = { - FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, - FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, + FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } export async function fundAccount( - address: string, - token: TokenType, - authorization: fcl.Authorization + address: string, + token: TokenType, + authorization: fcl.Authorization ) { - const {tx, amount} = tokens[token] + const {tx, amount} = tokens[token] - await sendTransaction({ - transaction: tx, - args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) + await sendTransaction({ + transaction: tx, + args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) - return amount + return amount } From 70b5e50af93ca8a9a4903d1d17cb31f56778e9ee Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:03:46 -0600 Subject: [PATCH 28/88] add contractEVM to PublicConfig --- lib/publicConfig.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 6dbc192..531580a 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -45,6 +45,7 @@ export type PublicConfig = { contractFungibleToken: string contractFlowToken: string contractFUSD: string + contractEVM: string accessAPIHost: string isLocal: boolean walletDiscovery?: string @@ -63,6 +64,7 @@ const publicConfig: PublicConfig = { contractFungibleToken, contractFlowToken, contractFUSD, + contractEVM, accessAPIHost: process.env.NEXT_PUBLIC_ACCESS_API_HOST || "http://localhost:8080", isLocal: process.env.NEXT_PUBLIC_IS_LOCAL === "true" || false, From df81478e253bcf3cb2f1c7af7fbd085cce23207d Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:56:30 -0600 Subject: [PATCH 29/88] update mint_and_fund_evm_address transaction --- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 3 --- 1 file changed, 3 deletions(-) diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index 696bece..f4a2df8 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -8,15 +8,12 @@ import "EVM" transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} let coa: &EVM.BridgedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - self.tokenReceiver = signer.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - ?? panic("Unable to borrow receiver reference") if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) } From f3c818e836db262b1221059f9761d142a2e62446 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:44:51 -0800 Subject: [PATCH 30/88] Setup funding evm tx --- components/FundAccountFields.tsx | 24 +++++++------- flow.json | 7 +--- lib/constants.ts | 4 +-- lib/flow/fund.ts | 56 +++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/components/FundAccountFields.tsx b/components/FundAccountFields.tsx index 2b9233e..160357f 100644 --- a/components/FundAccountFields.tsx +++ b/components/FundAccountFields.tsx @@ -11,7 +11,7 @@ import { } from "lib/constants" import {NETWORK_DISPLAY_NAME} from "lib/network" import {Box, Link, Themed} from "theme-ui" -import {CustomInputComponent, CustomSelectComponent} from "./inputs" +import {CustomInputComponent} from "./inputs" const FUSD_VAULT_DOCS_LINK = { url: "https://docs.onflow.org/fusd/#how-do-i-get-an-fusd-enabled-wallet", @@ -53,18 +53,18 @@ export default function FundAccountFields({ max={128} sx={{fontFamily: "monospace"}} /> + {/**/} + {/* Token*/} + {/**/} + {/**/} + {/* */} + {/**/} - Token - - - - - diff --git a/flow.json b/flow.json index a7c0259..5cea712 100644 --- a/flow.json +++ b/flow.json @@ -90,10 +90,6 @@ "test": { "address": "179b6b1cb6755e31", "key": "6d8452af4ffac3d09276ce58358b0fb7f959b48437bf368c1d4b9c02588cabda" - }, - "testnet-account": { - "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", - "keys": "${SIGNER_PRIVATE_KEY}" } }, "deployments": { @@ -101,8 +97,7 @@ "emulator-account": [ "FungibleTokenMetadataViews", "Burner", - "FUSD", - "ViewResolver" + "FUSD" ] } } diff --git a/lib/constants.ts b/lib/constants.ts index 5d2022f..1ddf2e0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -19,7 +19,7 @@ export const PUBLIC_KEY_FORMAT_ERROR = "Public key must be a hexadecimal string with no spaces." export const PUBLIC_KEY_MISSING_ERROR = "Public key is required." export const ADDRESS_FORMAT_ERROR = - "Address must be a 16-character hexadecimal string." + "Address must be a 16-character or 42-character hexadecimal string." export const ADDRESS_MISSING_ERROR = "Address is required." export const CREATE_ACCOUNT_ERROR = "Account creation has failed" export const FUND_ACCOUNT_ERROR = "Account funding has failed" @@ -33,7 +33,7 @@ export const paths = { fundAccount: "/fund-account", } -export const ADDRESS_REGEXP = /^(0x)?[0-9a-fA-F]{16}$/ +export const ADDRESS_REGEXP = /^(0x)?([0-9a-fA-F]{16}|[0-9a-fA-F]{40})$/ export const NETWORK_CODEWORDS = { testnet: "0x6834ba37b3980209", diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index af5e2b7..8331ed6 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -32,6 +32,46 @@ transaction(address: Address, amount: UFix64) { } ` +const txFundAccountFlowEVM = ` +import FungibleToken from ${publicConfig.contractFungibleToken} +import FlowToken from ${publicConfig.contractFlowToken} + +import EVM from ${publicConfig.contractEVM} + +/// Mints Flow and transfers it to the given EVM address via the signer's CadenceOwnedAccount. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let coa: &EVM.BridgedAccount + + prepare(signer: auth(Storage) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("Could not borrow reference to the signer's COA!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + self.coa.deposit(from: <-mintedVault) + self.coa.call( + to: to, + data: [], + gasLimit: gasLimit, + value: EVM.Balance(flow: amount), + ) + } +} +` + const txFundAccountFUSD = ` import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} @@ -57,7 +97,7 @@ transaction(address: Address, amount: UFix64) { } ` -type TokenType = "FLOW" | "FUSD" +type TokenType = "FLOW" | "FLOWEVM" | "FUSD" type Token = { tx: string amount: string @@ -66,14 +106,28 @@ type Tokens = Record export const tokens: Tokens = { FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FLOWEVM: {tx: txFundAccountFlowEVM, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } +function getAddressType(address: string = ''): 'FLOW' | 'FLOWEVM' { + if (address.length > 16) { + return 'FLOWEVM' + } else { + return 'FLOW' + } +} + export async function fundAccount( address: string, token: TokenType, authorization: fcl.Authorization ) { + + const addressType = getAddressType(address) + + console.log("Address Type", addressType) + const {tx, amount} = tokens[token] await sendTransaction({ From 09cf3ecee9ea0d2ffac4cfa9493f6c786665cb89 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:44:51 -0800 Subject: [PATCH 31/88] Setup funding evm tx --- components/FundAccountFields.tsx | 24 +++++++------- flow.json | 7 +--- lib/constants.ts | 4 +-- lib/flow/fund.ts | 56 +++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/components/FundAccountFields.tsx b/components/FundAccountFields.tsx index 2b9233e..160357f 100644 --- a/components/FundAccountFields.tsx +++ b/components/FundAccountFields.tsx @@ -11,7 +11,7 @@ import { } from "lib/constants" import {NETWORK_DISPLAY_NAME} from "lib/network" import {Box, Link, Themed} from "theme-ui" -import {CustomInputComponent, CustomSelectComponent} from "./inputs" +import {CustomInputComponent} from "./inputs" const FUSD_VAULT_DOCS_LINK = { url: "https://docs.onflow.org/fusd/#how-do-i-get-an-fusd-enabled-wallet", @@ -53,18 +53,18 @@ export default function FundAccountFields({ max={128} sx={{fontFamily: "monospace"}} /> + {/**/} + {/* Token*/} + {/**/} + {/**/} + {/* */} + {/**/} - Token - - - - - diff --git a/flow.json b/flow.json index a7c0259..5cea712 100644 --- a/flow.json +++ b/flow.json @@ -90,10 +90,6 @@ "test": { "address": "179b6b1cb6755e31", "key": "6d8452af4ffac3d09276ce58358b0fb7f959b48437bf368c1d4b9c02588cabda" - }, - "testnet-account": { - "address": "${NEXT_PUBLIC_SIGNER_ADDRESS}", - "keys": "${SIGNER_PRIVATE_KEY}" } }, "deployments": { @@ -101,8 +97,7 @@ "emulator-account": [ "FungibleTokenMetadataViews", "Burner", - "FUSD", - "ViewResolver" + "FUSD" ] } } diff --git a/lib/constants.ts b/lib/constants.ts index 5d2022f..1ddf2e0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -19,7 +19,7 @@ export const PUBLIC_KEY_FORMAT_ERROR = "Public key must be a hexadecimal string with no spaces." export const PUBLIC_KEY_MISSING_ERROR = "Public key is required." export const ADDRESS_FORMAT_ERROR = - "Address must be a 16-character hexadecimal string." + "Address must be a 16-character or 42-character hexadecimal string." export const ADDRESS_MISSING_ERROR = "Address is required." export const CREATE_ACCOUNT_ERROR = "Account creation has failed" export const FUND_ACCOUNT_ERROR = "Account funding has failed" @@ -33,7 +33,7 @@ export const paths = { fundAccount: "/fund-account", } -export const ADDRESS_REGEXP = /^(0x)?[0-9a-fA-F]{16}$/ +export const ADDRESS_REGEXP = /^(0x)?([0-9a-fA-F]{16}|[0-9a-fA-F]{40})$/ export const NETWORK_CODEWORDS = { testnet: "0x6834ba37b3980209", diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index af5e2b7..8331ed6 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -32,6 +32,46 @@ transaction(address: Address, amount: UFix64) { } ` +const txFundAccountFlowEVM = ` +import FungibleToken from ${publicConfig.contractFungibleToken} +import FlowToken from ${publicConfig.contractFlowToken} + +import EVM from ${publicConfig.contractEVM} + +/// Mints Flow and transfers it to the given EVM address via the signer's CadenceOwnedAccount. +/// +transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { + + let tokenAdmin: &FlowToken.Administrator + let coa: &EVM.BridgedAccount + + prepare(signer: auth(Storage) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + } + self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + ?? panic("Could not borrow reference to the signer's COA!") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + destroy minter + + self.coa.deposit(from: <-mintedVault) + self.coa.call( + to: to, + data: [], + gasLimit: gasLimit, + value: EVM.Balance(flow: amount), + ) + } +} +` + const txFundAccountFUSD = ` import FUSD from ${publicConfig.contractFUSD} import FungibleToken from ${publicConfig.contractFungibleToken} @@ -57,7 +97,7 @@ transaction(address: Address, amount: UFix64) { } ` -type TokenType = "FLOW" | "FUSD" +type TokenType = "FLOW" | "FLOWEVM" | "FUSD" type Token = { tx: string amount: string @@ -66,14 +106,28 @@ type Tokens = Record export const tokens: Tokens = { FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, + FLOWEVM: {tx: txFundAccountFlowEVM, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } +function getAddressType(address: string = ''): 'FLOW' | 'FLOWEVM' { + if (address.length > 16) { + return 'FLOWEVM' + } else { + return 'FLOW' + } +} + export async function fundAccount( address: string, token: TokenType, authorization: fcl.Authorization ) { + + const addressType = getAddressType(address) + + console.log("Address Type", addressType) + const {tx, amount} = tokens[token] await sendTransaction({ From e5fb0fa5db86fad41b4a84f1f8d52564ecb9867c Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:42:26 -0600 Subject: [PATCH 32/88] fix linting issues --- lib/flow/fund.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 8331ed6..e4ca593 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -110,11 +110,11 @@ export const tokens: Tokens = { FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } -function getAddressType(address: string = ''): 'FLOW' | 'FLOWEVM' { +function getAddressType(address: string): "FLOW" | "FLOWEVM" { if (address.length > 16) { - return 'FLOWEVM' + return "FLOWEVM" } else { - return 'FLOW' + return "FLOW" } } @@ -123,7 +123,6 @@ export async function fundAccount( token: TokenType, authorization: fcl.Authorization ) { - const addressType = getAddressType(address) console.log("Address Type", addressType) From fccb6ab6a8be8fb4cc22f09f22ac88af2ceaca52 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:32:05 -0800 Subject: [PATCH 33/88] Change network codeword and address error --- lib/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/constants.ts b/lib/constants.ts index 1ddf2e0..f6a425b 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -19,7 +19,7 @@ export const PUBLIC_KEY_FORMAT_ERROR = "Public key must be a hexadecimal string with no spaces." export const PUBLIC_KEY_MISSING_ERROR = "Public key is required." export const ADDRESS_FORMAT_ERROR = - "Address must be a 16-character or 42-character hexadecimal string." + "Address must be a 16-character Cadence address or 42-character Ethereum address." export const ADDRESS_MISSING_ERROR = "Address is required." export const CREATE_ACCOUNT_ERROR = "Account creation has failed" export const FUND_ACCOUNT_ERROR = "Account funding has failed" @@ -37,5 +37,5 @@ export const ADDRESS_REGEXP = /^(0x)?([0-9a-fA-F]{16}|[0-9a-fA-F]{40})$/ export const NETWORK_CODEWORDS = { testnet: "0x6834ba37b3980209", - crescendo: "0x6834ba37b3980209", + crescendo: "0x5211829E88528817", } From cf2e414fd1debcae485a3a63a57871514248f1bb Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:50:09 -0800 Subject: [PATCH 34/88] Remove fusd --- lib/flow/fund.ts | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index e4ca593..ef99378 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -72,32 +72,7 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { } ` -const txFundAccountFUSD = ` -import FUSD from ${publicConfig.contractFUSD} -import FungibleToken from ${publicConfig.contractFungibleToken} - -transaction(address: Address, amount: UFix64) { - let tokenMinter: auth(FUSD.ProxyOwner) &FUSD.MinterProxy - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(minterAccount: AuthAccount) { - self.tokenMinter = minterAccount.storage.borrow( - from: FUSD.MinterProxyStoragePath - ) ?? panic("No minter available") - - self.tokenReceiver = getAccount(address).capabilities.borrow<&{FungibleToken.Receiver}>( - /public/fusdReceiver - ) ?? panic("Unable to borrow receiver reference") - } - - execute { - let mintedVault <- self.tokenMinter.mintTokens(amount: amount) - self.tokenReceiver.deposit(from: <-mintedVault) - } -} -` - -type TokenType = "FLOW" | "FLOWEVM" | "FUSD" +type TokenType = "FLOW" | "FLOWEVM" type Token = { tx: string amount: string @@ -107,7 +82,6 @@ type Tokens = Record export const tokens: Tokens = { FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, FLOWEVM: {tx: txFundAccountFlowEVM, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, - FUSD: {tx: txFundAccountFUSD, amount: TOKEN_FUNDING_AMOUNTS[FUSD_TYPE]}, } function getAddressType(address: string): "FLOW" | "FLOWEVM" { @@ -125,9 +99,7 @@ export async function fundAccount( ) { const addressType = getAddressType(address) - console.log("Address Type", addressType) - - const {tx, amount} = tokens[token] + const {tx, amount} = tokens[addressType] await sendTransaction({ transaction: tx, From f6a9d47f068460336bc333a6c07f61d5911c4851 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:58:51 -0800 Subject: [PATCH 35/88] Remove import --- env.example | 1 - lib/flow/fund.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/env.example b/env.example index f8b9fa9..c2e8350 100644 --- a/env.example +++ b/env.example @@ -10,7 +10,6 @@ NEXT_PUBLIC_HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001 NEXT_PUBLIC_IS_LOCAL=true NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN=0xee82856bf20e2aa6 NEXT_PUBLIC_CONTRACT_FLOW_TOKEN=0x0ae53cb6e3f42a79 -NEXT_PUBLIC_CONTRACT_FUSD=0xf8d6e0586b0a20c7 NEXT_PUBLIC_CONTRACT_EVM=0xf8d6e0586b0a20c7 NEXT_PUBLIC_GA_MEASUREMENT_ID= diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index ef99378..dda10bd 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -1,6 +1,6 @@ import * as fcl from "@onflow/fcl" import * as t from "@onflow/types" -import {FLOW_TYPE, FUSD_TYPE} from "../constants" +import {FLOW_TYPE} from "../constants" import publicConfig, {TOKEN_FUNDING_AMOUNTS} from "../publicConfig" import {sendTransaction} from "./send" From 815a477d1120b7bdb26068f081076fa4d044129d Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:15:04 -0800 Subject: [PATCH 36/88] Remove FUSD --- flow.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/flow.json b/flow.json index 5cea712..38d3cdc 100644 --- a/flow.json +++ b/flow.json @@ -12,14 +12,6 @@ "emulator": "f8d6e0586b0a20c7" } }, - "FUSD": { - "source": "./cadence/contracts/FUSD.cdc", - "aliases": { - "crescendo": "e223d8a629e49c68", - "emulator": "0ae53cb6e3f42a79", - "testnet": "e223d8a629e49c68" - } - }, "FlowToken": { "source": "./cadence/contracts/FlowToken.cdc", "aliases": { @@ -96,8 +88,7 @@ "emulator": { "emulator-account": [ "FungibleTokenMetadataViews", - "Burner", - "FUSD" + "Burner" ] } } From 312502ca1b0f26da220d9a69bf801c9cddd968b1 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:15:48 -0600 Subject: [PATCH 37/88] update fund transaction --- lib/flow/fund.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index dda10bd..dd06483 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -12,7 +12,7 @@ transaction(address: Address, amount: UFix64) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") From 91e2348a4ec53f64fb2598a7e0be9b5e0cd5b3b7 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:45:59 -0600 Subject: [PATCH 38/88] update EVM against flow-go feature/stable-cadence --- cadence/contracts/EVM.cdc | 347 ++++++++++++++++++++++++++++++++------ 1 file changed, 298 insertions(+), 49 deletions(-) diff --git a/cadence/contracts/EVM.cdc b/cadence/contracts/EVM.cdc index 32bb949..4ac9554 100644 --- a/cadence/contracts/EVM.cdc +++ b/cadence/contracts/EVM.cdc @@ -1,8 +1,12 @@ +import Crypto import "FlowToken" access(all) contract EVM { + access(all) + event CadenceOwnedAccountCreated(addressBytes: [UInt8; 20]) + /// EVMAddress is an EVM-compatible address access(all) struct EVMAddress { @@ -16,80 +20,179 @@ contract EVM { self.bytes = bytes } - /// Deposits the given vault into the EVM account with the given address - access(all) - fun deposit(from: @FlowToken.Vault) { - InternalEVM.deposit( - from: <-from, - to: self.bytes - ) - } - /// Balance of the address access(all) fun balance(): Balance { let balance = InternalEVM.balance( address: self.bytes ) - - return Balance(flow: balance) + return Balance(attoflow: balance) } } access(all) struct Balance { - /// The balance in FLOW + /// The balance in atto-FLOW + /// Atto-FLOW is the smallest denomination of FLOW (1e18 FLOW) + /// that is used to store account balances inside EVM + /// similar to the way WEI is used to store ETH divisible to 18 decimal places. + access(all) + var attoflow: UInt + + /// Constructs a new balance + access(all) + init(attoflow: UInt) { + self.attoflow = attoflow + } + + /// Sets the balance by a UFix64 (8 decimal points), the format + /// that is used in Cadence to store FLOW tokens. + access(all) + fun setFLOW(flow: UFix64){ + self.attoflow = InternalEVM.castToAttoFLOW(balance: flow) + } + + /// Casts the balance to a UFix64 (rounding down) + /// Warning! casting a balance to a UFix64 which supports a lower level of precision + /// (8 decimal points in compare to 18) might result in rounding down error. + /// Use the toAttoFlow function if you care need more accuracy. + access(all) + fun inFLOW(): UFix64 { + return InternalEVM.castToFLOW(balance: self.attoflow) + } + + /// Returns the balance in Atto-FLOW + access(all) + fun inAttoFLOW(): UInt { + return self.attoflow + } + } + + /// reports the status of evm execution. + access(all) enum Status: UInt8 { + /// is (rarely) returned when status is unknown + /// and something has gone very wrong. + access(all) case unknown + + /// is returned when execution of an evm transaction/call + /// has failed at the validation step (e.g. nonce mismatch). + /// An invalid transaction/call is rejected to be executed + /// or be included in a block. + access(all) case invalid + + /// is returned when execution of an evm transaction/call + /// has been successful but the vm has reported an error as + /// the outcome of execution (e.g. running out of gas). + /// A failed tx/call is included in a block. + /// Note that resubmission of a failed transaction would + /// result in invalid status in the second attempt, given + /// the nonce would be come invalid. + access(all) case failed + + /// is returned when execution of an evm transaction/call + /// has been successful and no error is reported by the vm. + access(all) case successful + } + + /// reports the outcome of evm transaction/call execution attempt + access(all) struct Result { + /// status of the execution + access(all) + let status: Status + + /// error code (error code zero means no error) access(all) - let flow: UFix64 + let errorCode: UInt64 - /// Constructs a new balance, given the balance in FLOW - init(flow: UFix64) { - self.flow = flow + /// returns the amount of gas metered during + /// evm execution + access(all) + let gasUsed: UInt64 + + /// returns the data that is returned from + /// the evm for the call. For coa.deploy + /// calls it returns the address bytes of the + /// newly deployed contract. + access(all) + let data: [UInt8] + + init( + status: Status, + errorCode: UInt64, + gasUsed: UInt64, + data: [UInt8] + ) { + self.status = status + self.errorCode = errorCode + self.gasUsed = gasUsed + self.data = data } + } - // TODO: - // /// Returns the balance in terms of atto-FLOW. - // /// Atto-FLOW is the smallest denomination of FLOW inside EVM - // access(all) - // fun toAttoFlow(): UInt64 + access(all) + resource interface Addressable { + /// The EVM address + access(all) + fun address(): EVMAddress } access(all) - resource BridgedAccount { + resource CadenceOwnedAccount: Addressable { access(self) - let addressBytes: [UInt8; 20] + var addressBytes: [UInt8; 20] + + init() { + // address is initially set to zero + // but updated through initAddress later + // we have to do this since we need resource id (uuid) + // to calculate the EVM address for this cadence owned account + self.addressBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } - init(addressBytes: [UInt8; 20]) { + access(contract) + fun initAddress(addressBytes: [UInt8; 20]) { + // only allow set address for the first time + // check address is empty + for item in self.addressBytes { + assert(item == 0, message: "address byte is not empty") + } self.addressBytes = addressBytes } - /// The EVM address of the bridged account + /// The EVM address of the cadence owned account access(all) fun address(): EVMAddress { // Always create a new EVMAddress instance return EVMAddress(bytes: self.addressBytes) } - /// Get balance of the bridged account + /// Get balance of the cadence owned account access(all) fun balance(): Balance { return self.address().balance() } - /// Deposits the given vault into the bridged account's balance + /// Deposits the given vault into the cadence owned account's balance access(all) fun deposit(from: @FlowToken.Vault) { - self.address().deposit(from: <-from) + InternalEVM.deposit( + from: <-from, + to: self.addressBytes + ) } - /// Withdraws the balance from the bridged account's balance + /// Withdraws the balance from the cadence owned account's balance + /// Note that amounts smaller than 10nF (10e-8) can't be withdrawn + /// given that Flow Token Vaults use UFix64s to store balances. + /// If the given balance conversion to UFix64 results in + /// rounding error, this function would fail. access(all) fun withdraw(balance: Balance): @FlowToken.Vault { let vault <- InternalEVM.withdraw( from: self.addressBytes, - amount: balance.flow + amount: balance.attoflow ) as! @FlowToken.Vault return <-vault } @@ -106,7 +209,7 @@ contract EVM { from: self.addressBytes, code: code, gasLimit: gasLimit, - value: value.flow + value: value.attoflow ) return EVMAddress(bytes: addressBytes) } @@ -119,33 +222,50 @@ contract EVM { data: [UInt8], gasLimit: UInt64, value: Balance - ): [UInt8] { - return InternalEVM.call( - from: self.addressBytes, - to: to.bytes, - data: data, - gasLimit: gasLimit, - value: value.flow - ) + ): Result { + return InternalEVM.call( + from: self.addressBytes, + to: to.bytes, + data: data, + gasLimit: gasLimit, + value: value.attoflow + ) as! Result } } - /// Creates a new bridged account + /// Creates a new cadence owned account access(all) - fun createBridgedAccount(): @BridgedAccount { - return <-create BridgedAccount( - addressBytes: InternalEVM.createBridgedAccount() - ) + fun createCadenceOwnedAccount(): @CadenceOwnedAccount { + let acc <-create CadenceOwnedAccount() + let addr = InternalEVM.createCadenceOwnedAccount(uuid: acc.uuid) + acc.initAddress(addressBytes: addr) + emit CadenceOwnedAccountCreated(addressBytes: addr) + return <-acc } /// Runs an a RLP-encoded EVM transaction, deducts the gas fees, /// and deposits the gas fees into the provided coinbase address. - /// - /// Returns true if the transaction was successful, - /// and returns false otherwise access(all) - fun run(tx: [UInt8], coinbase: EVMAddress) { - InternalEVM.run(tx: tx, coinbase: coinbase.bytes) + fun run(tx: [UInt8], coinbase: EVMAddress): Result { + return InternalEVM.run( + tx: tx, + coinbase: coinbase.bytes + ) as! Result + } + + /// mustRun runs the transaction using EVM.run yet it + /// rollback if the tx execution status is unknown or invalid. + /// Note that this method does not rollback if transaction + /// is executed but an vm error is reported as the outcome + /// of the execution (status: failed). + access(all) + fun mustRun(tx: [UInt8], coinbase: EVMAddress): Result { + let runResult = self.run(tx: tx, coinbase: coinbase) + assert( + runResult.status == Status.failed || runResult.status == Status.successful, + message: "tx is not valid for execution" + ) + return runResult } access(all) @@ -157,4 +277,133 @@ contract EVM { fun decodeABI(types: [Type], data: [UInt8]): [AnyStruct] { return InternalEVM.decodeABI(types: types, data: data) } + + access(all) + fun encodeABIWithSignature( + _ signature: String, + _ values: [AnyStruct] + ): [UInt8] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + let arguments = InternalEVM.encodeABI(values) + + return methodID.concat(arguments) + } + + access(all) + fun decodeABIWithSignature( + _ signature: String, + types: [Type], + data: [UInt8] + ): [AnyStruct] { + let methodID = HashAlgorithm.KECCAK_256.hash( + signature.utf8 + ).slice(from: 0, upTo: 4) + + for byte in methodID { + if byte != data.removeFirst() { + panic("signature mismatch") + } + } + + return InternalEVM.decodeABI(types: types, data: data) + } + + /// ValidationResult returns the result of COA ownership proof validation + access(all) + struct ValidationResult { + access(all) + let isValid: Bool + + access(all) + let problem: String? + + init(isValid: Bool, problem: String?) { + self.isValid = isValid + self.problem = problem + } + } + + /// validateCOAOwnershipProof validates a COA ownership proof + access(all) + fun validateCOAOwnershipProof( + address: Address, + path: PublicPath, + signedData: [UInt8], + keyIndices: [UInt64], + signatures: [[UInt8]], + evmAddress: [UInt8; 20] + ): ValidationResult { + + // make signature set first + // check number of signatures matches number of key indices + if keyIndices.length != signatures.length { + return ValidationResult( + isValid: false, + problem: "key indices size doesn't match the signatures" + ) + } + + var signatureSet: [Crypto.KeyListSignature] = [] + for signatureIndex, signature in signatures{ + signatureSet.append(Crypto.KeyListSignature( + keyIndex: Int(keyIndices[signatureIndex]), + signature: signature + )) + } + + // fetch account + let acc = getAccount(address) + + // constructing key list + let keyList = Crypto.KeyList() + for signature in signatureSet { + let key = acc.keys.get(keyIndex: signature.keyIndex)! + assert(!key.isRevoked, message: "revoked key is used") + keyList.add( + key.publicKey, + hashAlgorithm: key.hashAlgorithm, + weight: key.weight, + ) + } + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData + ) + + if !isValid{ + return ValidationResult( + isValid: false, + problem: "the given signatures are not valid or provide enough weight" + ) + } + + let coaRef = acc.getCapability(path) + .borrow<&EVM.CadenceOwnedAccount{EVM.Addressable}>() + + if coaRef == nil { + return ValidationResult( + isValid: false, + problem: "could not borrow bridge account's resource" + ) + } + + // verify evm address matching + var addr = coaRef!.address() + for index, item in coaRef!.address().bytes { + if item != evmAddress[index] { + return ValidationResult( + isValid: false, + problem: "evm address mismatch" + ) + } + } + + return ValidationResult( + isValid: true, + problem: nil + ) + } } \ No newline at end of file From 55fdeb9b4e982eb5ee2ee004f34e4bedeb130a7f Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:46:43 -0600 Subject: [PATCH 39/88] update evm scripts --- cadence/scripts/evm/get_balance.cdc | 2 +- cadence/scripts/evm/get_evm_address_string.cdc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc index ea03aca..4b5c15d 100644 --- a/cadence/scripts/evm/get_balance.cdc +++ b/cadence/scripts/evm/get_balance.cdc @@ -10,5 +10,5 @@ access(all) fun main(address: String): UFix64 { bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] ] - return EVM.EVMAddress(bytes: addressBytes).balance().flow + return EVM.EVMAddress(bytes: addressBytes).balance().inFlow } diff --git a/cadence/scripts/evm/get_evm_address_string.cdc b/cadence/scripts/evm/get_evm_address_string.cdc index a481d16..23319cf 100644 --- a/cadence/scripts/evm/get_evm_address_string.cdc +++ b/cadence/scripts/evm/get_evm_address_string.cdc @@ -4,7 +4,7 @@ import "EVM" /// access(all) fun main(flowAddress: Address): String? { let account = getAuthAccount(flowAddress) - if let address = account.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm)?.address() { + if let address = account.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm)?.address() { let bytes: [UInt8] = [] for byte in address.bytes { bytes.append(byte) From fe2cdd3d6476c2f349b9ae49b06dc0779e8e6244 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:47:48 -0600 Subject: [PATCH 40/88] update EVM Cadence transactions --- cadence/transactions/evm/create_coa_and_fund.cdc | 10 +++++----- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cadence/transactions/evm/create_coa_and_fund.cdc b/cadence/transactions/evm/create_coa_and_fund.cdc index 316073d..78ea9d9 100644 --- a/cadence/transactions/evm/create_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_coa_and_fund.cdc @@ -8,18 +8,18 @@ import "EVM" /// transaction(amount: UFix64) { let sentVault: @FlowToken.Vault - let coa: &EVM.BridgedAccount + let coa: &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { - let vaultRef = signer.storage.borrow( + let vaultRef = signer.storage.borrow( from: /storage/flowTokenVault ) ?? panic("Could not borrow reference to the owner's Vault!") self.sentVault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index f4a2df8..1d1d76e 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -8,16 +8,16 @@ import "EVM" transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let coa: &EVM.BridgedAccount + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 91305b95d599128365de11e33bb2bee7d7f814cb Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:48:17 -0600 Subject: [PATCH 41/88] update transfer_flow Cadence transaction --- cadence/transactions/transfer_flow.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/transactions/transfer_flow.cdc b/cadence/transactions/transfer_flow.cdc index 9d186a5..3e853d5 100644 --- a/cadence/transactions/transfer_flow.cdc +++ b/cadence/transactions/transfer_flow.cdc @@ -9,7 +9,7 @@ transaction (amount: UFix64, to: Address) { prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault - let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) + let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") // Withdraw tokens from the signer's stored vault From a32df44d68c199388c5f0477b4ed00d21a0c038a Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:48:39 -0600 Subject: [PATCH 42/88] update fund.ts --- lib/flow/fund.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index dda10bd..436d0ed 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -43,16 +43,16 @@ import EVM from ${publicConfig.contractEVM} transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let tokenAdmin: &FlowToken.Administrator - let coa: &EVM.BridgedAccount + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") - if signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) == nil { - signer.storage.save(<-EVM.createBridgedAccount(), to: /storage/evm) + if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { + signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow<&EVM.BridgedAccount>(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 5620a5ba3b46b608a692ddcb2f735841b515062e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:49:36 -0600 Subject: [PATCH 43/88] fix EVM transactions --- .../transactions/evm/create_flow_account_with_coa_and_fund.cdc | 2 +- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 2 +- lib/flow/fund.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc index 8de4f8b..6a26a07 100644 --- a/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc +++ b/cadence/transactions/evm/create_flow_account_with_coa_and_fund.cdc @@ -57,7 +57,7 @@ transaction( self.tokenReceiver.deposit(from: <-flowVault) - let coa <- EVM.createBridgedAccount() + let coa <- EVM.createCadenceOwnedAccount() coa.address().deposit(from: <-evmVault) self.newAccount.storage.save(<-coa, to: StoragePath(identifier: "evm")!) diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index 1d1d76e..262f2cc 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -17,7 +17,7 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 436d0ed..7bc43cd 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -52,7 +52,7 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { if signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) == nil { signer.storage.save(<-EVM.createCadenceOwnedAccount(), to: /storage/evm) } - self.coa = signer.storage.borrow(from: /storage/evm) + self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow reference to the signer's COA!") } From 3564b5fb611f54608ba4dc6aee03b38576d58b67 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:52:38 -0600 Subject: [PATCH 44/88] fix EVM balance query --- cadence/scripts/evm/get_balance.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/scripts/evm/get_balance.cdc b/cadence/scripts/evm/get_balance.cdc index 4b5c15d..39e5cc3 100644 --- a/cadence/scripts/evm/get_balance.cdc +++ b/cadence/scripts/evm/get_balance.cdc @@ -10,5 +10,5 @@ access(all) fun main(address: String): UFix64 { bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] ] - return EVM.EVMAddress(bytes: addressBytes).balance().inFlow + return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() } From 92faf7330fb1b43431c14ba0ba17b9b1a80d19d4 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:56:08 -0800 Subject: [PATCH 45/88] Add tx send changes --- lib/flow/fund.ts | 38 +++++++++++++++++++++++++++++++------- modules.d.ts | 2 +- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index dda10bd..944fe1c 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -101,13 +101,37 @@ export async function fundAccount( const {tx, amount} = tokens[addressType] - await sendTransaction({ - transaction: tx, - args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], - authorizations: [authorization], - payer: authorization, - proposer: authorization, - }) + if (addressType === "FLOWEVM") { + + let addressBytes = Array.from(Buffer.from(address, "hex")).map(b => b.toString()) + + await sendTransaction({ + transaction: tx, + args: [ + fcl.arg( + { + fields: [{name: "bytes", value: addressBytes}], + }, + t.Struct(`A.${publicConfig.contractEVM}.EVM.EVMAddress`, [{value: t.Array(t.UInt8)}]) + ), + fcl.arg(amount, t.UFix64), + fcl.arg("60000", t.UInt64), + ], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + } else { + + await sendTransaction({ + transaction: tx, + args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], + authorizations: [authorization], + payer: authorization, + proposer: authorization, + }) + + } return amount } diff --git a/modules.d.ts b/modules.d.ts index 3d4175f..7903dce 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -70,7 +70,7 @@ declare module "@onflow/fcl" { export function script(script: string): unknown export function decode(): unknown export function authz(): Authorization - export function arg(encodedPublicKey: string, type: string): TransactionArg + export function arg(value: any, type: string): TransactionArg export function args(args: TransactionArg[]): unknown export function authorizations(authorizations: Authorization[]): unknown export function proposer(proposer: Authorization): unknown From 67c450f8da8130e30f1b01426ba38d2fe4cb31d0 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:02:19 -0800 Subject: [PATCH 46/88] Turn off eslint rule --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index cb300c2..1ff58ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,7 +22,8 @@ "files": ["*.ts", "*.tsx"], "plugins": ["@typescript-eslint"], "rules": { - "@typescript-eslint/explicit-module-boundary-types": "off" + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off" } } ] From 021e7e9cee583575dfc0936197b66ecc401b5307 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:03:31 -0800 Subject: [PATCH 47/88] Run lint fix --- lib/flow/fund.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 88231f1..18619e7 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -102,28 +102,29 @@ export async function fundAccount( const {tx, amount} = tokens[addressType] if (addressType === "FLOWEVM") { - - let addressBytes = Array.from(Buffer.from(address, "hex")).map(b => b.toString()) + const addressBytes = Array.from(Buffer.from(address, "hex")).map(b => + b.toString() + ) await sendTransaction({ transaction: tx, args: [ - fcl.arg( - { - fields: [{name: "bytes", value: addressBytes}], - }, - t.Struct(`A.${publicConfig.contractEVM}.EVM.EVMAddress`, [{value: t.Array(t.UInt8)}]) - ), - fcl.arg(amount, t.UFix64), - fcl.arg("60000", t.UInt64), + fcl.arg( + { + fields: [{name: "bytes", value: addressBytes}], + }, + t.Struct(`A.${publicConfig.contractEVM}.EVM.EVMAddress`, [ + {value: t.Array(t.UInt8)}, + ]) + ), + fcl.arg(amount, t.UFix64), + fcl.arg("60000", t.UInt64), ], authorizations: [authorization], payer: authorization, proposer: authorization, }) - } else { - await sendTransaction({ transaction: tx, args: [fcl.arg(address, t.Address), fcl.arg(amount, t.UFix64)], @@ -131,7 +132,6 @@ export async function fundAccount( payer: authorization, proposer: authorization, }) - } return amount } From 41447d7da62f01fbafcc10d558b7e947ec8ec041 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:23:49 -0600 Subject: [PATCH 48/88] add previewnet to flow.json networks --- flow.json | 1 + 1 file changed, 1 insertion(+) diff --git a/flow.json b/flow.json index 38d3cdc..fe7b9f3 100644 --- a/flow.json +++ b/flow.json @@ -63,6 +63,7 @@ }, "networks": { "crescendo": "access.crescendo.nodes.onflow.org:9000", + "previewnet": "access.previewnet.nodes.onflow.org:9000", "emulator": "127.0.0.1:3569", "testnet": "access.devnet.nodes.onflow.org:9000" }, From 2b5291f7cfc5e112a42ae6e7433c143d8d572d46 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:24:23 -0600 Subject: [PATCH 49/88] update with EVM contract currently deployed to previewnet --- cadence/contracts/EVM.cdc | 51 ++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/cadence/contracts/EVM.cdc b/cadence/contracts/EVM.cdc index 4ac9554..5c8fdba 100644 --- a/cadence/contracts/EVM.cdc +++ b/cadence/contracts/EVM.cdc @@ -4,6 +4,13 @@ import "FlowToken" access(all) contract EVM { + // Entitlements enabling finer-graned access control on a CadenceOwnedAccount + access(all) entitlement Validate + access(all) entitlement Withdraw + access(all) entitlement Call + access(all) entitlement Deploy + access(all) entitlement Owner + access(all) event CadenceOwnedAccountCreated(addressBytes: [UInt8; 20]) @@ -16,13 +23,13 @@ contract EVM { let bytes: [UInt8; 20] /// Constructs a new EVM address from the given byte representation - init(bytes: [UInt8; 20]) { + view init(bytes: [UInt8; 20]) { self.bytes = bytes } /// Balance of the address access(all) - fun balance(): Balance { + view fun balance(): Balance { let balance = InternalEVM.balance( address: self.bytes ) @@ -42,7 +49,7 @@ contract EVM { /// Constructs a new balance access(all) - init(attoflow: UInt) { + view init(attoflow: UInt) { self.attoflow = attoflow } @@ -58,13 +65,13 @@ contract EVM { /// (8 decimal points in compare to 18) might result in rounding down error. /// Use the toAttoFlow function if you care need more accuracy. access(all) - fun inFLOW(): UFix64 { + view fun inFLOW(): UFix64 { return InternalEVM.castToFLOW(balance: self.attoflow) } /// Returns the balance in Atto-FLOW access(all) - fun inAttoFLOW(): UInt { + view fun inAttoFLOW(): UInt { return self.attoflow } } @@ -134,11 +141,11 @@ contract EVM { resource interface Addressable { /// The EVM address access(all) - fun address(): EVMAddress + view fun address(): EVMAddress } access(all) - resource CadenceOwnedAccount: Addressable { + resource CadenceOwnedAccount: Addressable { access(self) var addressBytes: [UInt8; 20] @@ -163,14 +170,14 @@ contract EVM { /// The EVM address of the cadence owned account access(all) - fun address(): EVMAddress { + view fun address(): EVMAddress { // Always create a new EVMAddress instance return EVMAddress(bytes: self.addressBytes) } /// Get balance of the cadence owned account access(all) - fun balance(): Balance { + view fun balance(): Balance { return self.address().balance() } @@ -183,12 +190,18 @@ contract EVM { ) } + /// The EVM address of the cadence owned account behind an entitlement, acting as proof of access + access(Owner | Validate) + view fun protectedAddress(): EVMAddress { + return self.address() + } + /// Withdraws the balance from the cadence owned account's balance /// Note that amounts smaller than 10nF (10e-8) can't be withdrawn /// given that Flow Token Vaults use UFix64s to store balances. /// If the given balance conversion to UFix64 results in /// rounding error, this function would fail. - access(all) + access(Owner | Withdraw) fun withdraw(balance: Balance): @FlowToken.Vault { let vault <- InternalEVM.withdraw( from: self.addressBytes, @@ -199,7 +212,7 @@ contract EVM { /// Deploys a contract to the EVM environment. /// Returns the address of the newly deployed contract - access(all) + access(Owner | Deploy) fun deploy( code: [UInt8], gasLimit: UInt64, @@ -216,7 +229,7 @@ contract EVM { /// Calls a function with the given data. /// The execution is limited by the given amount of gas - access(all) + access(Owner | Call) fun call( to: EVMAddress, data: [UInt8], @@ -315,7 +328,7 @@ contract EVM { struct ValidationResult { access(all) let isValid: Bool - + access(all) let problem: String? @@ -370,19 +383,19 @@ contract EVM { let isValid = keyList.verify( signatureSet: signatureSet, - signedData: signedData + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" ) if !isValid{ return ValidationResult( isValid: false, - problem: "the given signatures are not valid or provide enough weight" + problem: "the given signatures are not valid or provide enough weight" ) } - let coaRef = acc.getCapability(path) - .borrow<&EVM.CadenceOwnedAccount{EVM.Addressable}>() - + let coaRef = acc.capabilities.borrow<&EVM.CadenceOwnedAccount>(path) + if coaRef == nil { return ValidationResult( isValid: false, @@ -400,7 +413,7 @@ contract EVM { ) } } - + return ValidationResult( isValid: true, problem: nil From 6889efa1ade322339e1fce968b1c9cd1cbf601dc Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:51:17 -0600 Subject: [PATCH 50/88] add previewnet aliases to flow.json --- flow.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/flow.json b/flow.json index fe7b9f3..b34bf9d 100644 --- a/flow.json +++ b/flow.json @@ -3,13 +3,15 @@ "Burner": { "source": "./cadence/contracts/Burner.cdc", "aliases": { - "emulator": "ee82856bf20e2aa6" + "emulator": "ee82856bf20e2aa6", + "previewnet": "b6763b4399a888c8" } }, "EVM": { "source": "./cadence/contracts/EVM.cdc", "aliases": { - "emulator": "f8d6e0586b0a20c7" + "emulator": "f8d6e0586b0a20c7", + "previewnet": "b6763b4399a888c8" } }, "FlowToken": { @@ -17,6 +19,7 @@ "aliases": { "crescendo": "7e60df042a9c0868", "emulator": "0ae53cb6e3f42a79", + "previewnet": "4445e7ad11568276", "testnet": "7e60df042a9c0868" } }, @@ -25,6 +28,7 @@ "aliases": { "crescendo": "9a0766d93b6608b7", "emulator": "ee82856bf20e2aa6", + "previewnet": "a0225e7000ac82a9", "testnet": "9a0766d93b6608b7" } }, @@ -33,6 +37,7 @@ "aliases": { "crescendo": "f233dcee88fe0abe", "emulator": "f8d6e0586b0a20c7", + "previewnet": "a0225e7000ac82a9", "testnet": "f233dcee88fe0abe" } }, @@ -41,6 +46,7 @@ "aliases": { "crescendo": "631e88ae7f1d7c20", "emulator": "f8d6e0586b0a20c7", + "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } }, @@ -49,6 +55,7 @@ "aliases": { "crescendo": "631e88ae7f1d7c20", "emulator": "f8d6e0586b0a20c7", + "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } }, @@ -57,6 +64,7 @@ "aliases": { "crescendo": "631e88ae7f1d7c20", "emulator": "f8d6e0586b0a20c7", + "previewnet": "b6763b4399a888c8", "testnet": "631e88ae7f1d7c20" } } From 5499285f328cd995e81a87571c370d913212a981 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:55:34 -0800 Subject: [PATCH 51/88] Fix address length --- lib/flow/fund.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index ed9ba40..2bc8899 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -85,10 +85,10 @@ export const tokens: Tokens = { } function getAddressType(address: string): "FLOW" | "FLOWEVM" { - if (address.length > 16) { - return "FLOWEVM" - } else { + if (address.length <= 18) { return "FLOW" + } else { + return "FLOWEVM" } } From 0f01645cc14f330eef1d8f9486dbb318980e72ae Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:17:48 -0800 Subject: [PATCH 52/88] Add check for address --- pages/api/fund.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/api/fund.ts b/pages/api/fund.ts index 8e3492f..702f94a 100644 --- a/pages/api/fund.ts +++ b/pages/api/fund.ts @@ -49,8 +49,8 @@ export default async function fund(req: NextApiRequest, res: NextApiResponse) { const address = fcl.withPrefix(req.body.address) || "" const token = req.body.token - // Validate address - if (!isValidNetworkAddress(address, publicConfig.network)) { + // Validate Flow address + if (address.length <= 18 && !isValidNetworkAddress(address, publicConfig.network)) { res .status(400) .json({errors: [INVALID_NETWORK_ADDRESS_ERROR(publicConfig.network)]}) From 58f97ef92f83c463d33932a67f63a6441f657f29 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:20:54 -0800 Subject: [PATCH 53/88] Format --- pages/api/fund.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pages/api/fund.ts b/pages/api/fund.ts index 702f94a..0c12f22 100644 --- a/pages/api/fund.ts +++ b/pages/api/fund.ts @@ -49,8 +49,11 @@ export default async function fund(req: NextApiRequest, res: NextApiResponse) { const address = fcl.withPrefix(req.body.address) || "" const token = req.body.token - // Validate Flow address - if (address.length <= 18 && !isValidNetworkAddress(address, publicConfig.network)) { + // Validate Flow Address + if ( + address.length <= 18 && + !isValidNetworkAddress(address, publicConfig.network) + ) { res .status(400) .json({errors: [INVALID_NETWORK_ADDRESS_ERROR(publicConfig.network)]}) From 04cc488144fae24f0d60501f98762fa873044c8e Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:10:20 -0600 Subject: [PATCH 54/88] Fix EVM transactions (#84) --- cadence/transactions/evm/mint_and_fund_evm_address.cdc | 5 ++++- lib/flow/fund.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cadence/transactions/evm/mint_and_fund_evm_address.cdc b/cadence/transactions/evm/mint_and_fund_evm_address.cdc index 262f2cc..0a1b338 100644 --- a/cadence/transactions/evm/mint_and_fund_evm_address.cdc +++ b/cadence/transactions/evm/mint_and_fund_evm_address.cdc @@ -26,12 +26,15 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let mintedVault <- minter.mintTokens(amount: amount) destroy minter + let balance = EVM.Balance(attoflow: 0) + balance.setFLOW(flow: amount) + self.coa.deposit(from: <-mintedVault) self.coa.call( to: to, data: [], gasLimit: gasLimit, - value: EVM.Balance(flow: amount), + value: balance, ) } } diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 2bc8899..4da040d 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -61,12 +61,15 @@ transaction(to: EVM.EVMAddress, amount: UFix64, gasLimit: UInt64) { let mintedVault <- minter.mintTokens(amount: amount) destroy minter + let balance = EVM.Balance(attoflow: 0) + balance.setFLOW(flow: amount) + self.coa.deposit(from: <-mintedVault) self.coa.call( to: to, data: [], gasLimit: gasLimit, - value: EVM.Balance(flow: amount), + value: balance, ) } } From 55e783c79718e60fc5a035b61bb2d58768fc9522 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 26 Feb 2024 08:59:32 -0800 Subject: [PATCH 55/88] Fix EVM.Address struct in funding transaction (#85) --- lib/flow/fund.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 4da040d..0a94214 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -105,7 +105,11 @@ export async function fundAccount( const {tx, amount} = tokens[addressType] if (addressType === "FLOWEVM") { - const addressBytes = Array.from(Buffer.from(address, "hex")).map(b => + const withoutPrefix = fcl.sansPrefix(address) + if (!withoutPrefix) { + throw new Error("Invalid address") + } + const addressBytes = Array.from(Buffer.from(withoutPrefix, "hex")).map(b => b.toString() ) @@ -116,9 +120,10 @@ export async function fundAccount( { fields: [{name: "bytes", value: addressBytes}], }, - t.Struct(`A.${publicConfig.contractEVM}.EVM.EVMAddress`, [ - {value: t.Array(t.UInt8)}, - ]) + t.Struct( + `A.${fcl.sansPrefix(publicConfig.contractEVM)}.EVM.EVMAddress`, + [{value: t.Array(t.UInt8)}] + ) ), fcl.arg(amount, t.UFix64), fcl.arg("60000", t.UInt64), From 5b72914e508040e3586ef9047a230989df4542e2 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 26 Feb 2024 10:26:48 -0800 Subject: [PATCH 56/88] Increase fund timeout limit (#86) --- vercel.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..25d3984 --- /dev/null +++ b/vercel.json @@ -0,0 +1,7 @@ +{ + "functions": { + "pages/api/fund.ts": { + "maxDuration": 60 + } + } +} \ No newline at end of file From 45b164a9bb95c8f7553e60a7c504a77af193a3e7 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:24:57 -0800 Subject: [PATCH 57/88] Change crescendo to previewnet --- .github/workflows/ci.yml | 2 +- components/NetworkLinks.tsx | 12 ++++++------ env.example | 2 +- lib/constants.ts | 6 +++--- lib/publicConfig.ts | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c04346f..aae7fea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: NEXT_PUBLIC_SIGNER_ADDRESS: "0xf8d6e0586b0a20c7" SIGNER_PRIVATE_KEY: "91a22fbd87392b019fbe332c32695c14cf2ba5b6521476a8540228bdf1987068" NEXT_PUBLIC_TEST_NET_URL: "http://localhost:3000" - NEXT_PUBLIC_CRESCENDO_URL: "http://localhost:3000" + NEXT_PUBLIC_PREVIEWNET_URL: "http://localhost:3000" NEXT_PUBLIC_TOKEN_AMOUNT_FLOW: "1000.0" NEXT_PUBLIC_TOKEN_AMOUNT_FUSD: "10.0" NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN: "0xee82856bf20e2aa6" diff --git a/components/NetworkLinks.tsx b/components/NetworkLinks.tsx index 55c8d5a..b77d745 100644 --- a/components/NetworkLinks.tsx +++ b/components/NetworkLinks.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource theme-ui */ import TabNav, {TabNavLink} from "components/TabNav" -import {TEST_NET, CRESCENDO_NET} from "lib/constants" +import {TEST_NET, PREVIEW_NET} from "lib/constants" import {NETWORK_DISPLAY_NAME} from "lib/network" import publicConfig from "lib/publicConfig" @@ -41,19 +41,19 @@ export default function NetworkLinks() { Testnet Crescendo Faucet - Crescendo + Previewnet diff --git a/env.example b/env.example index c2e8350..52a04de 100644 --- a/env.example +++ b/env.example @@ -1,6 +1,6 @@ NEXT_PUBLIC_NETWORK=testnet NEXT_PUBLIC_TEST_NET_URL=http://localhost:3000 -NEXT_PUBLIC_CRESCENDO_URL=http://localhost:3000 +NEXT_PUBLIC_PREVIEWNET_URL=http://localhost:3000 NEXT_PUBLIC_MIXPANEL_TOKEN="dev" NEXT_PUBLIC_TOKEN_AMOUNT_FLOW=1000.0 NEXT_PUBLIC_TOKEN_AMOUNT_FUSD=10.0 diff --git a/lib/constants.ts b/lib/constants.ts index f6a425b..722e4b5 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,8 +1,8 @@ -export type Networks = "testnet" | "crescendo" +export type Networks = "testnet" | "previewnet" export type TokenTypes = typeof FLOW_TYPE | typeof FUSD_TYPE export const TEST_NET = "testnet" -export const CRESCENDO_NET = "crescendo" +export const PREVIEW_NET = "previewnet" export const FLOW_TYPE = "FLOW" export const FUSD_TYPE = "FUSD" export const NETWORK_STATUS_URL = "https://status.onflow.org/" @@ -37,5 +37,5 @@ export const ADDRESS_REGEXP = /^(0x)?([0-9a-fA-F]{16}|[0-9a-fA-F]{40})$/ export const NETWORK_CODEWORDS = { testnet: "0x6834ba37b3980209", - crescendo: "0x5211829E88528817", + previewnet: "0x5211829E88528817", } diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index 688c6c2..e55c198 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -6,8 +6,8 @@ if (!network) throw "Missing NEXT_PUBLIC_NETWORK" const testNetUrl = process.env.NEXT_PUBLIC_TEST_NET_URL if (!testNetUrl) throw "Missing NEXT_PUBLIC_TEST_NET_URL" -const crescendoNetUrl = process.env.NEXT_PUBLIC_CRESCENDO_URL -if (!crescendoNetUrl) throw "Missing NEXT_PUBLIC_CRESCENDO_URL" +const previewnetUrl = process.env.NEXT_PUBLIC_PREVIEWNET_URL +if (!previewnetUrl) throw "Missing NEXT_PUBLIC_PREVIEWNET_URL" const tokenAmountFlow = process.env.NEXT_PUBLIC_TOKEN_AMOUNT_FLOW if (!tokenAmountFlow) throw "Missing NEXT_PUBLIC_TOKEN_AMOUNT_FLOW" @@ -37,7 +37,7 @@ const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY export type PublicConfig = { network: Networks testNetUrl: string - crescendoNetUrl: string + previewnetUrl: string tokenAmountFlow: string tokenAmountFusd: string hcaptchaSiteKey: string @@ -54,7 +54,7 @@ export type PublicConfig = { const publicConfig: PublicConfig = { network, testNetUrl, - crescendoNetUrl, + previewnetUrl: previewnetUrl, tokenAmountFlow, tokenAmountFusd, hcaptchaSiteKey: From 842a646c7f9471157c30f8a631afb6c529147b37 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:25:28 -0800 Subject: [PATCH 58/88] Change image names --- components/NetworkLinks.tsx | 2 +- ...aucet-logo-crescendo.svg => flow-faucet-logo-previewnet.svg} | 0 .../{crescendo-faucet-icon.svg => previewnet-faucet-icon.svg} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename public/{flow-faucet-logo-crescendo.svg => flow-faucet-logo-previewnet.svg} (100%) rename public/{crescendo-faucet-icon.svg => previewnet-faucet-icon.svg} (100%) diff --git a/components/NetworkLinks.tsx b/components/NetworkLinks.tsx index b77d745..65a46bd 100644 --- a/components/NetworkLinks.tsx +++ b/components/NetworkLinks.tsx @@ -47,7 +47,7 @@ export default function NetworkLinks() { {`${NETWORK_DISPLAY_NAME} Date: Mon, 26 Feb 2024 11:26:27 -0800 Subject: [PATCH 59/88] Change name --- components/PageTitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/PageTitle.tsx b/components/PageTitle.tsx index e5032ea..d8e1c94 100644 --- a/components/PageTitle.tsx +++ b/components/PageTitle.tsx @@ -3,7 +3,7 @@ import publicConfig from "lib/publicConfig" import Head from "next/head" export const BASE_HTML_TITLE = `Flow ${ - publicConfig.network === TEST_NET ? "Testnet" : "Crescendo" + publicConfig.network === TEST_NET ? "Testnet" : "Previewnet" } Faucet` export default function PageTitle({children}: {children?: string}) { From 3f542315214dc6f8688e18fed69f596d12ac4bd9 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:18:02 -0800 Subject: [PATCH 60/88] Setup balance display --- components/FundAccountSubmitted.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 318d51f..0a45480 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -60,14 +60,21 @@ export default function FundAccountSubmitted({ result.token } tokens`} - - View Account - + {publicConfig.network !== 'testnet' ? ( + + View Account + + ) : ( + <> + +
1000.00
+ + )}
From 1c13adffc546c6f1efcabfddfee75b6b7ff3ef8e Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:19:27 -0800 Subject: [PATCH 61/88] Fix prettier --- components/FundAccountSubmitted.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 0a45480..31f1f5f 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -60,20 +60,20 @@ export default function FundAccountSubmitted({ result.token } tokens`} - {publicConfig.network !== 'testnet' ? ( - - View Account - + {publicConfig.network !== "testnet" ? ( + + View Account + ) : ( - <> - -
1000.00
- + <> + +
1000.00
+ )}
From e52156122b5281474943954de56881004d68a7b3 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:22:20 -0800 Subject: [PATCH 62/88] Fix conditional --- components/FundAccountSubmitted.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 31f1f5f..d83b065 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -60,7 +60,7 @@ export default function FundAccountSubmitted({ result.token } tokens`} - {publicConfig.network !== "testnet" ? ( + {publicConfig.network === "testnet" ? ( Date: Mon, 26 Feb 2024 14:55:23 -0800 Subject: [PATCH 63/88] Set balance --- components/FundAccountSubmitted.tsx | 68 +++++++++++++++++++++++++++-- lib/flow/send.ts | 13 ++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index d83b065..de6ae32 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -4,6 +4,10 @@ import LoadingFeedback from "components/LoadingFeedback" import {Box, Flex, Link, Themed, ThemeUICSSObject} from "theme-ui" import {ClientFundAccountResult} from "./FundAccountPanel" import publicConfig from "lib/publicConfig" +import {useEffect, useState} from "react"; +import {sendScript, sendTransaction} from "../lib/flow/send"; +import fcl from "@onflow/fcl"; +import t from "@onflow/types"; const styles: Record = { resultsContainer: { @@ -29,6 +33,58 @@ export default function FundAccountSubmitted({ }: { result?: ClientFundAccountResult }) { + const [isFetchingBalance, setIsFetchingBalance] = useState(false) + const [balance, setBalance] = useState('') + + const balanceScript = publicConfig.network === 'testnet' ? + `import EVM from ${publicConfig.contractEVM} + + /// Returns the Flow balance of a given EVM address in FlowEVM + /// + access(all) fun main(address: String): UFix64 { + let bytes = address.decodeHex() + let addressBytes: [UInt8; 20] = [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], + bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], + bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ] + return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() + }`: + `import FungibleToken from ${publicConfig.contractFungibleToken} +import FlowToken from ${publicConfig.contractFlowToken} + +access(all) fun main(account: Address): UFix64 { + + let vaultRef = getAccount(account) + .capabilities.borrow<&{FungibleToken.Balance}>(/public/flowTokenBalance) + ?? panic("Could not borrow Balance reference to the Vault") + + return vaultRef.balance +}` + + useEffect(() => { + if (typeof result === "undefined") return + + const fetchBalance = async (addr: string) => { + try { + setIsFetchingBalance(true); + const balance = await sendScript({ + script: balanceScript, + args: [fcl.arg(addr, t.Address)], + }); + console.log("Balance:", balance); + setBalance(balance) + } catch (error) { + console.error("Failed to fetch balance:", error); + } finally { + setIsFetchingBalance(false); + } + }; + + fetchBalance(result.address) + }, [result]) + return ( <> @@ -70,10 +126,14 @@ export default function FundAccountSubmitted({ View Account ) : ( - <> - -
1000.00
- + <> + + {isFetchingBalance ? ( +
Fetching...
+ ) : ( +
{balance}
+ )} + )}
diff --git a/lib/flow/send.ts b/lib/flow/send.ts index 1d73cff..1248350 100644 --- a/lib/flow/send.ts +++ b/lib/flow/send.ts @@ -24,3 +24,16 @@ export async function sendTransaction({ return fcl.tx(response).onceSealed() } + +export async function sendScript({ + script, + args, + }: { + script: string + args: fcl.TransactionArg[] +}) { + return await fcl.send([ + fcl.script(script), + fcl.args(args), + ]) +} From c52b597dcb8f84863819502497d95a4b5cb2f8c2 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:57:51 -0800 Subject: [PATCH 64/88] prettier and misc fixes --- components/FundAccountSubmitted.tsx | 48 ++++++++++++++--------------- lib/flow/send.ts | 11 +++---- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index de6ae32..02cf78b 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -4,10 +4,10 @@ import LoadingFeedback from "components/LoadingFeedback" import {Box, Flex, Link, Themed, ThemeUICSSObject} from "theme-ui" import {ClientFundAccountResult} from "./FundAccountPanel" import publicConfig from "lib/publicConfig" -import {useEffect, useState} from "react"; -import {sendScript, sendTransaction} from "../lib/flow/send"; -import fcl from "@onflow/fcl"; -import t from "@onflow/types"; +import {useEffect, useState} from "react" +import {sendScript} from "../lib/flow/send" +import fcl from "@onflow/fcl" +import t from "@onflow/types" const styles: Record = { resultsContainer: { @@ -34,10 +34,11 @@ export default function FundAccountSubmitted({ result?: ClientFundAccountResult }) { const [isFetchingBalance, setIsFetchingBalance] = useState(false) - const [balance, setBalance] = useState('') + const [balance, setBalance] = useState("") - const balanceScript = publicConfig.network === 'testnet' ? - `import EVM from ${publicConfig.contractEVM} + const balanceScript = + publicConfig.network === "testnet" + ? `import EVM from ${publicConfig.contractEVM} /// Returns the Flow balance of a given EVM address in FlowEVM /// @@ -50,8 +51,8 @@ export default function FundAccountSubmitted({ bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] ] return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() - }`: - `import FungibleToken from ${publicConfig.contractFungibleToken} + }` + : `import FungibleToken from ${publicConfig.contractFungibleToken} import FlowToken from ${publicConfig.contractFlowToken} access(all) fun main(account: Address): UFix64 { @@ -68,22 +69,21 @@ access(all) fun main(account: Address): UFix64 { const fetchBalance = async (addr: string) => { try { - setIsFetchingBalance(true); + setIsFetchingBalance(true) const balance = await sendScript({ script: balanceScript, args: [fcl.arg(addr, t.Address)], - }); - console.log("Balance:", balance); + }) setBalance(balance) } catch (error) { - console.error("Failed to fetch balance:", error); + setBalance("error") } finally { - setIsFetchingBalance(false); + setIsFetchingBalance(false) } - }; + } fetchBalance(result.address) - }, [result]) + }, [result, balanceScript]) return ( <> @@ -126,14 +126,14 @@ access(all) fun main(account: Address): UFix64 { View Account ) : ( - <> - - {isFetchingBalance ? ( -
Fetching...
- ) : ( -
{balance}
- )} - + <> + + {isFetchingBalance ? ( +
Fetching...
+ ) : ( +
{balance}
+ )} + )} diff --git a/lib/flow/send.ts b/lib/flow/send.ts index 1248350..d2ace1b 100644 --- a/lib/flow/send.ts +++ b/lib/flow/send.ts @@ -26,14 +26,11 @@ export async function sendTransaction({ } export async function sendScript({ - script, - args, - }: { + script, + args, +}: { script: string args: fcl.TransactionArg[] }) { - return await fcl.send([ - fcl.script(script), - fcl.args(args), - ]) + return await fcl.send([fcl.script(script), fcl.args(args)]) } From 2d42e2b5553691f1505e8f9665c9d524fe0b9db8 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:05:48 -0800 Subject: [PATCH 65/88] Move balance section --- components/FundAccountSubmitted.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 02cf78b..f00b00c 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -116,7 +116,7 @@ access(all) fun main(account: Address): UFix64 { result.token } tokens`} - {publicConfig.network === "testnet" ? ( + {publicConfig.network === "testnet" && View Account - ) : ( + } + + {publicConfig.network === "previewnet" && <> {isFetchingBalance ? ( -
Fetching...
+
Fetching...
) : ( -
{balance}
+
{balance}
)} - )} - + } )} From 732685b36f1a04a579b27e6395a289fabd9eb2a5 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:07:33 -0800 Subject: [PATCH 66/88] Run lint fix --- components/FundAccountSubmitted.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index f00b00c..820acdf 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -116,7 +116,7 @@ access(all) fun main(account: Address): UFix64 { result.token } tokens`} - {publicConfig.network === "testnet" && + {publicConfig.network === "testnet" && ( View Account - } + )} - {publicConfig.network === "previewnet" && - <> - - {isFetchingBalance ? ( -
Fetching...
- ) : ( -
{balance}
- )} - - } + {publicConfig.network === "previewnet" && ( + <> + + {isFetchingBalance ? ( +
Fetching...
+ ) : ( +
{balance}
+ )} + + )} )} From c1d112c08dcd2fa2e868f4266e06cd39fa0707af Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:11:07 -0800 Subject: [PATCH 67/88] Switch conditional --- components/FundAccountSubmitted.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 820acdf..1fa3b2d 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -37,7 +37,7 @@ export default function FundAccountSubmitted({ const [balance, setBalance] = useState("") const balanceScript = - publicConfig.network === "testnet" + publicConfig.network === "previewnet" ? `import EVM from ${publicConfig.contractEVM} /// Returns the Flow balance of a given EVM address in FlowEVM From 4c39579b201ff7652857e50daed6479169ec0a64 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:11:56 -0800 Subject: [PATCH 68/88] Change to label component --- components/FundAccountSubmitted.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 1fa3b2d..91a5269 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -129,7 +129,7 @@ access(all) fun main(account: Address): UFix64 { {publicConfig.network === "previewnet" && ( <> - + {isFetchingBalance ? (
Fetching...
) : ( From ea58ea196601193938b542befb50ba0624730c0f Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:18:59 -0800 Subject: [PATCH 69/88] Add address type check for arg --- components/FundAccountSubmitted.tsx | 10 +++++++++- lib/common.ts | 8 ++++++++ lib/flow/fund.ts | 10 +--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 91a5269..c6d38b7 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -8,6 +8,7 @@ import {useEffect, useState} from "react" import {sendScript} from "../lib/flow/send" import fcl from "@onflow/fcl" import t from "@onflow/types" +import {getAddressType} from "../lib/common" const styles: Record = { resultsContainer: { @@ -70,9 +71,16 @@ access(all) fun main(account: Address): UFix64 { const fetchBalance = async (addr: string) => { try { setIsFetchingBalance(true) + + const addressArgType = + publicConfig.network === "previewnet" && + getAddressType(result.address) === "FLOW" + ? t.Address + : t.String + const balance = await sendScript({ script: balanceScript, - args: [fcl.arg(addr, t.Address)], + args: [fcl.arg(addr, addressArgType)], }) setBalance(balance) } catch (error) { diff --git a/lib/common.ts b/lib/common.ts index 2d605d9..8ff9d4e 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -16,3 +16,11 @@ export function verifyAPIKey(req: string, keys: string[]): boolean { } return false } + +export const getAddressType = function (address: string): "FLOW" | "FLOWEVM" { + if (address.length <= 18) { + return "FLOW" + } else { + return "FLOWEVM" + } +} diff --git a/lib/flow/fund.ts b/lib/flow/fund.ts index 0a94214..d93d58c 100644 --- a/lib/flow/fund.ts +++ b/lib/flow/fund.ts @@ -3,6 +3,7 @@ import * as t from "@onflow/types" import {FLOW_TYPE} from "../constants" import publicConfig, {TOKEN_FUNDING_AMOUNTS} from "../publicConfig" import {sendTransaction} from "./send" +import {getAddressType} from "../common" const txFundAccountFLOW = ` import FlowToken from ${publicConfig.contractFlowToken} @@ -86,15 +87,6 @@ export const tokens: Tokens = { FLOW: {tx: txFundAccountFLOW, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, FLOWEVM: {tx: txFundAccountFlowEVM, amount: TOKEN_FUNDING_AMOUNTS[FLOW_TYPE]}, } - -function getAddressType(address: string): "FLOW" | "FLOWEVM" { - if (address.length <= 18) { - return "FLOW" - } else { - return "FLOWEVM" - } -} - export async function fundAccount( address: string, token: TokenType, From 89126087de89539d5f467d048e6866d39ad9c319 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:20:31 -0800 Subject: [PATCH 70/88] Switch address check logic --- components/FundAccountSubmitted.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index c6d38b7..813412f 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -73,7 +73,7 @@ access(all) fun main(account: Address): UFix64 { setIsFetchingBalance(true) const addressArgType = - publicConfig.network === "previewnet" && + publicConfig.network === "testnet" || getAddressType(result.address) === "FLOW" ? t.Address : t.String From 1d3a80b519702e0cf3a326b573505cd2cc63c809 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:26:24 -0800 Subject: [PATCH 71/88] sansPrefix evm addr --- components/FundAccountSubmitted.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 813412f..ee4b070 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -72,15 +72,28 @@ access(all) fun main(account: Address): UFix64 { try { setIsFetchingBalance(true) + const addressType = getAddressType(result.address) + let addressArg + const addressArgType = - publicConfig.network === "testnet" || - getAddressType(result.address) === "FLOW" + publicConfig.network === "testnet" || addressType === "FLOW" ? t.Address : t.String + if (addressType === "FLOWEVM") { + const withoutPrefix = fcl.sansPrefix(result.address) + if (!withoutPrefix) { + throw new Error("Invalid address") + } + + addressArg = withoutPrefix + } else { + addressArg = addr + } + const balance = await sendScript({ script: balanceScript, - args: [fcl.arg(addr, addressArgType)], + args: [fcl.arg(addressArg, addressArgType)], }) setBalance(balance) } catch (error) { From 48b43451e8289be69df5713aaca5521ad148826b Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:49:14 -0800 Subject: [PATCH 72/88] Change imports --- components/FundAccountSubmitted.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index ee4b070..9cf2d9b 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -6,8 +6,8 @@ import {ClientFundAccountResult} from "./FundAccountPanel" import publicConfig from "lib/publicConfig" import {useEffect, useState} from "react" import {sendScript} from "../lib/flow/send" -import fcl from "@onflow/fcl" -import t from "@onflow/types" +import * as fcl from "@onflow/fcl" +import * as t from "@onflow/types" import {getAddressType} from "../lib/common" const styles: Record = { From 0874cbf12f0f04f5af06691a7f23f0a114d4ec4b Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:58:54 -0800 Subject: [PATCH 73/88] Set error --- components/FundAccountSubmitted.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 9cf2d9b..0059c80 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -36,6 +36,7 @@ export default function FundAccountSubmitted({ }) { const [isFetchingBalance, setIsFetchingBalance] = useState(false) const [balance, setBalance] = useState("") + const [error, setError] = useState("") const balanceScript = publicConfig.network === "previewnet" @@ -97,7 +98,13 @@ access(all) fun main(account: Address): UFix64 { }) setBalance(balance) } catch (error) { - setBalance("error") + setBalance("--") + + if (error instanceof Error) { + setError(error.message) + } else { + setError("An unknown error occurred") + } } finally { setIsFetchingBalance(false) } @@ -109,7 +116,7 @@ access(all) fun main(account: Address): UFix64 { return ( <> - Funding account + Funding Account Great! Your request has been submitted. @@ -154,7 +161,10 @@ access(all) fun main(account: Address): UFix64 { {isFetchingBalance ? (
Fetching...
) : ( -
{balance}
+ <> +
{balance}
+ {error && error.length > 0 &&
{error}
} + )} )} From c8874b6a9165a5954033b0876971b77711065fe0 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:00:15 -0800 Subject: [PATCH 74/88] Add check on balance script --- components/FundAccountSubmitted.tsx | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 0059c80..f30112d 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -38,34 +38,6 @@ export default function FundAccountSubmitted({ const [balance, setBalance] = useState("") const [error, setError] = useState("") - const balanceScript = - publicConfig.network === "previewnet" - ? `import EVM from ${publicConfig.contractEVM} - - /// Returns the Flow balance of a given EVM address in FlowEVM - /// - access(all) fun main(address: String): UFix64 { - let bytes = address.decodeHex() - let addressBytes: [UInt8; 20] = [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], - bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], - bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] - ] - return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() - }` - : `import FungibleToken from ${publicConfig.contractFungibleToken} -import FlowToken from ${publicConfig.contractFlowToken} - -access(all) fun main(account: Address): UFix64 { - - let vaultRef = getAccount(account) - .capabilities.borrow<&{FungibleToken.Balance}>(/public/flowTokenBalance) - ?? panic("Could not borrow Balance reference to the Vault") - - return vaultRef.balance -}` - useEffect(() => { if (typeof result === "undefined") return @@ -92,6 +64,34 @@ access(all) fun main(account: Address): UFix64 { addressArg = addr } + const balanceScript = + publicConfig.network === "previewnet" && addressType === "FLOWEVM" + ? `import EVM from ${publicConfig.contractEVM} + + /// Returns the Flow balance of a given EVM address in FlowEVM + /// + access(all) fun main(address: String): UFix64 { + let bytes = address.decodeHex() + let addressBytes: [UInt8; 20] = [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], + bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], + bytes[15], bytes[16], bytes[17], bytes[18], bytes[19] + ] + return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() + }` + : `import FungibleToken from ${publicConfig.contractFungibleToken} +import FlowToken from ${publicConfig.contractFlowToken} + +access(all) fun main(account: Address): UFix64 { + + let vaultRef = getAccount(account) + .capabilities.borrow<&{FungibleToken.Balance}>(/public/flowTokenBalance) + ?? panic("Could not borrow Balance reference to the Vault") + + return vaultRef.balance +}` + const balance = await sendScript({ script: balanceScript, args: [fcl.arg(addressArg, addressArgType)], From c41da49627df25cdfa5a72b91d38863f30407e2c Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:00:40 -0800 Subject: [PATCH 75/88] run lint fix --- components/FundAccountSubmitted.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index f30112d..5ca648f 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -65,8 +65,8 @@ export default function FundAccountSubmitted({ } const balanceScript = - publicConfig.network === "previewnet" && addressType === "FLOWEVM" - ? `import EVM from ${publicConfig.contractEVM} + publicConfig.network === "previewnet" && addressType === "FLOWEVM" + ? `import EVM from ${publicConfig.contractEVM} /// Returns the Flow balance of a given EVM address in FlowEVM /// @@ -80,7 +80,7 @@ export default function FundAccountSubmitted({ ] return EVM.EVMAddress(bytes: addressBytes).balance().inFLOW() }` - : `import FungibleToken from ${publicConfig.contractFungibleToken} + : `import FungibleToken from ${publicConfig.contractFungibleToken} import FlowToken from ${publicConfig.contractFlowToken} access(all) fun main(account: Address): UFix64 { @@ -111,7 +111,7 @@ access(all) fun main(account: Address): UFix64 { } fetchBalance(result.address) - }, [result, balanceScript]) + }, [result]) return ( <> From 56772d96496d027f2f6ee22b503929e0801a3632 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:04:22 -0800 Subject: [PATCH 76/88] Change error name --- components/FundAccountSubmitted.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 5ca648f..ff631b8 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -36,7 +36,7 @@ export default function FundAccountSubmitted({ }) { const [isFetchingBalance, setIsFetchingBalance] = useState(false) const [balance, setBalance] = useState("") - const [error, setError] = useState("") + const [balanceError, setBalanceError] = useState("") useEffect(() => { if (typeof result === "undefined") return @@ -101,9 +101,9 @@ access(all) fun main(account: Address): UFix64 { setBalance("--") if (error instanceof Error) { - setError(error.message) + setBalanceError(error.message) } else { - setError("An unknown error occurred") + setBalanceError("An unknown error occurred") } } finally { setIsFetchingBalance(false) @@ -163,7 +163,9 @@ access(all) fun main(account: Address): UFix64 { ) : ( <>
{balance}
- {error && error.length > 0 &&
{error}
} + {balanceError && balanceError.length > 0 && ( +
{balanceError}
+ )} )} From 7b6a1ea94ec750294fd5b3d5ad34a5123d2f6f04 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:06:40 -0800 Subject: [PATCH 77/88] change error --- components/FundAccountSubmitted.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index ff631b8..1bb5c3a 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -101,9 +101,9 @@ access(all) fun main(account: Address): UFix64 { setBalance("--") if (error instanceof Error) { - setBalanceError(error.message) + setBalanceError((error as Error).message) } else { - setBalanceError("An unknown error occurred") + setBalanceError("An error occurred") } } finally { setIsFetchingBalance(false) From 502a1664ac4b446e15dace6e3f8d703f63fedb49 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:09:15 -0800 Subject: [PATCH 78/88] update error message --- components/FundAccountSubmitted.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 1bb5c3a..530311b 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -99,12 +99,7 @@ access(all) fun main(account: Address): UFix64 { setBalance(balance) } catch (error) { setBalance("--") - - if (error instanceof Error) { - setBalanceError((error as Error).message) - } else { - setBalanceError("An error occurred") - } + setBalanceError("An error occurred") } finally { setIsFetchingBalance(false) } From 765af53624d233293b6f0f643561ae72b081522f Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:23:38 -0800 Subject: [PATCH 79/88] decode response --- components/FundAccountSubmitted.tsx | 4 ++-- lib/flow/send.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/FundAccountSubmitted.tsx b/components/FundAccountSubmitted.tsx index 530311b..6571dd5 100644 --- a/components/FundAccountSubmitted.tsx +++ b/components/FundAccountSubmitted.tsx @@ -92,11 +92,11 @@ access(all) fun main(account: Address): UFix64 { return vaultRef.balance }` - const balance = await sendScript({ + const res = await sendScript({ script: balanceScript, args: [fcl.arg(addressArg, addressArgType)], }) - setBalance(balance) + setBalance(res) } catch (error) { setBalance("--") setBalanceError("An error occurred") diff --git a/lib/flow/send.ts b/lib/flow/send.ts index d2ace1b..25e40ae 100644 --- a/lib/flow/send.ts +++ b/lib/flow/send.ts @@ -32,5 +32,5 @@ export async function sendScript({ script: string args: fcl.TransactionArg[] }) { - return await fcl.send([fcl.script(script), fcl.args(args)]) + return fcl.send([fcl.script(script), fcl.args(args)]).then(fcl.decode) } From a1f8183c17e543d129ca5812423f88f722493806 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:04:01 -0800 Subject: [PATCH 80/88] Remove from footer --- components/Footer.tsx | 29 +---------------------------- lib/constants.ts | 1 - public/tanooki-labs.png | Bin 5654 -> 0 bytes public/tanooki-labs@2x.png | Bin 15830 -> 0 bytes public/tanooki-labs@3x.png | Bin 28699 -> 0 bytes 5 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 public/tanooki-labs.png delete mode 100644 public/tanooki-labs@2x.png delete mode 100644 public/tanooki-labs@3x.png diff --git a/components/Footer.tsx b/components/Footer.tsx index f3d4b59..2bc7be9 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -1,7 +1,6 @@ /** @jsxImportSource theme-ui */ -import {TANOOKI_LABS_URL} from "lib/constants" -import {Box, Container, Flex, Link, ThemeUICSSObject} from "theme-ui" +import {Container, Flex, ThemeUICSSObject} from "theme-ui" export default function Footer() { const styles: Record = { @@ -24,10 +23,6 @@ export default function Footer() { alignItems: "center", flexDirection: ["column", "row"], }, - tanookiLabsLink: { - borderBottom: "1px solid", - borderColor: "gray.200", - }, } return ( @@ -37,28 +32,6 @@ export default function Footer() { {`Copyright © ${new Date().getFullYear()} Flow Foundation.`} - - - Design & Development by{" "} - - Tanooki Labs - - - - - Tanooki Labs - - - diff --git a/lib/constants.ts b/lib/constants.ts index 722e4b5..283d0ac 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -12,7 +12,6 @@ export const ACCOUNTS_KEYS_DOCS_URL = "https://developers.flow.com/concepts/start-here/accounts-and-keys" export const H_CAPTCHA_URL = "https://www.hcaptcha.com/" export const FAUCET_GITHUB_URL = "https://www.github.com/onflow/faucet/issues" -export const TANOOKI_LABS_URL = "https://tanookilabs.com/" export const DISCORD_URL = "https://discord.gg/flow" export const PUBLIC_KEY_FORMAT_ERROR = diff --git a/public/tanooki-labs.png b/public/tanooki-labs.png deleted file mode 100644 index c6149b7ad33f1bf3a71a983b8886c14d60b3c7df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5654 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 diff --git a/public/tanooki-labs@2x.png b/public/tanooki-labs@2x.png deleted file mode 100644 index 67c691f54fc2c879283bfbb20ff57f071418429c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15830 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≷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 From dca9293dac4bfc4de866d16e796a1a5f6aa734ca Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 26 Feb 2024 22:42:14 -0800 Subject: [PATCH 81/88] Bump max duration on all endpoints & fix hashing/signing algorithms (#91) --- lib/crypto.ts | 14 ++++---------- modules.d.ts | 1 - package-lock.json | 21 --------------------- package.json | 1 - vercel.json | 2 +- 5 files changed, 5 insertions(+), 34 deletions(-) diff --git a/lib/crypto.ts b/lib/crypto.ts index c47108c..84e19c1 100644 --- a/lib/crypto.ts +++ b/lib/crypto.ts @@ -1,9 +1,3 @@ -import { - ECDSA_P256, - ECDSA_secp256k1, - SHA2_256, - SHA3_256, -} from "@onflow/util-encode-key" import {createHash} from "crypto" import {ec as EC} from "elliptic" import {SHA3} from "sha3" @@ -12,13 +6,13 @@ export type SigAlgoTypes = "ECDSA_P256" | "ECDSA_secp256k1" export type HashAlgoTypes = "SHA2_256" | "SHA3_256" export const SigAlgos: Record = { - ECDSA_P256: ECDSA_P256, - ECDSA_secp256k1: ECDSA_secp256k1, + ECDSA_P256: 1, + ECDSA_secp256k1: 2, } export const HashAlgos: Record = { - SHA2_256: SHA2_256, - SHA3_256: SHA3_256, + SHA2_256: 1, + SHA3_256: 3, } const hashSHA2 = (msg: string) => { diff --git a/modules.d.ts b/modules.d.ts index 7903dce..999e163 100644 --- a/modules.d.ts +++ b/modules.d.ts @@ -102,5 +102,4 @@ declare module "*.cdc" { export default content } -declare module "@onflow/util-encode-key" declare module "@onflow/types" diff --git a/package-lock.json b/package-lock.json index 2c26bee..21d2600 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@hcaptcha/react-hcaptcha": "^0.3.4", "@onflow/fcl": "1.9.0", "@onflow/types": "1.2.1", - "@onflow/util-encode-key": "1.2.1", "@prisma/client": "^4.2.1", "@theme-ui/match-media": "^0.9.1", "clipboard-copy": "^4.0.1", @@ -1601,16 +1600,6 @@ "@babel/runtime": "^7.18.6" } }, - "node_modules/@onflow/util-encode-key": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@onflow/util-encode-key/-/util-encode-key-1.2.1.tgz", - "integrity": "sha512-Blqpi0Xox4sHjxM+Qcu1RP8Li5DbTMSMxnK8+VPjHibbeQXlg+bva7mskgtMcJyjZfemqhRuubjNwTMbwGiOrw==", - "dependencies": { - "@babel/runtime": "^7.18.6", - "@onflow/rlp": "^1.2.1", - "@onflow/util-invariant": "^1.2.1" - } - }, "node_modules/@onflow/util-invariant": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@onflow/util-invariant/-/util-invariant-1.2.1.tgz", @@ -9243,16 +9232,6 @@ "@babel/runtime": "^7.18.6" } }, - "@onflow/util-encode-key": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@onflow/util-encode-key/-/util-encode-key-1.2.1.tgz", - "integrity": "sha512-Blqpi0Xox4sHjxM+Qcu1RP8Li5DbTMSMxnK8+VPjHibbeQXlg+bva7mskgtMcJyjZfemqhRuubjNwTMbwGiOrw==", - "requires": { - "@babel/runtime": "^7.18.6", - "@onflow/rlp": "^1.2.1", - "@onflow/util-invariant": "^1.2.1" - } - }, "@onflow/util-invariant": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@onflow/util-invariant/-/util-invariant-1.2.1.tgz", diff --git a/package.json b/package.json index 1b677ab..9d225a4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@hcaptcha/react-hcaptcha": "^0.3.4", "@onflow/fcl": "1.9.0", "@onflow/types": "1.2.1", - "@onflow/util-encode-key": "1.2.1", "@prisma/client": "^4.2.1", "@theme-ui/match-media": "^0.9.1", "clipboard-copy": "^4.0.1", diff --git a/vercel.json b/vercel.json index 25d3984..655e336 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "functions": { - "pages/api/fund.ts": { + "pages/api/**/*.ts": { "maxDuration": 60 } } From ab380ecb12c246df6ba777649c7df1d3351a24ed Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:20:40 -0800 Subject: [PATCH 82/88] Remove link on create account for previewnet --- components/CreateAccountSubmitted.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/components/CreateAccountSubmitted.tsx b/components/CreateAccountSubmitted.tsx index f85dc2a..1d41a33 100644 --- a/components/CreateAccountSubmitted.tsx +++ b/components/CreateAccountSubmitted.tsx @@ -8,7 +8,7 @@ import {Field} from "formik" import {NETWORK_DISPLAY_NAME} from "lib/network" import publicConfig from "lib/publicConfig" import {useRef, useState} from "react" -import {Box, Flex, Link, Themed, ThemeUICSSObject} from "theme-ui" +import {Box, Flex, Themed, ThemeUICSSObject} from "theme-ui" import {CustomInputComponent} from "./inputs" const styles: Record = { @@ -81,14 +81,14 @@ export default function CreateAccountSubmitted({address}: {address: string}) { publicConfig.tokenAmountFlow ).toLocaleString()} FLOW tokens`} - - View Account - + {/**/} + {/* View Account*/} + {/**/}
From 72185eb417e76bd3e4dae512d62ad6afdb5cd82d Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:43:06 -0700 Subject: [PATCH 83/88] Add to label for account type --- components/FundAccountFields.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/FundAccountFields.tsx b/components/FundAccountFields.tsx index 160357f..b3ba923 100644 --- a/components/FundAccountFields.tsx +++ b/components/FundAccountFields.tsx @@ -45,7 +45,7 @@ export default function FundAccountFields({ Date: Mon, 10 Jun 2024 15:41:31 -0400 Subject: [PATCH 84/88] fix copy --- components/FundAccountFields.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/FundAccountFields.tsx b/components/FundAccountFields.tsx index b3ba923..532bf05 100644 --- a/components/FundAccountFields.tsx +++ b/components/FundAccountFields.tsx @@ -37,10 +37,12 @@ export default function FundAccountFields({ return ( <> - Fund your FLOW account + Fund your Flow or Flow EVM account Once you have created an account, you can incrementally add additional - funds to it. Your address should be a 16 character hexadecimal string. + funds to it. Your address should be a valid Flow account (16 character + hexadecimal string) or a valid EVM address (42 character hexadecimal + including the `0x` prefix). Date: Wed, 14 Aug 2024 16:26:11 -0400 Subject: [PATCH 85/88] remove previewnet --- .github/workflows/ci.yml | 1 - components/FundAccountFields.tsx | 25 +------------------ components/NetworkLinks.tsx | 15 ------------ lib/constants.ts | 5 +--- lib/fclConfig.ts | 1 - lib/publicConfig.ts | 10 -------- pages/api/fund.ts | 41 +------------------------------- 7 files changed, 3 insertions(+), 95 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aae7fea..a3dc8c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,6 @@ jobs: NEXT_PUBLIC_TOKEN_AMOUNT_FUSD: "10.0" NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN: "0xee82856bf20e2aa6" NEXT_PUBLIC_CONTRACT_FLOW_TOKEN: "0x0ae53cb6e3f42a79" - NEXT_PUBLIC_CONTRACT_FUSD: "0xf8d6e0586b0a20c7" NEXT_PUBLIC_CONTRACT_EVM: "0xf8d6e0586b0a20c7" NEXT_PUBLIC_NETWORK: "testnet" diff --git a/components/FundAccountFields.tsx b/components/FundAccountFields.tsx index 532bf05..4b6da48 100644 --- a/components/FundAccountFields.tsx +++ b/components/FundAccountFields.tsx @@ -1,32 +1,19 @@ /** @jsxImportSource theme-ui */ import Button from "components/Button" import Captcha from "components/Captcha" -import FormErrors from "components/FormErrors" import {Field, useFormikContext} from "formik" -import { - FLOW_TYPE, - FUSD_TYPE, - MISSING_FUSD_VAULT_ERROR, - paths, -} from "lib/constants" +import {FLOW_TYPE, paths} from "lib/constants" import {NETWORK_DISPLAY_NAME} from "lib/network" import {Box, Link, Themed} from "theme-ui" import {CustomInputComponent} from "./inputs" -const FUSD_VAULT_DOCS_LINK = { - url: "https://docs.onflow.org/fusd/#how-do-i-get-an-fusd-enabled-wallet", - name: "How do I get an FUSD-enabled wallet?", -} - export const TOKEN_OPTIONS = [ {value: FLOW_TYPE, label: `${NETWORK_DISPLAY_NAME} FLOW`}, - {value: FUSD_TYPE, label: `${NETWORK_DISPLAY_NAME} FUSD`}, ] export default function FundAccountFields({ captchaToken, setCaptchaToken, - errors, }: { captchaToken: string setCaptchaToken: React.Dispatch> @@ -79,16 +66,6 @@ export default function FundAccountFields({ > Fund Your Account - {errors.length > 0 && ( - e === MISSING_FUSD_VAULT_ERROR) - ? FUSD_VAULT_DOCS_LINK - : undefined - } - /> - )} diff --git a/components/NetworkLinks.tsx b/components/NetworkLinks.tsx index 65a46bd..e651ead 100644 --- a/components/NetworkLinks.tsx +++ b/components/NetworkLinks.tsx @@ -40,21 +40,6 @@ export default function NetworkLinks() { /> Testnet - - {`${NETWORK_DISPLAY_NAME} - Previewnet - ) diff --git a/lib/constants.ts b/lib/constants.ts index 283d0ac..d14a9c3 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,10 +1,9 @@ export type Networks = "testnet" | "previewnet" -export type TokenTypes = typeof FLOW_TYPE | typeof FUSD_TYPE +export type TokenTypes = typeof FLOW_TYPE export const TEST_NET = "testnet" export const PREVIEW_NET = "previewnet" export const FLOW_TYPE = "FLOW" -export const FUSD_TYPE = "FUSD" export const NETWORK_STATUS_URL = "https://status.onflow.org/" export const GENERATE_KEYS_DOCS_URL = "https://developers.flow.com/tooling/flow-cli/generate-keys" @@ -22,8 +21,6 @@ export const ADDRESS_FORMAT_ERROR = export const ADDRESS_MISSING_ERROR = "Address is required." export const CREATE_ACCOUNT_ERROR = "Account creation has failed" export const FUND_ACCOUNT_ERROR = "Account funding has failed" -export const MISSING_FUSD_VAULT_ERROR = - "This account does not have an FUSD vault" export const INVALID_NETWORK_ADDRESS_ERROR = (network: Networks) => `This address is invalid for ${network}, please verify that it is correct` diff --git a/lib/fclConfig.ts b/lib/fclConfig.ts index 5a63ca2..051ba5d 100644 --- a/lib/fclConfig.ts +++ b/lib/fclConfig.ts @@ -10,5 +10,4 @@ config() // .put("challenge.handshake", publicConfig.walletDiscovery) .put("0xFUNGIBLETOKENADDRESS", publicConfig.contractFungibleToken) .put("0xFLOWTOKENADDRESS", publicConfig.contractFlowToken) - .put("0xFUSDADDRESS", publicConfig.contractFUSD) .put("0xEVMADDRESS", publicConfig.contractEVM) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index e55c198..eb018c9 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -6,9 +6,6 @@ if (!network) throw "Missing NEXT_PUBLIC_NETWORK" const testNetUrl = process.env.NEXT_PUBLIC_TEST_NET_URL if (!testNetUrl) throw "Missing NEXT_PUBLIC_TEST_NET_URL" -const previewnetUrl = process.env.NEXT_PUBLIC_PREVIEWNET_URL -if (!previewnetUrl) throw "Missing NEXT_PUBLIC_PREVIEWNET_URL" - const tokenAmountFlow = process.env.NEXT_PUBLIC_TOKEN_AMOUNT_FLOW if (!tokenAmountFlow) throw "Missing NEXT_PUBLIC_TOKEN_AMOUNT_FLOW" @@ -24,9 +21,6 @@ if (!contractFungibleToken) throw "Missing NEXT_PUBLIC_CONTRACT_FUNGIBLE_TOKEN" const contractFlowToken = process.env.NEXT_PUBLIC_CONTRACT_FLOW_TOKEN if (!contractFlowToken) throw "Missing NEXT_PUBLIC_CONTRACT_FLOW_TOKEN" -const contractFUSD = process.env.NEXT_PUBLIC_CONTRACT_FUSD -if (!contractFUSD) throw "Missing NEXT_PUBLIC_CONTRACT_FUSD" - const contractEVM = process.env.NEXT_PUBLIC_CONTRACT_EVM if (!contractEVM) throw "Missing NEXT_PUBLIC_CONTRACT_EVM" @@ -37,14 +31,12 @@ const walletDiscovery = process.env.NEXT_PUBLIC_WALLET_DISCOVERY export type PublicConfig = { network: Networks testNetUrl: string - previewnetUrl: string tokenAmountFlow: string tokenAmountFusd: string hcaptchaSiteKey: string signerAddress: string contractFungibleToken: string contractFlowToken: string - contractFUSD: string contractEVM: string accessAPIHost: string isLocal: boolean @@ -54,7 +46,6 @@ export type PublicConfig = { const publicConfig: PublicConfig = { network, testNetUrl, - previewnetUrl: previewnetUrl, tokenAmountFlow, tokenAmountFusd, hcaptchaSiteKey: @@ -63,7 +54,6 @@ const publicConfig: PublicConfig = { signerAddress, contractFungibleToken, contractFlowToken, - contractFUSD, contractEVM, accessAPIHost: process.env.NEXT_PUBLIC_ACCESS_API_HOST || "http://localhost:8888", diff --git a/pages/api/fund.ts b/pages/api/fund.ts index 0c12f22..d13921d 100644 --- a/pages/api/fund.ts +++ b/pages/api/fund.ts @@ -1,11 +1,6 @@ import * as fcl from "@onflow/fcl" -import * as t from "@onflow/types" import {verify} from "hcaptcha" -import { - FUSD_TYPE, - INVALID_NETWORK_ADDRESS_ERROR, - MISSING_FUSD_VAULT_ERROR, -} from "lib/constants" +import {INVALID_NETWORK_ADDRESS_ERROR} from "lib/constants" import publicConfig from "lib/publicConfig" import {NextApiRequest, NextApiResponse} from "next" import config from "../../lib/config" @@ -16,21 +11,6 @@ import {verifyAPIKey} from "../../lib/common" import {ValidationError} from "yup" import {isValidNetworkAddress} from "lib/network" -const scriptCheckFUSDVault = ` - import FUSD from ${publicConfig.contractFUSD} - import FungibleToken from ${publicConfig.contractFungibleToken} - - pub fun main(address: Address): Bool { - let receiver = getAccount(address) - .getCapability<&FUSD.Vault{FungibleToken.Receiver}>(/public/fusdReceiver) - .check() - let balance = getAccount(address) - .getCapability<&FUSD.Vault{FungibleToken.Balance}>(/public/fusdBalance) - .check() - return receiver && balance - } -` - export default async function fund(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") { const apiKey = req.headers["authorization"] @@ -60,25 +40,6 @@ export default async function fund(req: NextApiRequest, res: NextApiResponse) { return } - if (token === FUSD_TYPE) { - try { - const hasFUSDVault = await fcl - .send([ - fcl.script(scriptCheckFUSDVault), - fcl.args([fcl.arg(address, t.Address)]), - ]) - .then(fcl.decode) - - if (hasFUSDVault === false) { - res.status(400).json({errors: [MISSING_FUSD_VAULT_ERROR]}) - return - } - } catch { - res.status(400).json({errors: ["FUSD vault check failed"]}) - return - } - } - if (apiKey) { if (!verifyAPIKey(apiKey, config.apiKeys)) { res.status(401).json({errors: ["Invalid API key"]}) From bc051ec1aad5bd4cf09aef1d03f65df19d098037 Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:28:06 -0400 Subject: [PATCH 86/88] remove previewnet --- components/NetworkLinks.tsx | 2 +- lib/constants.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/components/NetworkLinks.tsx b/components/NetworkLinks.tsx index e651ead..1f3b010 100644 --- a/components/NetworkLinks.tsx +++ b/components/NetworkLinks.tsx @@ -1,7 +1,7 @@ /** @jsxImportSource theme-ui */ import TabNav, {TabNavLink} from "components/TabNav" -import {TEST_NET, PREVIEW_NET} from "lib/constants" +import {TEST_NET} from "lib/constants" import {NETWORK_DISPLAY_NAME} from "lib/network" import publicConfig from "lib/publicConfig" diff --git a/lib/constants.ts b/lib/constants.ts index d14a9c3..969ff12 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -2,7 +2,6 @@ export type Networks = "testnet" | "previewnet" export type TokenTypes = typeof FLOW_TYPE export const TEST_NET = "testnet" -export const PREVIEW_NET = "previewnet" export const FLOW_TYPE = "FLOW" export const NETWORK_STATUS_URL = "https://status.onflow.org/" export const GENERATE_KEYS_DOCS_URL = From d3d5d12a33d76f675bbee5857dcfdeccbff0169b Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:30:25 -0400 Subject: [PATCH 87/88] remove fusd --- lib/publicConfig.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/publicConfig.ts b/lib/publicConfig.ts index eb018c9..14a45b0 100644 --- a/lib/publicConfig.ts +++ b/lib/publicConfig.ts @@ -63,7 +63,6 @@ const publicConfig: PublicConfig = { export const TOKEN_FUNDING_AMOUNTS: Record = { FLOW: publicConfig.tokenAmountFlow, - FUSD: publicConfig.tokenAmountFusd, } export default publicConfig From c28f0a18fc01d7466142a24724fafc1c1672267b Mon Sep 17 00:00:00 2001 From: Alex Ni <12097569+nialexsan@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:00:00 -0400 Subject: [PATCH 88/88] restore link --- components/CreateAccountSubmitted.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/components/CreateAccountSubmitted.tsx b/components/CreateAccountSubmitted.tsx index 1d41a33..9323517 100644 --- a/components/CreateAccountSubmitted.tsx +++ b/components/CreateAccountSubmitted.tsx @@ -8,7 +8,7 @@ import {Field} from "formik" import {NETWORK_DISPLAY_NAME} from "lib/network" import publicConfig from "lib/publicConfig" import {useRef, useState} from "react" -import {Box, Flex, Themed, ThemeUICSSObject} from "theme-ui" +import {Box, Flex, Link, Themed, ThemeUICSSObject} from "theme-ui" import {CustomInputComponent} from "./inputs" const styles: Record = { @@ -81,14 +81,14 @@ export default function CreateAccountSubmitted({address}: {address: string}) { publicConfig.tokenAmountFlow ).toLocaleString()} FLOW tokens`} - {/**/} - {/* View Account*/} - {/**/} + + View Account +