Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 hook to move NFT from evm to cadence
63 changes: 63 additions & 0 deletions packages/demo/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,66 @@ body {
height: 100vh;
background-color: #ffffff;
}

/* Custom scrollbar styles - Light mode (default) */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}

::-webkit-scrollbar-track {
background: #ffffff;
border-radius: 8px;
}

::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 8px;
border: 2px solid #ffffff;
transition: background-color 0.2s ease-in-out;
}

::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}

::-webkit-scrollbar-thumb:active {
background: #6b7280;
}

::-webkit-scrollbar-corner {
background: #ffffff;
}

/* Firefox scrollbar styles - Light mode (default) */
html {
scrollbar-width: thin;
scrollbar-color: #d1d5db #ffffff;
}

/* Class-based dark mode support (theme toggle) */
html.dark ::-webkit-scrollbar-track {
background: #374151 !important;
}

html.dark ::-webkit-scrollbar-thumb {
background: #6b7280 !important;
border: 2px solid #374151 !important;
}

html.dark ::-webkit-scrollbar-thumb:hover {
background: #9ca3af !important;
}

html.dark ::-webkit-scrollbar-thumb:active {
background: #d1d5db !important;
}

html.dark ::-webkit-scrollbar-corner {
background: #374151 !important;
}

/* Firefox class-based dark mode (theme toggle) */
html.dark {
scrollbar-color: #6b7280 #374151 !important;
}
10 changes: 10 additions & 0 deletions packages/demo/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@ import FlowProviderWrapper, {
} from "./components/flow-provider-wrapper"
import {Navbar} from "./components/navbar"
import {Sidebar} from "./components/sidebar"
import {useEffect} from "react"

function AppContent() {
const {darkMode} = useDarkMode()

// Apply dark class to document element for scrollbar styling
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark")
} else {
document.documentElement.classList.remove("dark")
}
}, [darkMode])

