Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/mighty-rocks-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@onflow/react-sdk": minor
"@onflow/demo": minor
---

Added `useCrossVmBridgeNftFromEvm` hook for bridging NFTs from Flow EVM to Cadence. This hook withdraws an NFT from the signer's Cadence-Owned Account (COA) in EVM and deposits it into their Cadence collection, automatically configuring the collection if needed.
4 changes: 4 additions & 0 deletions packages/demo/src/components/content-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {UseFlowMutateCard} from "./hook-cards/use-flow-mutate-card"
import {UseFlowEventsCard} from "./hook-cards/use-flow-events-card"
import {UseFlowTransactionStatusCard} from "./hook-cards/use-flow-transaction-status-card"
import {UseFlowRevertibleRandomCard} from "./hook-cards/use-flow-revertible-random-card"
import {UseCrossVmBridgeNftFromEvmCard} from "./hook-cards/use-cross-vm-bridge-nft-from-evm-card"
import {UseCrossVmBridgeNftToEvmCard} from "./hook-cards/use-cross-vm-bridge-nft-to-evm-card"
import {UseCrossVmBridgeTokenFromEvmCard} from "./hook-cards/use-cross-vm-bridge-token-from-evm-card"
import {UseCrossVmBridgeTokenToEvmCard} from "./hook-cards/use-cross-vm-bridge-token-to-evm-card"
import {UseFlowNftMetadataCard} from "./hook-cards/use-flow-nft-metadata-card"
Expand Down Expand Up @@ -88,6 +90,8 @@ export function ContentSection() {
<UseFlowEventsCard />
<UseFlowRevertibleRandomCard />
<UseFlowTransactionStatusCard />
<UseCrossVmBridgeNftFromEvmCard />
<UseCrossVmBridgeNftToEvmCard />
<UseCrossVmBridgeTokenFromEvmCard />
<UseCrossVmBridgeTokenToEvmCard />
<UseFlowNftMetadataCard />
Expand Down
12 changes: 12 additions & 0 deletions packages/demo/src/components/content-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ const sidebarItems: SidebarItem[] = [
category: "hooks",
description: "Track transaction status",
},
{
id: "usecrossvmbridgenftfromevm",
label: "Bridge NFT from EVM",
category: "hooks",
description: "Bridge NFTs from EVM to Cadence",
},
{
id: "usecrossvmbridgenfttoevm",
label: "Bridge NFT to EVM",
category: "hooks",
description: "Bridge NFTs from Cadence to EVM",
},
{
id: "usecrossvmbridgetokenfromevm",
label: "Bridge Token from EVM",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {useCrossVmBridgeNftFromEvm, useFlowConfig} from "@onflow/react-sdk"
import {useState, useMemo} from "react"
import {useDarkMode} from "../flow-provider-wrapper"
import {DemoCard} from "../ui/demo-card"
import {ResultsSection} from "../ui/results-section"
import {getContractAddress} from "../../constants"
import {PlusGridIcon} from "../ui/plus-grid"

const IMPLEMENTATION_CODE = `import { useCrossVmBridgeNftFromEvm } from "@onflow/react-sdk"

const {
crossVmBridgeNftFromEvm,
isPending,
error,
data: txId
} = useCrossVmBridgeNftFromEvm()

crossVmBridgeNftFromEvm({
nftIdentifier: "A.dfc20aee650fcbdf.ExampleNFT.NFT",
nftId: "1"
})`

export function UseCrossVmBridgeNftFromEvmCard() {
const {darkMode} = useDarkMode()
const config = useFlowConfig()
const currentNetwork = config.flowNetwork || "emulator"
const [nftIdentifier, setNftIdentifier] = useState("")
const [nftId, setNftId] = useState("1")

const {
crossVmBridgeNftFromEvm,
isPending,
data: transactionId,
error,
} = useCrossVmBridgeNftFromEvm()

const exampleNftData = useMemo(() => {
if (currentNetwork !== "testnet") return null

const exampleNftAddress = getContractAddress("ExampleNFT", currentNetwork)
return {
name: "Example NFT",
nftIdentifier: `A.${exampleNftAddress.replace("0x", "")}.ExampleNFT.NFT`,
nftId: "1",
}
}, [currentNetwork])

// Set default NFT identifier when network changes to testnet
useMemo(() => {
if (exampleNftData && !nftIdentifier) {
setNftIdentifier(exampleNftData.nftIdentifier)
}
}, [exampleNftData, nftIdentifier])

const handleBridgeNft = () => {
crossVmBridgeNftFromEvm({
nftIdentifier,
nftId,
})
}

return (
<DemoCard
id="usecrossvmbridgenftfromevm"
title="useCrossVmBridgeNftFromEvm"
description="Bridge NFTs from Flow EVM to Cadence by withdrawing from the signer's Cadence-Owned Account (COA) in EVM."
code={IMPLEMENTATION_CODE}
docsUrl="https://developers.flow.com/build/tools/react-sdk/hooks#usecrossvmbridgenftfromevm"
>
<div className="space-y-6">
{exampleNftData && (
<div
className={`relative p-4 rounded-lg border ${
darkMode
? "bg-blue-500/10 border-blue-500/20"
: "bg-blue-50 border-blue-200"
}`}
>
<PlusGridIcon placement="top left" className="absolute" />
<p
className={`text-sm ${darkMode ? "text-blue-300" : "text-blue-800"}`}
>
<strong>Note:</strong> Example prefilled with ExampleNFT type
identifier for testnet
</p>
</div>
)}

<div className="space-y-3">
<label
className={`block text-sm font-medium ${darkMode ? "text-gray-300" : "text-gray-700"}`}
>
NFT Identifier
</label>
<input
type="text"
value={nftIdentifier}
onChange={e => setNftIdentifier(e.target.value)}
placeholder={
exampleNftData
? exampleNftData.nftIdentifier
: "e.g., A.dfc20aee650fcbdf.ExampleNFT.NFT"
}
className={`w-full px-4 py-3 rounded-lg border font-mono text-sm transition-all duration-200
${
darkMode
? `bg-gray-900/50 border-white/10 text-white placeholder-gray-500
focus:border-flow-primary/50`
: `bg-white border-black/10 text-black placeholder-gray-400
focus:border-flow-primary/50`
} outline-none`}
/>
</div>

<div className="space-y-3">
<label
className={`block text-sm font-medium ${darkMode ? "text-gray-300" : "text-gray-700"}`}
>
NFT ID (UInt256)
</label>
<input
type="text"
value={nftId}
onChange={e => setNftId(e.target.value)}
placeholder="e.g., 1"
className={`w-full px-4 py-3 rounded-lg border font-mono text-sm transition-all duration-200
${
darkMode
? `bg-gray-900/50 border-white/10 text-white placeholder-gray-500
focus:border-flow-primary/50`
: `bg-white border-black/10 text-black placeholder-gray-400
focus:border-flow-primary/50`
} outline-none`}
/>
</div>

<div className="flex justify-start">
<button
onClick={handleBridgeNft}
disabled={isPending || !nftIdentifier || !nftId}
className={`px-6 py-3 rounded-lg font-medium transition-all duration-200 ${
isPending || !nftIdentifier || !nftId
? "bg-gray-300 text-gray-500 cursor-not-allowed"
: "bg-flow-primary text-black hover:bg-flow-primary/80"
}`}
>
{isPending ? "Bridging..." : "Bridge NFT from EVM"}
</button>
</div>

<ResultsSection
data={transactionId || error}
darkMode={darkMode}
show={!!transactionId || !!error}
title={
transactionId
? "NFT bridged successfully!"
: error
? "Bridge failed"
: undefined
}
/>
</div>
</DemoCard>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {useCrossVmBridgeNftToEvm, useFlowConfig} from "@onflow/react-sdk"
import {useState, useMemo} from "react"
import {useDarkMode} from "../flow-provider-wrapper"
import {DemoCard} from "../ui/demo-card"
import {ResultsSection} from "../ui/results-section"
import {getContractAddress} from "../../constants"
import {PlusGridIcon} from "../ui/plus-grid"

const IMPLEMENTATION_CODE = `import { useCrossVmBridgeNftToEvm } from "@onflow/react-sdk"

const {
crossVmBridgeNftToEvm,
isPending,
error,
data: txId
} = useCrossVmBridgeNftToEvm()

crossVmBridgeNftToEvm({
nftIdentifier: "A.012e4d204a60ac6f.ExampleNFT.NFT",
nftIds: ["1", "2", "3"],
calls: []
})`

export function UseCrossVmBridgeNftToEvmCard() {
const {darkMode} = useDarkMode()
const config = useFlowConfig()
const currentNetwork = config.flowNetwork || "emulator"
const [nftIdentifier, setNftIdentifier] = useState("")
const [nftIds, setNftIds] = useState("1")

const {
crossVmBridgeNftToEvm,
isPending,
data: transactionId,
error,
} = useCrossVmBridgeNftToEvm()

const exampleNftData = useMemo(() => {
if (currentNetwork !== "testnet") return null

const exampleNftAddress = getContractAddress("ExampleNFT", currentNetwork)
return {
name: "Example NFT",
nftIdentifier: `A.${exampleNftAddress.replace("0x", "")}.ExampleNFT.NFT`,
nftIds: "1",
}
}, [currentNetwork])

// Set default NFT identifier when network changes to testnet
useMemo(() => {
if (exampleNftData && !nftIdentifier) {
setNftIdentifier(exampleNftData.nftIdentifier)
setNftIds(exampleNftData.nftIds)
}
}, [exampleNftData, nftIdentifier])

const handleBridgeNft = () => {
const nftIdArray = nftIds.split(",").map(id => id.trim())

crossVmBridgeNftToEvm({
nftIdentifier,
nftIds: nftIdArray,
calls: [], // No EVM calls, just bridging
})
}

return (
<DemoCard
id="usecrossvmbridgenfttoevm"
title="useCrossVmBridgeNftToEvm"
description="Bridge NFTs from Cadence to Flow EVM by depositing them into the signer's Cadence-Owned Account (COA)."
code={IMPLEMENTATION_CODE}
docsUrl="https://developers.flow.com/build/tools/react-sdk/hooks#usecrossvmbridgenfttoevm"
>
<div className="space-y-6">
{exampleNftData && (
<div
className={`relative p-4 rounded-lg border ${
darkMode
? "bg-blue-500/10 border-blue-500/20"
: "bg-blue-50 border-blue-200"
}`}
>
<PlusGridIcon placement="top right" className="absolute" />
<p
className={`text-sm ${darkMode ? "text-blue-300" : "text-blue-800"}`}
>
<strong>Note:</strong> Example prefilled with ExampleNFT type
identifier for testnet
</p>
</div>
)}

<div className="space-y-3">
<label
className={`block text-sm font-medium ${darkMode ? "text-gray-300" : "text-gray-700"}`}
>
NFT Identifier
</label>
<input
type="text"
value={nftIdentifier}
onChange={e => setNftIdentifier(e.target.value)}
placeholder={
exampleNftData
? exampleNftData.nftIdentifier
: "e.g., A.012e4d204a60ac6f.ExampleNFT.NFT"
}
className={`w-full px-4 py-3 rounded-lg border font-mono text-sm transition-all duration-200
${
darkMode
? `bg-gray-900/50 border-white/10 text-white placeholder-gray-500
focus:border-flow-primary/50`
: `bg-white border-black/10 text-black placeholder-gray-400
focus:border-flow-primary/50`
} outline-none`}
/>
</div>

<div className="space-y-3">
<label
className={`block text-sm font-medium ${darkMode ? "text-gray-300" : "text-gray-700"}`}
>
NFT IDs (UInt64, comma-separated)
</label>
<input
type="text"
value={nftIds}
onChange={e => setNftIds(e.target.value)}
placeholder="e.g., 1,2,3"
className={`w-full px-4 py-3 rounded-lg border font-mono text-sm transition-all duration-200
${
darkMode
? `bg-gray-900/50 border-white/10 text-white placeholder-gray-500
focus:border-flow-primary/50`
: `bg-white border-black/10 text-black placeholder-gray-400
focus:border-flow-primary/50`
} outline-none`}
/>
</div>

<div className="flex justify-start">
<button
onClick={handleBridgeNft}
disabled={isPending || !nftIdentifier || !nftIds}
className={`px-6 py-3 rounded-lg font-medium transition-all duration-200 ${
isPending || !nftIdentifier || !nftIds
? "bg-gray-300 text-gray-500 cursor-not-allowed"
: "bg-flow-primary text-black hover:bg-flow-primary/80"
}`}
>
{isPending ? "Bridging..." : "Bridge NFT to EVM"}
</button>
</div>

<ResultsSection
data={transactionId || error}
darkMode={darkMode}
show={!!transactionId || !!error}
title={
transactionId
? "NFTs bridged successfully!"
: error
? "Bridge failed"
: undefined
}
/>
</div>
</DemoCard>
)
}
3 changes: 3 additions & 0 deletions packages/demo/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const CONTRACT_ADDRESSES: Record<string, Record<any, string>> = {
testnet: "0x7e60df042a9c0868",
mainnet: "0x1654653399040a61",
},
ExampleNFT: {
testnet: "0x012e4d204a60ac6f",
},
ClickToken: {
testnet: "0xdfc20aee650fcbdf",
},
Expand Down
Loading