Compare commits

...

3 Commits

Author SHA1 Message Date
Derrick Hammer e3b014db80
fix: update footer copy 2023-10-30 23:35:29 -04:00
Derrick Hammer b1bb32b921
fix: update copy 2023-10-30 23:31:39 -04:00
Derrick Hammer fca81d8ead
fix: update placeholder 2023-10-30 23:29:52 -04:00
2 changed files with 154 additions and 152 deletions

View File

@ -1,15 +1,15 @@
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import "@lumeweb/sdk/lib/style.css"
import "@/styles/global.css"
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import "@lumeweb/sdk/lib/style.css";
import "@/styles/global.css";
import React, {
createContext,
createRef,
type ReactNode,
useContext,
useEffect,
useState
} from "react"
useState,
} from "react";
import {
type AuthContextType,
AuthProvider,
@ -22,53 +22,49 @@ import {
useAuth,
useLumeStatus,
useNetworks,
LumeDashboardTrigger
} from "@lumeweb/sdk"
import * as kernel from "@lumeweb/libkernel/kernel"
import { kernelLoaded } from "@lumeweb/libkernel/kernel"
LumeDashboardTrigger,
} from "@lumeweb/sdk";
import * as kernel from "@lumeweb/libkernel/kernel";
import { kernelLoaded } from "@lumeweb/libkernel/kernel";
import {
dnsClient,
ethClient,
ipfsClient,
networkRegistryClient,
peerDiscoveryClient,
swarmClient
} from "@/lib/clients"
import { ethers } from "ethers"
import * as ethersBytes from "@ethersproject/bytes"
import { createProvider } from "@lumeweb/kernel-eth-client"
swarmClient,
} from "@/lib/clients";
import { ethers } from "ethers";
import * as ethersBytes from "@ethersproject/bytes";
import { createProvider } from "@lumeweb/kernel-eth-client";
// @ts-ignore
import jdu from "json-data-uri"
import { ERC721_ABI } from "@/lib/erc721-abi"
import {
Card,
CardContent,
CardHeader,
} from "@/components/ui/card"
import LogoImg from "@/assets/lume-logo.png"
let BOOT_FUNCTIONS: (() => Promise<any>)[] = []
import jdu from "json-data-uri";
import { ERC721_ABI } from "@/lib/erc721-abi";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import LogoImg from "@/assets/lume-logo.png";
let BOOT_FUNCTIONS: (() => Promise<any>)[] = [];
export const AppContext = createContext<any>(undefined)
export const AppContext = createContext<any>(undefined);
export function useApp() {
const context = useContext(AppContext)
const context = useContext(AppContext);
if (!context) {
throw new Error("useApp must be used within an AppProvider")
throw new Error("useApp must be used within an AppProvider");
}
return context
return context;
}
interface AppProviderProps {
children: ReactNode
children: ReactNode;
}
const provider = createProvider()
const provider = createProvider();
const ERC721_TRANSFER_EVENT_SIGNATURE = ethers.id(
"Transfer(address,address,uint256)"
)
);
async function findPotentialERC721Contracts(
address: string
@ -79,24 +75,24 @@ async function findPotentialERC721Contracts(
topics: [
ERC721_TRANSFER_EVENT_SIGNATURE,
null,
ethersBytes.hexZeroPad(address, 32)
]
})
ethersBytes.hexZeroPad(address, 32),
],
});
const potentialContracts = new Set<string>()
logs.forEach((log: any) => potentialContracts.add(log.address))
const potentialContracts = new Set<string>();
logs.forEach((log: any) => potentialContracts.add(log.address));
const confirmedERC721Contracts: string[] = []
const confirmedERC721Contracts: string[] = [];
for (let contractAddress of potentialContracts) {
if (await isERC721(contractAddress)) {
confirmedERC721Contracts.push(contractAddress)
confirmedERC721Contracts.push(contractAddress);
}
}
return confirmedERC721Contracts
return confirmedERC721Contracts;
}
const TRANSFER_EVENT_SIGNATURE = ethers.id("Transfer(address,address,uint256)")
const TRANSFER_EVENT_SIGNATURE = ethers.id("Transfer(address,address,uint256)");
async function fetchTokensViaTransferEvent(
address: string,
@ -109,93 +105,93 @@ async function fetchTokensViaTransferEvent(
topics: [
TRANSFER_EVENT_SIGNATURE,
null,
ethersBytes.hexZeroPad(address, 32)
]
})
ethersBytes.hexZeroPad(address, 32),
],
});
const tokenIds: number[] = []
const tokenIds: number[] = [];
logs.forEach((log) => {
if (log.topics && log.topics.length === 4) {
const tokenIdBigNumber = ethers.toNumber(log.topics[3])
tokenIds.push(tokenIdBigNumber)
const tokenIdBigNumber = ethers.toNumber(log.topics[3]);
tokenIds.push(tokenIdBigNumber);
}
})
});
return tokenIds
return tokenIds;
}
async function fetchOwnedNFTs(
address: string,
confirmedERC721Contracts: string[]
): Promise<{ contract: string; tokenId: number; metadata: any }[]> {
const ownedNFTs = []
const ownedNFTs = [];
for (let contractAddress of confirmedERC721Contracts) {
const contract = new ethers.Contract(contractAddress, ERC721_ABI, provider)
let tokenIds: number[] = []
const contract = new ethers.Contract(contractAddress, ERC721_ABI, provider);
let tokenIds: number[] = [];
try {
const balance = await contract.balanceOf(address)
const balance = await contract.balanceOf(address);
for (let i = 0; i < balance; i++) {
const tokenId = await contract.tokenOfOwnerByIndex(address, i)
tokenIds.push(tokenId.toNumber())
const tokenId = await contract.tokenOfOwnerByIndex(address, i);
tokenIds.push(tokenId.toNumber());
}
} catch (error) {
// If tokenOfOwnerByIndex is not available, fall back to fetchTokensViaTransferEvent
tokenIds = await fetchTokensViaTransferEvent(address, contractAddress)
tokenIds = await fetchTokensViaTransferEvent(address, contractAddress);
}
for (let tokenId of tokenIds) {
try {
const uri = await contract.tokenURI(tokenId)
const uri = await contract.tokenURI(tokenId);
// const metadata = await fetchMetadataFromURI(uri);
ownedNFTs.push({
contract: contractAddress,
tokenId: tokenId,
metadata: uri
})
metadata: uri,
});
} catch (error: any) {
console.error(
`Failed to fetch metadata for token ${tokenId} from contract ${contractAddress}: ${error.message}`
)
);
}
}
}
return ownedNFTs
return ownedNFTs;
}
async function isERC721(address: string): Promise<boolean> {
const contract = new ethers.Contract(address, ERC721_ABI, provider)
const contract = new ethers.Contract(address, ERC721_ABI, provider);
try {
// Try calling some ERC-721 methods to confirm if this is an ERC-721 contract.
await contract.name()
await contract.symbol()
return true
await contract.name();
await contract.symbol();
return true;
} catch (error) {
return false
return false;
}
}
const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
return <AppContext.Provider value={{}}>{children}</AppContext.Provider>
}
return <AppContext.Provider value={{}}>{children}</AppContext.Provider>;
};
async function boot(status: LumeStatusContextType, auth: AuthContextType) {
kernel.init().then(() => {
status.setInited(true)
})
status.setInited(true);
});
await kernelLoaded()
await kernelLoaded();
auth.setIsLoggedIn(true)
auth.setIsLoggedIn(true);
BOOT_FUNCTIONS.push(
async () =>
await swarmClient.addRelay(
"2d7ae1517caf4aae4de73c6d6f400765d2dd00b69d65277a29151437ef1c7d1d"
)
)
);
// IRC
BOOT_FUNCTIONS.push(
@ -203,40 +199,40 @@ async function boot(status: LumeStatusContextType, auth: AuthContextType) {
await peerDiscoveryClient.register(
"zrjHTx8tSQFWnmZ9JzK7XmJirqJQi2WRBLYp3fASaL2AfBQ"
)
)
);
BOOT_FUNCTIONS.push(
async () => await networkRegistryClient.registerType("content")
)
);
BOOT_FUNCTIONS.push(
async () => await networkRegistryClient.registerType("blockchain")
)
BOOT_FUNCTIONS.push(async () => await ethClient.register())
BOOT_FUNCTIONS.push(async () => await ipfsClient.register())
);
BOOT_FUNCTIONS.push(async () => await ethClient.register());
BOOT_FUNCTIONS.push(async () => await ipfsClient.register());
const resolvers = [
"zrjEYq154PS7boERAbRAKMyRGzAR6CTHVRG6mfi5FV4q9FA" // ENS
]
"zrjEYq154PS7boERAbRAKMyRGzAR6CTHVRG6mfi5FV4q9FA", // ENS
];
for (const resolver of resolvers) {
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver))
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver));
}
BOOT_FUNCTIONS.push(async () => status.setReady(true))
BOOT_FUNCTIONS.push(async () => status.setReady(true));
await bootup()
await bootup();
await Promise.all([ethClient.ready(), ipfsClient.ready()])
await Promise.all([ethClient.ready(), ipfsClient.ready()]);
}
async function bootup() {
for (const entry of Object.entries(BOOT_FUNCTIONS)) {
console.log(entry[1].toString())
await entry[1]()
console.log(entry[1].toString());
await entry[1]();
}
}
function LoginDash() {
const { isLoggedIn } = useAuth()
const { ready, inited } = useLumeStatus()
const { isLoggedIn } = useAuth();
const { ready, inited } = useLumeStatus();
return (
<>
@ -259,130 +255,130 @@ function LoginDash() {
</LumeDashboard>
)}
</>
)
);
}
async function asyncIterableToUint8Array(asyncIterable: any) {
const chunks = []
let totalLength = 0
const chunks = [];
let totalLength = 0;
for await (const chunk of asyncIterable) {
chunks.push(chunk)
totalLength += chunk.length
chunks.push(chunk);
totalLength += chunk.length;
}
const result = new Uint8Array(totalLength)
let offset = 0
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset)
offset += chunk.length
result.set(chunk, offset);
offset += chunk.length;
}
return result
return result;
}
function uint8ArrayToBase64(byteArray: Uint8Array) {
let base64 = ""
let base64 = "";
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let padding = 0
let padding = 0;
for (let i = 0; i < byteArray.length; i += 3) {
const a = byteArray[i]
const b = byteArray[i + 1]
const c = byteArray[i + 2]
const a = byteArray[i];
const b = byteArray[i + 1];
const c = byteArray[i + 2];
const triplet = (a << 16) + ((b || 0) << 8) + (c || 0)
const triplet = (a << 16) + ((b || 0) << 8) + (c || 0);
base64 += characters.charAt((triplet & 0xfc0000) >> 18)
base64 += characters.charAt((triplet & 0x03f000) >> 12)
base64 += characters.charAt((triplet & 0x000fc0) >> 6)
base64 += characters.charAt(triplet & 0x00003f)
base64 += characters.charAt((triplet & 0xfc0000) >> 18);
base64 += characters.charAt((triplet & 0x03f000) >> 12);
base64 += characters.charAt((triplet & 0x000fc0) >> 6);
base64 += characters.charAt(triplet & 0x00003f);
if (byteArray.length - i < 3) {
padding = 3 - (byteArray.length - i)
padding = 3 - (byteArray.length - i);
}
}
// Add padding if necessary
if (padding > 0) {
base64 = base64.slice(0, -padding) + (padding === 1 ? "=" : "==")
base64 = base64.slice(0, -padding) + (padding === 1 ? "=" : "==");
}
return base64
return base64;
}
function App() {
const status = useLumeStatus()
const auth = useAuth()
const [nftList, setNftList] = useState<any[]>([])
const status = useLumeStatus();
const auth = useAuth();
const [nftList, setNftList] = useState<any[]>([]);
useEffect(() => {
boot(status, auth)
}, [])
boot(status, auth);
}, []);
const { networks } = useNetworks()
const { networks } = useNetworks();
const ipfsStatus = networks
.filter((item) => item.name.toLowerCase() === "ipfs")
?.pop()
.filter((item) => item.name.toLowerCase() === "ipfs")
?.pop();
const ethStatus = networks
.filter((item) => item.name.toLowerCase() === "ethereum")
?.pop()
?.pop();
const ready = ethStatus?.ready && status.ready
const ready = ethStatus?.ready && status.ready;
const inputRef = createRef<HTMLInputElement>()
const inputRef = createRef<HTMLInputElement>();
async function search(e: any | Event) {
e.preventDefault()
e.preventDefault();
let address = inputRef?.current?.value as string
let address = inputRef?.current?.value as string;
address = await ethers.resolveAddress(address, provider)
address = await ethers.resolveAddress(address, provider);
const contracts = await findPotentialERC721Contracts(address)
const contracts = await findPotentialERC721Contracts(address);
const nfts = await fetchOwnedNFTs(address, contracts)
const nfts = await fetchOwnedNFTs(address, contracts);
const list = []
const list = [];
for (const nft of nfts) {
let meta
let meta;
if (typeof nft.metadata === "string") {
try {
meta = await (await fetch(nft.metadata)).json()
meta = await (await fetch(nft.metadata)).json();
} catch (e) {
meta = {
image: "", // TODO: Improve this by bringing an actual image
name: "Failed to Load",
fail: true
}
fail: true,
};
}
} else {
meta = jdu.parse(nft.metadata)
meta = jdu.parse(nft.metadata);
}
let image
let image;
if (!meta.fail) {
const imageCID = meta.image.replace("ipfs://", "")
const imageCID = meta.image.replace("ipfs://", "");
image = await asyncIterableToUint8Array(
ipfsClient.cat(imageCID).iterable()
)
);
} else {
image = meta.image
image = meta.image;
}
list.push({
image,
name: meta.name,
base64: meta.fail
})
base64: meta.fail,
});
setNftList(list)
setNftList(list);
}
}
@ -418,7 +414,7 @@ function App() {
</svg>
<Input
className="pl-10 w-full bg-zinc-900 border-zinc-700 text-white ring-offset-primary"
placeholder="Introduce ETH Address or ENS. eg: 0x00...ABC or vitalik.ens"
placeholder="Introduce ETH Address or ENS. eg: 0x00...ABC or vitalik.eth"
type="search"
disabled={!ready}
ref={inputRef}
@ -433,7 +429,13 @@ function App() {
{auth.isLoggedIn && !ethStatus?.ready ? (
<span className="max-w-4xl w-full block my-1 p-4 rounded-lg opacity-80 bg-yellow-900/70 border border-yellow-500 text-yellow-500">
You'll need to wait for a couple minutes before we can start
searching. You are currently locally syncing to the ETH network. <b className="font-bold">Current Progress: {ethStatus?.sync ? `${ethStatus?.sync.toLocaleString()}%` : 'Initializing...'}</b>
searching. You are currently locally syncing to the ETH network.{" "}
<b className="font-bold">
Current Progress:{" "}
{ethStatus?.sync
? `${ethStatus?.sync.toLocaleString()}%`
: "Initializing..."}
</b>
</span>
) : null}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
@ -453,7 +455,7 @@ function App() {
))}
</div>
</div>
)
);
}
export default function () {
@ -467,5 +469,5 @@ export default function () {
</AuthProvider>
</LumeStatusProvider>
</AppProvider>
)
);
}