return (
<div
className={`min-h-screen flex flex-col ${
Expand Down
147 changes: 147 additions & 0 deletions packages/demo/src/components/cards/cross-vm-receive-nft-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {useCrossVmReceiveNft, useFlowConfig} from "@onflow/react-sdk"
import {useState, useMemo} from "react"
import {getContractAddress} from "../../constants"

export function CrossVmReceiveNftCard() {
const config = useFlowConfig()
const currentNetwork = config.flowNetwork || "emulator"
const [nftIdentifier, setNftIdentifier] = useState("")
const [nftId, setNftId] = useState("1") // NFT ID to bridge

const {
receiveNft,
isPending,
data: transactionId,
error,
} = useCrossVmReceiveNft()

const isNetworkSupported = currentNetwork === "testnet"

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
useMemo(() => {
if (exampleNftData && !nftIdentifier) {
setNftIdentifier(exampleNftData.nftIdentifier)
}
}, [exampleNftData, nftIdentifier])

const handleReceiveNft = () => {
receiveNft({
nftIdentifier,
nftId,
})
}

if (!isNetworkSupported) {
return (
<div className="p-4 border border-gray-200 rounded-xl bg-white mb-8">
<h2 className="text-black mt-0 mb-6 text-xl font-bold">
useCrossVmReceiveNft
</h2>
<div className="p-4 bg-yellow-100 border border-yellow-200 rounded text-yellow-800">
<p className="m-0">
<strong>Network not supported:</strong> This feature is only
available on testnet.
</p>
</div>
</div>
)
}

return (
<div className="p-4 border border-gray-200 rounded-xl bg-white mb-8">
<h2 className="text-black mt-0 mb-6 text-xl font-bold">
useCrossVmReceiveNft
</h2>
<div className="mb-6">
<label className="block mb-2 text-black">
<strong>Note:</strong> Example prefilled with ExampleNFT type
Copy link

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label text says 'Example prefilled with ExampleNFT type identifier' but should be 'Example pre-filled with ExampleNFT type identifier' (hyphenated).

Suggested change
<strong>Note:</strong> Example prefilled with ExampleNFT type
<strong>Note:</strong> Example pre-filled with ExampleNFT type

Copilot uses AI. Check for mistakes.
identifier
</label>
<label className="block mb-2 text-black font-medium">
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="p-3 border-2 border-[#00EF8B] rounded-md text-sm text-black bg-white
outline-none transition-colors duration-200 ease-in-out w-full mb-4 font-mono"
/>

<label className="block mb-2 text-black font-medium">
NFT ID (UInt256):
</label>
<input
type="text"
value={nftId}
onChange={e => setNftId(e.target.value)}
placeholder="e.g., 1"
className="p-3 border-2 border-[#00EF8B] rounded-md text-sm text-black bg-white
outline-none transition-colors duration-200 ease-in-out w-full mb-4 font-mono"
/>

<button
onClick={handleReceiveNft}
className={`py-3 px-6 text-base font-semibold rounded-md transition-all duration-200
ease-in-out mr-4 ${
isPending || !nftIdentifier || !nftId
? "bg-gray-300 text-gray-500 cursor-not-allowed"
: "bg-[#00EF8B] text-black cursor-pointer"
}`}
disabled={isPending || !nftIdentifier || !nftId}
>
{isPending ? "Receiving..." : "Receive NFT"}
</button>
</div>

<div className="p-4 bg-[#f8f9fa] rounded-md border border-[#00EF8B]">
<h4 className="text-black m-0 mb-4">Transaction Status:</h4>

{isPending && (
<p className="text-gray-500 m-0">Receiving NFT from EVM...</p>
)}

{error && (
<div className="p-4 bg-red-100 border border-red-200 rounded text-red-800 m-0">
<strong>Error:</strong> {error.message}
</div>
)}

{transactionId && !isPending && !error && (
<div className="p-4 bg-green-100 border border-green-200 rounded m-0">
<p className="text-green-800 m-0 mb-2">
<strong>NFT received successfully!</strong>
</p>
<p className="text-green-800 m-0 font-mono">
<strong>Transaction ID:</strong> {transactionId}
</p>
</div>
)}

{!transactionId && !isPending && !error && (
<div className="text-gray-500 m-0">
<p className="mb-2">
Click "Receive NFT" to bridge an NFT from EVM to Cadence
</p>
</div>
)}
</div>
</div>
)
}
146 changes: 146 additions & 0 deletions packages/demo/src/components/cards/cross-vm-spend-nft-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {useCrossVmSpendNft, useFlowConfig} from "@onflow/react-sdk"
import {useState, useMemo} from "react"
import {getContractAddress} from "../../constants"

export function CrossVmSpendNftCard() {
const config = useFlowConfig()
const currentNetwork = config.flowNetwork || "emulator"
const [nftIdentifier, setNftIdentifier] = useState("")
const [nftIds, setNftIds] = useState("1") // NFT IDs to bridge (comma-separated)

const {spendNft, isPending, data: transactionId, error} = useCrossVmSpendNft()

const isNetworkSupported = currentNetwork === "testnet"

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
useMemo(() => {
if (exampleNftData && !nftIdentifier) {
setNftIdentifier(exampleNftData.nftIdentifier)
setNftIds(exampleNftData.nftIds)
}
}, [exampleNftData, nftIdentifier])

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

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

if (!isNetworkSupported) {
return (
<div className="p-4 border border-gray-200 rounded-xl bg-white mb-8">
<h2 className="text-black mt-0 mb-6 text-xl font-bold">
useCrossVmSpendNft
</h2>
<div className="p-4 bg-yellow-100 border border-yellow-200 rounded text-yellow-800">
<p className="m-0">
<strong>Network not supported:</strong> This feature is only
available on testnet.
</p>
</div>
</div>
)
}

return (
<div className="p-4 border border-gray-200 rounded-xl bg-white mb-8">
<h2 className="text-black mt-0 mb-6 text-xl font-bold">
useCrossVmSpendNft
</h2>
<div className="mb-6">
<label className="block mb-2 text-black">
<strong>Note:</strong> Bridge NFTs from Cadence to EVM
</label>

<label className="block mb-2 text-black font-medium">
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="p-3 border-2 border-[#00EF8B] rounded-md text-sm text-black bg-white
outline-none transition-colors duration-200 ease-in-out w-full mb-4 font-mono"
/>

<label className="block mb-2 text-black font-medium">
NFT IDs (UInt64, comma-separated):
</label>
<input
type="text"
value={nftIds}
onChange={e => setNftIds(e.target.value)}
placeholder="e.g., 1,2,3"
className="p-3 border-2 border-[#00EF8B] rounded-md text-sm text-black bg-white
outline-none transition-colors duration-200 ease-in-out w-full mb-4 font-mono"
/>

<button
onClick={handleSpendNft}
className={`py-3 px-6 text-base font-semibold rounded-md transition-all duration-200
ease-in-out mr-4 ${
isPending || !nftIdentifier || !nftIds
? "bg-gray-300 text-gray-500 cursor-not-allowed"
: "bg-[#00EF8B] text-black cursor-pointer"
}`}
disabled={isPending || !nftIdentifier || !nftIds}
>
{isPending ? "Spending..." : "Spend NFT"}
</button>
</div>

<div className="p-4 bg-[#f8f9fa] rounded-md border border-[#00EF8B]">
<h4 className="text-black m-0 mb-4">Transaction Status:</h4>

{isPending && (
<p className="text-gray-500 m-0">Bridging NFTs to EVM...</p>
)}

{error && (
<div className="p-4 bg-red-100 border border-red-200 rounded text-red-800 m-0">
<strong>Error:</strong> {error.message}
</div>
)}

{transactionId && !isPending && !error && (
<div className="p-4 bg-green-100 border border-green-200 rounded m-0">
<p className="text-green-800 m-0 mb-2">
<strong>NFTs spent successfully!</strong>
</p>
<p className="text-green-800 m-0 font-mono">
<strong>Transaction ID:</strong> {transactionId}
</p>
</div>
)}

{!transactionId && !isPending && !error && (
<div className="text-gray-500 m-0">
<p className="mb-2">
Click "Spend NFT" to bridge NFTs from Cadence to EVM
</p>
</div>
)}
</div>
</div>
)
}
Loading