From 6010b864f7a166e14e2258ea1cc49a897c2a58bb Mon Sep 17 00:00:00 2001 From: lanford33 Date: Fri, 11 Aug 2023 09:42:04 +0800 Subject: [PATCH 01/15] feat: setup owned account --- .env.development | 2 + .env.production | 1 + components/common/Siderbar.js | 28 ++- .../hybrid_custody/SetupOwnedAccountModal.js | 207 ++++++++++++++++++ flow/config.js | 5 + flow/hc_scripts.js | 14 ++ flow/hc_transactions.js | 63 ++++++ lib/atoms.js | 5 + lib/utils.js | 13 ++ pages/account/[account]/parent/index.js | 132 +++++++++++ .../linked_accounts/get_parent_addresses.cdc | 43 ++++ .../linked_accounts/setup_owned_account.cdc | 41 ++++ ...up_owned_account_and_publish_to_parent.cdc | 61 ++++++ publicConfig.js | 4 + 14 files changed, 611 insertions(+), 8 deletions(-) create mode 100644 components/hybrid_custody/SetupOwnedAccountModal.js create mode 100644 flow/hc_scripts.js create mode 100644 flow/hc_transactions.js create mode 100644 pages/account/[account]/parent/index.js create mode 100644 public/scripts/linked_accounts/get_parent_addresses.cdc create mode 100644 public/transactions/linked_accounts/setup_owned_account.cdc create mode 100644 public/transactions/linked_accounts/setup_owned_account_and_publish_to_parent.cdc diff --git a/.env.development b/.env.development index 307861c..1c5e7ff 100644 --- a/.env.development +++ b/.env.development @@ -23,3 +23,5 @@ NEXT_PUBLIC_FUNGIBLE_TOKEN_SWITCHBOARD_ADDRESS=0xf233dcee88fe0abe NEXT_PUBLIC_FLOWBOX_ADDRESS=0x1b3930856571a52b NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS=0x4eb8a10cb9f87357 + +NEXT_PUBLIC_HYBRIDCUSTODY_ADDRESS=0xd8a7e05a7ac670c0 \ No newline at end of file diff --git a/.env.production b/.env.production index 307861c..4d551aa 100644 --- a/.env.production +++ b/.env.production @@ -23,3 +23,4 @@ NEXT_PUBLIC_FUNGIBLE_TOKEN_SWITCHBOARD_ADDRESS=0xf233dcee88fe0abe NEXT_PUBLIC_FLOWBOX_ADDRESS=0x1b3930856571a52b NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS=0x4eb8a10cb9f87357 +NEXT_PUBLIC_HYBRIDCUSTODY_ADDRESS=0xd8a7e05a7ac670c0 diff --git a/components/common/Siderbar.js b/components/common/Siderbar.js index 810d246..c121aaa 100644 --- a/components/common/Siderbar.js +++ b/components/common/Siderbar.js @@ -15,11 +15,17 @@ export default function Sidebar(props) { { id: "4", label: `Collection`, link: { pathname: "/account/[account]/collection", query: { account: account } } }, { id: "5", label: `Storefront`, link: { pathname: "/account/[account]/storefront", query: { account: account } } }, { id: "6", label: `Contract`, link: { pathname: "/account/[account]/contract", query: { account: account } } }, + { id: "7", label: `Linked Accts`, subItems: [ + { id: "7-0", isSubItem: true, label: "Parents", smLabel: "Parents", link: { pathname: "/account/[account]/parent", query: { account: account } } }, + { id: "7-1", isSubItem: true, label: "Child Accts", smLabel: "ChildAccts", link: { pathname: "/account/[account]/child_account", query: { account: account } } }, + { id: "7-2", isSubItem: true, label: "Owned Accts", smLabel: "OwnedAccts", link: { pathname: "/account/[account]/owned_account", query: { account: account } } }, + ] + }, { - id: "7", label: "Storage", subItems: [ - { id: "7-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } }, - { id: "7-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } }, - { id: "7-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } }, + id: "8", label: "Storage", subItems: [ + { id: "8-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } }, + { id: "8-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } }, + { id: "8-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } }, ] } // { id: "5", label: `Analyzer`, link: { pathname: "/account/[account]/analyzer", query: { account: account } } }, @@ -32,11 +38,17 @@ export default function Sidebar(props) { { id: "2", label: `Token`, link: { pathname: "/account/[account]/fungible_token", query: { account: account } } }, { id: "4", label: `Collection`, link: { pathname: "/account/[account]/collection", query: { account: account } } }, { id: "5", label: `Contract`, link: { pathname: "/account/[account]/contract", query: { account: account } } }, + { id: "6", label: `Linked Accts`, subItems: [ + { id: "6-0", isSubItem: true, label: "Parents", smLabel: "Parents", link: { pathname: "/account/[account]/parent", query: { account: account } } }, + { id: "6-1", isSubItem: true, label: "Child Accts", smLabel: "ChildAccts", link: { pathname: "/account/[account]/child_account", query: { account: account } } }, + { id: "6-2", isSubItem: true, label: "Owned Accts", smLabel: "OwnedAccts", link: { pathname: "/account/[account]/owned_account", query: { account: account } } }, + ] + }, { - id: "6", label: "Storage", subItems: [ - { id: "6-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } }, - { id: "6-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } }, - { id: "6-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } }, + id: "7", label: "Storage", subItems: [ + { id: "7-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } }, + { id: "7-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } }, + { id: "7-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } }, ] } // { id: "5", label: `Analyzer`, link: { pathname: "/account/[account]/analyzer", query: { account: account } } }, diff --git a/components/hybrid_custody/SetupOwnedAccountModal.js b/components/hybrid_custody/SetupOwnedAccountModal.js new file mode 100644 index 0000000..082ff52 --- /dev/null +++ b/components/hybrid_custody/SetupOwnedAccountModal.js @@ -0,0 +1,207 @@ +import { Fragment, useEffect, useRef, useState } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { useRecoilState } from "recoil" +import { showSetupOwnedAccountState, transactionStatusState, transactionInProgressState } from '../../lib/atoms' +import * as fcl from "@onflow/fcl"; +import { isValidFlowAddress, isValidPositiveFlowDecimals, isValidPositiveNumber, isValidUrl } from '../../lib/utils' +import { useRouter } from 'next/router' +import { useSWRConfig } from 'swr' +import { setupOwnedAccount, setupOwnedAccountAndPublishToParent } from '../../flow/hc_transactions'; + +export default function SetupOwnedAccountModal(props) { + const router = useRouter() + const { mutate } = useSWRConfig() + const {account} = props + const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) + const [, setTransactionStatus] = useRecoilState(transactionStatusState) + + const [showSetupOwnedAccount, setShowSetupOwnedAccount] = useRecoilState(showSetupOwnedAccountState) + const [name, setName] = useState("") + const [desc, setDesc] = useState("") + const thumbnailPlaceholder = "https://assets-global.website-files.com/5f734f4dbd95382f4fdfa0ea/6395e6749db8fe00a41cc279_flow-flow-logo.svg" + const [thumbnail, setThumbnail] = useState(thumbnailPlaceholder) + const [thumbnailError, setThumbnailError] = useState(null) + + useEffect(() => fcl.currentUser.subscribe(setUser), []) + const [user, setUser] = useState({ loggedIn: null }) + + const cancelButtonRef = useRef(null) + + return ( + + + +
+ + +
+
+ + +
+
+ + {"Setup Owned Account"} + +
+ {/*
+ +
+ { + setParentError(null) + setParent("") + if (e.target.value === "") { + return + } + + if (!isValidFlowAddress(e.target.value)) { + setParentError("Invalid address") + return + } + setParent(e.target.value) + }} + /> +
+ { + parentError ? + : null + } +
*/} +
+ +
+ { + setName(e.target.value) + }} + /> +
+
+
+ +
+ { + setDesc(e.target.value) + }} + /> +
+
+
+ +
+ { + setThumbnailError(null) + if (e.target.value === "") { + return + } + + if (!isValidUrl(e.target.value)) { + setThumbnailError("Invalid URL") + return + } + setThumbnail(e.target.value) + }} + /> +
+ { + thumbnailError ? + : null + } +
+
+
+
+
+ + +
+
+
+
+
+
+
+ ) +} diff --git a/flow/config.js b/flow/config.js index f3a0702..cad3805 100644 --- a/flow/config.js +++ b/flow/config.js @@ -19,4 +19,9 @@ config({ "0xFlowbox": publicConfig.flowboxAddress, "0xFlowviewAccountBookmark": publicConfig.accountBookmarkAddress, "0xNFTStorefrontV2": publicConfig.nftStorefrontV2Address, + + "0xHybridCustody": publicConfig.hybridCustodyAddress, + "0xCapabilityFactory": publicConfig.hybridCustodyAddress, + "0xCapabilityFilter": publicConfig.hybridCustodyAddress, + "0xCapabilityDelegator": publicConfig.hybridCustodyAddress }) \ No newline at end of file diff --git a/flow/hc_scripts.js b/flow/hc_scripts.js new file mode 100644 index 0000000..1df186f --- /dev/null +++ b/flow/hc_scripts.js @@ -0,0 +1,14 @@ +import * as fcl from "@onflow/fcl" + +export const getParentAddresses = async (address) => { + const code = await (await fetch("/scripts/linked_accounts/get_parent_addresses.cdc")).text() + + const result = await fcl.query({ + cadence: code, + args: (arg, t) => [ + arg(address, t.Address) + ] + }) + + return result +} diff --git a/flow/hc_transactions.js b/flow/hc_transactions.js new file mode 100644 index 0000000..c2d6343 --- /dev/null +++ b/flow/hc_transactions.js @@ -0,0 +1,63 @@ +import * as fcl from "@onflow/fcl" +import { txHandler } from "./transactions" + +export const setupOwnedAccountAndPublishToParent = async ( + parent, name, desc, thumbnail, + setTransactionInProgress, + setTransactionStatus +) => { + const txFunc = async () => { + return await doSetupOwnedAccountAndPublishToParent(parent, name, desc, thumbnail) + } + + return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) +} + +const doSetupOwnedAccountAndPublishToParent = async (parent, name, desc, thumbnail) => { + const code = await (await fetch("/transactions/linked_accounts/setup_owned_account_and_publish_to_parent.cdc")).text() + + const transactionId = fcl.mutate({ + cadence: code, + args: (arg, t) => [ + arg(parent, t.Address), + arg(name, t.Optional(t.String)), + arg(desc, t.Optional(t.String)), + arg(thumbnail, t.Optional(t.String)) + ], + proposer: fcl.currentUser, + payer: fcl.currentUser, + limit: 9999 + }) + + return transactionId +} + +export const setupOwnedAccount = async ( + name, desc, thumbnail, + setTransactionInProgress, + setTransactionStatus +) => { + const txFunc = async () => { + return await doSetupOwnedAccount(name, desc, thumbnail) + } + + return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) +} + +const doSetupOwnedAccount = async (name, desc, thumbnail) => { + const code = await (await fetch("/transactions/linked_accounts/setup_owned_account.cdc")).text() + + const transactionId = fcl.mutate({ + cadence: code, + args: (arg, t) => [ + arg(name, t.Optional(t.String)), + arg(desc, t.Optional(t.String)), + arg(thumbnail, t.Optional(t.String)) + ], + proposer: fcl.currentUser, + payer: fcl.currentUser, + limit: 9999 + }) + + return transactionId +} \ No newline at end of file diff --git a/lib/atoms.js b/lib/atoms.js index 36ad5a9..e5dece1 100644 --- a/lib/atoms.js +++ b/lib/atoms.js @@ -45,6 +45,11 @@ export const showCreateListingState = atom({ default: false }) +export const showSetupOwnedAccountState = atom({ + key: "showSetupOwnedAccountState", + default: false +}) + export const accountBookmarkState = atom({ key: "accountBookmarkState", default: {address: "Flow Address", note: "Note"} diff --git a/lib/utils.js b/lib/utils.js index cb4a289..5afdd9f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -44,6 +44,19 @@ export const isValidPositiveFlowDecimals = (number) => { } } +export const isValidUrl = (url) => { + // 创建一个正则表达式模式,用于匹配 URL + var pattern = new RegExp('^(https?:\\/\\/)?'+ // 协议 + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // 域名 + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // IP 地址 + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // 端口和路径 + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // 查询字符串 + '(\\#[-a-z\\d_]*)?$','i'); // 锚点 + + // 使用正则表达式进行匹配 + return pattern.test(url); +} + // TODO: replace this with fcl.account(address) export const isValidFlowAddress = (address) => { if (!address.startsWith("0x") || address.length != 18) { diff --git a/pages/account/[account]/parent/index.js b/pages/account/[account]/parent/index.js new file mode 100644 index 0000000..f73090e --- /dev/null +++ b/pages/account/[account]/parent/index.js @@ -0,0 +1,132 @@ +import * as fcl from "@onflow/fcl" +import { useRouter } from "next/router" +import { useEffect, useState } from "react" +import useSWR from "swr" +import ItemsView from "../../../../components/common/ItemsView" +import Layout from "../../../../components/common/Layout" +import Spinner from "../../../../components/common/Spinner" +import { isValidFlowAddress } from "../../../../lib/utils" +import Custom404 from "../404" +import { getParentAddresses } from "../../../../flow/hc_scripts" +import { useRecoilState } from "recoil" +import { showSetupOwnedAccountState, transactionInProgressState, transactionStatusState } from "../../../../lib/atoms" +import SetupOwnedAccountModal from "../../../../components/hybrid_custody/SetupOwnedAccountModal" + +const parentsFetcher = async (funcName, address) => { + return getParentAddresses(address) +} + +export default function Parents(props) { + const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) + const [, setTransactionStatus] = useRecoilState(transactionStatusState) + const [showSetupOwnedAccount, setShowSetupOwnedAccount] = useRecoilState(showSetupOwnedAccountState) + + const router = useRouter() + const { account } = router.query + + const [ownedAccountInfo, setOwnedAccountInfo] = useState(null) + const [user, setUser] = useState({ loggedIn: null }) + + useEffect(() => fcl.currentUser.subscribe(setUser), []) + + const { data: itemsData, error: itemsError } = useSWR( + account && isValidFlowAddress(account) ? ["parentsFetcher", account] : null, parentsFetcher + ) + + useEffect(() => { + console.log(itemsData) + if (itemsData) { + setOwnedAccountInfo(itemsData) + } + }, [itemsData]) + + if (!account) { + return
+ } + + if (!isValidFlowAddress(account)) { + return + } + + const showItems = () => { + if (!ownedAccountInfo) { + return ( +
+ +
+ ) + } + + return ( + <> + {ownedAccountInfo && ownedAccountInfo.parentAddresses.length > 0 ? + ownedAccountInfo.parentAddresses.map((item, index) => { + return ( + + ) + }) : +
+ Nothing found +
+ } + + ) + } + + return ( +
+ +
+
+

+ {`Parents ${ownedAccountInfo ? `(${ownedAccountInfo.parentAddresses.length})` : ""}`} +

+
+ { + user && user.loggedIn && user.addr == account && ownedAccountInfo && !ownedAccountInfo.isOwnedAccountExists ? + + : null + } + { + user && user.loggedIn && user.addr == account && ownedAccountInfo && ownedAccountInfo.isOwnedAccountExists ? + + : null + } +
+
+
+
+
+ {showItems()} +
+
+
+
+
+ +
+ ) +} \ No newline at end of file diff --git a/public/scripts/linked_accounts/get_parent_addresses.cdc b/public/scripts/linked_accounts/get_parent_addresses.cdc new file mode 100644 index 0000000..02a7fda --- /dev/null +++ b/public/scripts/linked_accounts/get_parent_addresses.cdc @@ -0,0 +1,43 @@ +import HybridCustody from 0xHybridCustody +import MetadataViews from 0xMetadataViews + +pub struct OwnedAccountInfo { + pub let display: MetadataViews.Display? + pub let parentStatuses: {Address: Bool} + pub let parentAddresses: [Address] + pub let isOwnedAccountExists: Bool + + init( + display: MetadataViews.Display?, + parentStatuses: {Address: Bool}, + parentAddresses: [Address], + isOwnedAccountExists: Bool + ) { + self.display = display + self.parentStatuses = parentStatuses + self.parentAddresses = parentAddresses + self.isOwnedAccountExists = isOwnedAccountExists + } +} + +pub fun main(child: Address): OwnedAccountInfo { + let acct = getAuthAccount(child) + let o = acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) + if let owned = o { + let viewType = Type() + let display = owned.resolveView(viewType) as! MetadataViews.Display? + return OwnedAccountInfo( + display: display, + parentStatuses: owned.getParentStatuses(), + parentAddresses: owned.getParentAddresses(), + isOwnedAccountExists: true + ) + } + + return OwnedAccountInfo( + display: nil, + parentStatuses: {}, + parentAddresses: [], + isOwnedAccountExists: false + ) +} \ No newline at end of file diff --git a/public/transactions/linked_accounts/setup_owned_account.cdc b/public/transactions/linked_accounts/setup_owned_account.cdc new file mode 100644 index 0000000..af91916 --- /dev/null +++ b/public/transactions/linked_accounts/setup_owned_account.cdc @@ -0,0 +1,41 @@ +#allowAccountLinking + +import MetadataViews from 0xMetadataViews + +import HybridCustody from 0xHybridCustody + +/// This transaction configures an OwnedAccount in the signer if needed and configures its Capabilities per +/// HybridCustody's intended design. If Display values are specified (as recommended), they will be set on the +/// signer's OwnedAccount. +/// +transaction(name: String?, desc: String?, thumbnailURL: String?) { + prepare(acct: AuthAccount) { + var acctCap = acct.getCapability<&AuthAccount>(HybridCustody.LinkedAccountPrivatePath) + if !acctCap.check() { + acctCap = acct.linkAccount(HybridCustody.LinkedAccountPrivatePath)! + } + + if acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) == nil { + let ownedAccount <- HybridCustody.createOwnedAccount(acct: acctCap) + acct.save(<-ownedAccount, to: HybridCustody.OwnedAccountStoragePath) + } + + let owned = acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) + ?? panic("owned account not found") + + // Set the display metadata for the OwnedAccount + if name != nil && desc != nil && thumbnailURL != nil { + let thumbnail = MetadataViews.HTTPFile(url: thumbnailURL!) + let display = MetadataViews.Display(name: name!, description: desc!, thumbnail: thumbnail!) + owned.setDisplay(display) + } + + // check that paths are all configured properly + acct.unlink(HybridCustody.OwnedAccountPrivatePath) + acct.link<&HybridCustody.OwnedAccount{HybridCustody.BorrowableAccount, HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPrivatePath, target: HybridCustody.OwnedAccountStoragePath) + + acct.unlink(HybridCustody.OwnedAccountPublicPath) + acct.link<&HybridCustody.OwnedAccount{HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPublicPath, target: HybridCustody.OwnedAccountStoragePath) + } +} + \ No newline at end of file diff --git a/public/transactions/linked_accounts/setup_owned_account_and_publish_to_parent.cdc b/public/transactions/linked_accounts/setup_owned_account_and_publish_to_parent.cdc new file mode 100644 index 0000000..4ec6a92 --- /dev/null +++ b/public/transactions/linked_accounts/setup_owned_account_and_publish_to_parent.cdc @@ -0,0 +1,61 @@ +#allowAccountLinking + +import MetadataViews from 0xMetadataViews + +import HybridCustody from 0xHybridCustody +import CapabilityFactory from 0xCapabilityFactory +import CapabilityFilter from 0xCapabilityFilter +import CapabilityDelegator from 0xCapabilityDelegator + +/// This transaction configures an OwnedAccount in the signer if needed, and proceeds to create a ChildAccount +/// using CapabilityFactory.Manager and CapabilityFilter.Filter Capabilities from the given addresses. A +/// Capability on the ChildAccount is then published to the specified parent account. +/// +transaction( + parent: Address, + factoryAddress: Address, + filterAddress: Address, + name: String?, + desc: String?, + thumbnailURL: String? + ) { + + prepare(acct: AuthAccount) { + // Configure OwnedAccount if it doesn't exist + if acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) == nil { + var acctCap = acct.getCapability<&AuthAccount>(HybridCustody.LinkedAccountPrivatePath) + if !acctCap.check() { + acctCap = acct.linkAccount(HybridCustody.LinkedAccountPrivatePath)! + } + let ownedAccount <- HybridCustody.createOwnedAccount(acct: acctCap) + acct.save(<-ownedAccount, to: HybridCustody.OwnedAccountStoragePath) + } + + // check that paths are all configured properly + acct.unlink(HybridCustody.OwnedAccountPrivatePath) + acct.link<&HybridCustody.OwnedAccount{HybridCustody.BorrowableAccount, HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPrivatePath, target: HybridCustody.OwnedAccountStoragePath) + + acct.unlink(HybridCustody.OwnedAccountPublicPath) + acct.link<&HybridCustody.OwnedAccount{HybridCustody.OwnedAccountPublic, MetadataViews.Resolver}>(HybridCustody.OwnedAccountPublicPath, target: HybridCustody.OwnedAccountStoragePath) + + let owned = acct.borrow<&HybridCustody.OwnedAccount>(from: HybridCustody.OwnedAccountStoragePath) + ?? panic("owned account not found") + + // Set the display metadata for the OwnedAccount + if name != nil && desc != nil && thumbnailURL != nil { + let thumbnail = MetadataViews.HTTPFile(url: thumbnailURL!) + let display = MetadataViews.Display(name: name!, description: desc!, thumbnail: thumbnail!) + owned.setDisplay(display) + } + + // Get CapabilityFactory & CapabilityFilter Capabilities + let factory = getAccount(factoryAddress).getCapability<&CapabilityFactory.Manager{CapabilityFactory.Getter}>(CapabilityFactory.PublicPath) + assert(factory.check(), message: "factory address is not configured properly") + + let filter = getAccount(filterAddress).getCapability<&{CapabilityFilter.Filter}>(CapabilityFilter.PublicPath) + assert(filter.check(), message: "capability filter is not configured properly") + + // Finally publish a ChildAccount capability on the signing account to the specified parent + owned.publishToParent(parentAddress: parent, factory: factory, filter: filter) + } +} \ No newline at end of file diff --git a/publicConfig.js b/publicConfig.js index cb82d85..0c92f40 100644 --- a/publicConfig.js +++ b/publicConfig.js @@ -61,6 +61,9 @@ if (!linkURL) throw "Missing NEXT_PUBLIC_LINK_URL" const nftStorefrontV2Address = process.env.NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS if (!nftStorefrontV2Address) throw "Missing NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS" +const hybridCustodyAddress = process.env.NEXT_PUBLIC_HYBRIDCUSTODY_ADDRESS +if (!hybridCustodyAddress) throw "Missing NEXT_PUBLIC_HYBRIDCUSTODY_ADDRESS" + const publicConfig = { chainEnv, accessNodeAPI, @@ -77,6 +80,7 @@ const publicConfig = { flowboxAddress, accountBookmarkAddress, nftStorefrontV2Address, + hybridCustodyAddress, flownsURL, findURL, bayouURL, From f49ee99494a710944f70e8eeab76541e36aa4e5a Mon Sep 17 00:00:00 2001 From: lanford33 Date: Fri, 11 Aug 2023 17:09:05 +0800 Subject: [PATCH 02/15] feat: parent view --- .env.development | 2 +- .env.production | 2 +- components/common/Siderbar.js | 7 +- components/hybrid_custody/ParentView.js | 57 +++++ .../hybrid_custody/PublishToParentModal.js | 206 ++++++++++++++++++ .../hybrid_custody/SetupOwnedAccountModal.js | 39 +--- flow/hc_scripts.js | 4 +- flow/hc_transactions.js | 30 +++ lib/atoms.js | 5 + .../{parent => hc/owned_acct}/index.js | 46 ++-- ..._addresses.cdc => get_hc_manager_info.cdc} | 4 +- .../get_owned_account_info.cdc | 83 +++++++ .../linked_accounts/publish_to_parent.cdc | 19 ++ 13 files changed, 433 insertions(+), 71 deletions(-) create mode 100644 components/hybrid_custody/ParentView.js create mode 100644 components/hybrid_custody/PublishToParentModal.js rename pages/account/[account]/{parent => hc/owned_acct}/index.js (69%) rename public/scripts/linked_accounts/{get_parent_addresses.cdc => get_hc_manager_info.cdc} (90%) create mode 100644 public/scripts/linked_accounts/get_owned_account_info.cdc create mode 100644 public/transactions/linked_accounts/publish_to_parent.cdc diff --git a/.env.development b/.env.development index 1c5e7ff..c6f2f20 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ NEXT_PUBLIC_CHAIN_ENV=mainnet -NEXT_PUBLIC_APP_URL=https://flowview.vercel.app/ +NEXT_PUBLIC_APP_URL=http://localhost:3000/ NEXT_PUBLIC_ACCESS_NODE_API=https://floral-special-valley.flow-mainnet.quiknode.pro NEXT_PUBLIC_WALLET_DISCOVERY=https://fcl-discovery.onflow.org/authn diff --git a/.env.production b/.env.production index 4d551aa..13fe6ff 100644 --- a/.env.production +++ b/.env.production @@ -1,6 +1,6 @@ NEXT_PUBLIC_CHAIN_ENV=mainnet -NEXT_PUBLIC_APP_URL=https://flowview.vercel.app/ +NEXT_PUBLIC_APP_URL=https://flowview.app/ NEXT_PUBLIC_ACCESS_NODE_API=https://floral-special-valley.flow-mainnet.quiknode.pro NEXT_PUBLIC_WALLET_DISCOVERY=https://fcl-discovery.onflow.org/authn diff --git a/components/common/Siderbar.js b/components/common/Siderbar.js index c121aaa..d77ee6b 100644 --- a/components/common/Siderbar.js +++ b/components/common/Siderbar.js @@ -15,10 +15,9 @@ export default function Sidebar(props) { { id: "4", label: `Collection`, link: { pathname: "/account/[account]/collection", query: { account: account } } }, { id: "5", label: `Storefront`, link: { pathname: "/account/[account]/storefront", query: { account: account } } }, { id: "6", label: `Contract`, link: { pathname: "/account/[account]/contract", query: { account: account } } }, - { id: "7", label: `Linked Accts`, subItems: [ - { id: "7-0", isSubItem: true, label: "Parents", smLabel: "Parents", link: { pathname: "/account/[account]/parent", query: { account: account } } }, - { id: "7-1", isSubItem: true, label: "Child Accts", smLabel: "ChildAccts", link: { pathname: "/account/[account]/child_account", query: { account: account } } }, - { id: "7-2", isSubItem: true, label: "Owned Accts", smLabel: "OwnedAccts", link: { pathname: "/account/[account]/owned_account", query: { account: account } } }, + { id: "7", label: `Acct Linking`, subItems: [ + { id: "7-0", isSubItem: true, label: "Owned Acct", smLabel: "Owned Acct", link: { pathname: "/account/[account]/hc/owned_acct", query: { account: account } } }, + { id: "7-1", isSubItem: true, label: "Manager", smLabel: "Manager", link: { pathname: "/account/[account]/hc/manager", query: { account: account } } }, ] }, { diff --git a/components/hybrid_custody/ParentView.js b/components/hybrid_custody/ParentView.js new file mode 100644 index 0000000..2b82d4d --- /dev/null +++ b/components/hybrid_custody/ParentView.js @@ -0,0 +1,57 @@ +import { useRouter } from "next/router"; +import publicConfig from "../../publicConfig"; + +export default function ParentView(props) { + const router = useRouter() + const { parent, account, user } = props + + return ( +
+
+ { + !parent.isClaimed ? +
+ +
+ :
+ +
+ } + + + +
+ + +
+ + ) +} \ No newline at end of file diff --git a/components/hybrid_custody/PublishToParentModal.js b/components/hybrid_custody/PublishToParentModal.js new file mode 100644 index 0000000..ce7989a --- /dev/null +++ b/components/hybrid_custody/PublishToParentModal.js @@ -0,0 +1,206 @@ +import { Fragment, useEffect, useRef, useState } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { useRecoilState } from "recoil" +import { showPublishToParentState, transactionStatusState, transactionInProgressState } from '../../lib/atoms' +import * as fcl from "@onflow/fcl"; +import { isValidFlowAddress, isValidPositiveFlowDecimals, isValidPositiveNumber, isValidUrl } from '../../lib/utils' +import { useRouter } from 'next/router' +import { useSWRConfig } from 'swr' +import { publishToParent, setupOwnedAccount, setupOwnedAccountAndPublishToParent } from '../../flow/hc_transactions'; + +export default function PublishToParentModal(props) { + const router = useRouter() + const { mutate } = useSWRConfig() + const {account} = props + const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) + const [, setTransactionStatus] = useRecoilState(transactionStatusState) + + const [showPublishToParent, setShowPublishToParent] = useRecoilState(showPublishToParentState) + + const [parent, setParent] = useState("") + const [parentError, setParentError] = useState(null) + const [factory, setFactory] = useState("") + const [factoryError, setFactoryError] = useState(null) + const [filter, setFilter] = useState("") + const [filterError, setFilterError] = useState(null) + + useEffect(() => fcl.currentUser.subscribe(setUser), []) + const [user, setUser] = useState({ loggedIn: null }) + + const cancelButtonRef = useRef(null) + + return ( + + + +
+ + +
+
+ + +
+
+ + {"Setup Owned Account"} + +
+
+ +
+ { + setParentError(null) + setParent("") + if (e.target.value === "") { + return + } + + if (!isValidFlowAddress(e.target.value)) { + setParentError("Invalid address") + return + } + setParent(e.target.value) + }} + /> +
+ { + parentError ? + : null + } +
+
+ +
+ { + setFactoryError(null) + setFactory("") + if (e.target.value === "") { + return + } + + if (!isValidFlowAddress(e.target.value)) { + setFactoryError("Invalid address") + return + } + setFactory(e.target.value) + }} + /> +
+ { + factoryError ? + : null + } +
+
+ +
+ { + setFilterError(null) + setFilter("") + if (e.target.value === "") { + return + } + + if (!isValidFlowAddress(e.target.value)) { + setFilterError("Invalid address") + return + } + setFilter(e.target.value) + }} + /> +
+ { + filterError ? + : null + } +
+
+
+
+
+ + +
+
+
+
+
+
+
+ ) +} diff --git a/components/hybrid_custody/SetupOwnedAccountModal.js b/components/hybrid_custody/SetupOwnedAccountModal.js index 082ff52..a45c1c9 100644 --- a/components/hybrid_custody/SetupOwnedAccountModal.js +++ b/components/hybrid_custody/SetupOwnedAccountModal.js @@ -11,7 +11,7 @@ import { setupOwnedAccount, setupOwnedAccountAndPublishToParent } from '../../fl export default function SetupOwnedAccountModal(props) { const router = useRouter() const { mutate } = useSWRConfig() - const {account} = props + const { account } = props const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) const [, setTransactionStatus] = useRecoilState(transactionStatusState) @@ -60,41 +60,6 @@ export default function SetupOwnedAccountModal(props) { {"Setup Owned Account"}
- {/*
- -
- { - setParentError(null) - setParent("") - if (e.target.value === "") { - return - } - - if (!isValidFlowAddress(e.target.value)) { - setParentError("Invalid address") - return - } - setParent(e.target.value) - }} - /> -
- { - parentError ? - : null - } -
*/}