View File

@ -23,8 +23,8 @@ import '@/styles/global.css'
<Card className="max-w-3xl bg-zinc-950 border-zinc-800 shadow-xl">
<CardHeader>
<img src={LogoImg.src} class="w-20 mb-8" />
<CardTitle className='text-white'>Welcome to Web3Toybox</CardTitle>
<CardDescription className='text-zinc-500 text-lg'>This is a place where you can play with different famous web3 apps in a truly decentralized way - no infura, no gateways, no censorship.</CardDescription>
<CardTitle className='text-white'>Welcome to Web3 Toybox</CardTitle>
<CardDescription className='text-zinc-500 text-lg'>This is a place where you can play with different web3 use cases by example in a truly decentralized way - no infura, no gateways, no censorship.</CardDescription>
</CardHeader>
<CardContent className='flex flex-col md:flex-row gap-y-5 md:gap-y-0 justify-between gap-x-5'>
<Card className="md:max-w-[400px] w-full bg-zinc-900 border-zinc-800 hover:shadow-lg hover:ring-1 hover:ring-green-400/20 hover:shadow-green-400/20 hover:transform-gpu hover:-translate-y-[3px] transition-all duration-150">
@ -43,12 +43,12 @@ import '@/styles/global.css'
</Button>
</a>
</CardFooter>
</Card>
</Card>
<Card className="w-full max-w-[300px] bg-zinc-950 border-zinc-700 border-dashed">
<CardHeader>
<CardTitle className='text-zinc-400 mb-3'>More Coming Soon...</CardTitle>
<CardDescription className='text-zinc-400'>
We're diligently developing more intriguing examples. If you have any suggestions, don't hesitate to join our Discord channel and propose or submit any examples you'd like to see here!
<CardDescription className='text-zinc-400'>
We're are definitely interested in new ideas for demos! If you have any suggestions, don't hesitate to join our Discord channel and propose or submit any examples you'd like to see here!
<a href="https://discord.com/invite/qpC8ADp3rS">
<Button variant="link" className='h-auto p-0 inline pl-1'>
Join Discord
@ -56,13 +56,13 @@ import '@/styles/global.css'
</a>
</CardDescription>
</CardHeader>
</Card>
</Card>
</CardContent>
<CardFooter className='flex flex-col space-y-3'>
<span class="max-w-4xl w-full block my-2 p-4 rounded-lg opacity-80 bg-gray-900/70 border border-gray-600 text-gray-400">
For an enhanced experience, we recommend opening the developer console while interacting with these demos. If you're unsure how to do this, you can find instructions <Button variant="link" className="inline p-0 h-auto w-auto text-md"><a href="https://support.google.com/campaignmanager/answer/2828688">here</a></Button>.
</span>
<p class="text-zinc-700 text-sm">Brought to you with 💚 by the <a href="https://lumeweb.com" class="text-zinc-500 underline">lumeweb.com</a> team alongside the <a href="https://sia.tech/about-sia-foundation" class="text-zinc-500 underline">Sia Foundation</a></p>
<p class="text-zinc-700 text-sm">Brought to you with 💚 by the <a href="https://lumeweb.com" class="text-zinc-500 underline">Lume</a> team, and grant sponsored by the <a href="https://sia.tech/about-sia-foundation" class="text-zinc-500 underline">Sia Foundation</a></p>
</CardFooter>
</Card>
</div>