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
2 changes: 1 addition & 1 deletion app/.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
NEXT_PUBLIC_NETWORK_URL="https://raw.githubusercontent.com/overlock-network/net/main"
NEXT_PUBLIC_CONFIGURATION_COLLECTION_UPDATE_AUTHORITY="7yJ14gQeQn3nNGv8sG1Up1MHLZdk8WD61g1s4A5Kn81h,7yJ14gQeQn3nNGv8sG1Up1MHLZdk8WD61g1s4A5Kn81h"
NEXT_PUBLIC_ANCHOR_PROVIDER_URL="http://localhost:8899"
NEXT_PUBLIC_GAS_CONTRACT_ID="1"
NEXT_PUBLIC_GAS_CONTRACT_ID="1"
28 changes: 28 additions & 0 deletions app/src/app/marketplace/gas/collection/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { GasContractClient } from "@/chain/client";

import { useSearchParams } from "next/navigation";
import { NftProvider } from "@/chain/client";
import { NftCards } from "@/components/NftCards";

export function Content() {
const searchParams = useSearchParams();
const contractId = searchParams.get("id");
if (!contractId) return;

return (
<NftProvider<GasContractClient>
queryClientClass={GasContractClient}
fetchInfo={async (client, tokenId) => {
return await client.nftInfo({ tokenId });
}}
contractId={contractId}
fetchNft={async (client, limit, startAfter) => {
return await client.allTokens({ limit, startAfter });
}}
>
<NftCards id={contractId} />
</NftProvider>
);
}
18 changes: 18 additions & 0 deletions app/src/app/marketplace/gas/collection/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Inset } from "@/components/AppSidebar";
import { Content } from "./content";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Gas Collection NFT",
description: "List of Collection NFT.",
};

export default async function GasCollectionPage() {
return (
<Inset pageTitle={metadata.title as string}>
<div className="flex h-full items-center flex-col p-8">
<Content />
</div>
</Inset>
);
}
5 changes: 2 additions & 3 deletions app/src/app/marketplace/gas/content.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"use client";

import { ContractsProvider } from "@/chain/client";
import { Cw721baseQueryClient } from "@/../../generated/Cw721base.client";
import { ContractsProvider, GasContractClient } from "@/chain/client";
import { ContractsTable } from "@/components/ContractsTable";

export function Content() {
const contractCodeId = process.env.NEXT_PUBLIC_GAS_CONTRACT_ID;
if (!contractCodeId) return;
return (
<ContractsProvider
queryClientClass={Cw721baseQueryClient}
queryClientClass={GasContractClient}
fetchInfo={async (client) => {
const info = await client.contractInfo();
return {
Expand Down
2 changes: 1 addition & 1 deletion app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function Home() {
</div>
</div>
</Link>
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 mb-5">
<div className="flex items-center justify-center rounded-full bg-secondary min-w-12 w-12 h-12">
<ScanSearch />
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/src/app/providers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const metadata: Metadata = {
export default function ProvidersListPage() {
return (
<Inset pageTitle={metadata.title as string}>
<div className="flex h-full items-center flex-col p-8 ">
<div className="flex h-full items-center flex-col p-8">
<Content />
</div>
</Inset>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,7 @@ export function ContractsProvider<T>({

useEffect(() => {
const rpcEndpoint = networkMeta.apis.rpc[0].address;
if (!rpcEndpoint) {
toast({
title: "Error",
description: "Missing RPC endpoint in environment",
variant: "destructive",
});
return;
}

CosmWasmClient.connect(rpcEndpoint)
.then(setClient)
.catch(() => {
Expand All @@ -56,8 +49,8 @@ export function ContractsProvider<T>({
const addresses = await client.getContracts(parseInt(contractCodeId));
const results = await Promise.all(
addresses.map(async (address) => {
const instance = new queryClientClass(client, address);
return await fetchInfo(instance);
const queryClient = new queryClientClass(client, address);
return await fetchInfo(queryClient);
}),
);
setContracts(results);
Expand Down
112 changes: 112 additions & 0 deletions app/src/chain/client/cosmos/components/NftProvider/NftProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client";

import {
createContext,
useContext,
useEffect,
useState,
useCallback,
} from "react";
import { CosmWasmClient, JsonObject } from "@cosmjs/cosmwasm-stargate";
import { useToast } from "@/hooks/use-toast";
import { Nft } from "@/lib/types";
import {
NftContextType,
NftProviderProps,
useNetwork,
} from "@/chain/client/cosmos";

const NftContext = createContext<NftContextType | undefined>(undefined);

const normalizeUri = (uri: string): string => {
if (!uri) return "";
return uri.startsWith("ipfs://")
? uri.replace("ipfs://", "https://ipfs.io/ipfs/")
: uri;
};

export function NftProvider<T>({
queryClientClass,
fetchNft,
fetchInfo,
children,
}: NftProviderProps<T>) {
const [client, setClient] = useState<CosmWasmClient | null>(null);
const { toast } = useToast();
const { networkMeta } = useNetwork();

useEffect(() => {
const rpcEndpoint = networkMeta.apis.rpc[0].address;

CosmWasmClient.connect(rpcEndpoint)
.then(setClient)
.catch(() => {
toast({
title: "Error",
description: "Failed to connect to RPC",
variant: "destructive",
});
});
}, [toast, networkMeta]);

const getCollectionNft = useCallback(
async (
address: string,
startAfter?: string,
limit: number = 20,
): Promise<{ nft: Nft[]; nextStartAfter?: string }> => {
if (!client) return { nft: [] };

try {
const queryClient = new queryClientClass(client, address);
const { tokens = [] } = await fetchNft(queryClient, limit, startAfter);

const nextStartAfter =
tokens.length === limit ? tokens[tokens.length - 1] : undefined;

const nft = await Promise.all(
tokens.map(async (tokenId) => {
const nftInfo = await fetchInfo(queryClient, tokenId);
const tokenUri = nftInfo.token_uri || "";

let metadata: JsonObject = {};
if (tokenUri) {
const res = await fetch(normalizeUri(tokenUri));
if (res.ok) metadata = await res.json();
}

return {
tokenId,
tokenUri,
name: metadata.name,
image: normalizeUri(metadata.image),
description: metadata.description,
};
}),
);

return { nft, nextStartAfter };
} catch {
toast({
title: "Error",
description: "Failed to load NFT from collection",
variant: "destructive",
});
return { nft: [] };
}
},
[client, toast],
);

return (
<NftContext.Provider value={{ getCollectionNft }}>
{children}
</NftContext.Provider>
);
}

export const useNft = () => {
const context = useContext(NftContext);
if (!context) throw new Error("useNft must be used within a NftProvider");
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NftProvider, useNft } from "./NftProvider";
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { createContext, useContext, useEffect, useState } from "react";
import { createContext, useContext, useEffect, useState } from "react";
import { useChain } from "@cosmos-kit/react";
import { useNetwork } from "../NetworkProvider";

Expand Down
1 change: 1 addition & 0 deletions app/src/chain/client/cosmos/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export {
export { WalletProviderWrapper } from "./WalletProviderWrapper";
export { useWallet } from "./WalletProvider";
export { ContractsProvider, useContracts } from "./ContractsProvider";
export { NftProvider, useNft } from "./NftProvider";
1 change: 1 addition & 0 deletions app/src/chain/client/cosmos/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./components";
export * from "./lib/types";
export * from "./hooks";
export { Cw721baseQueryClient as GasContractClient } from "@/../../generated/Cw721base.client";
29 changes: 28 additions & 1 deletion app/src/chain/client/cosmos/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { Contract } from "@/lib/types";
import { Contract, Nft } from "@/lib/types";
import { overlock } from "@overlocknetwork/api";
import { TokensResponse } from "@/../../generated/Cw721base.types";

export type Network = { name: string; icon: React.ElementType };

Expand Down Expand Up @@ -96,6 +97,17 @@ export interface ContractsContextType {
loading: boolean;
}

export interface NftContextType {
getCollectionNft: (
address: string,
startAfter?: string,
limit?: number,
) => Promise<{
nft: Nft[];
nextStartAfter?: string;
}>;
}

export interface ContractsProviderProps<T> {
queryClientClass: QueryClientConstructor<T>;
fetchInfo: (instance: T) => Promise<Contract>;
Expand All @@ -107,3 +119,18 @@ export type QueryClientConstructor<T> = new (
client: CosmWasmClient,
address: string,
) => T;

export interface NftProviderProps<T> {
queryClientClass: QueryClientConstructor<T>;
fetchNft: (
instance: T,
limit: number,
startAfter?: string,
) => Promise<TokensResponse>;
fetchInfo: (
instance: T,
tokenId: string,
) => Promise<{ token_uri?: string | null }>;
children: React.ReactNode;
contractId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { createContext, useContext, useCallback } from "react";
import { Nft } from "@/lib/types";
import { NftContextType, NftProviderProps } from "@/chain/client/cosmos";

const NftContext = createContext<NftContextType | undefined>(undefined);

export function NftProvider<T>({ children }: NftProviderProps<T>) {
const getCollectionNft = useCallback(async (): Promise<{
nft: Nft[];
nextStartAfter?: string;
}> => {
return { nft: [] };
}, []);

return (
<NftContext.Provider value={{ getCollectionNft }}>
{children}
</NftContext.Provider>
);
}

export const useNft = () => {
const context = useContext(NftContext);
if (!context) throw new Error("useNft must be used within a NftProvider");
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NftProvider, useNft } from "./NftProvider";
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { createContext, useContext } from "react";
"use client";
import { createContext, useContext } from "react";
import { useWalletModal as useSolanaWalletModal } from "@solana/wallet-adapter-react-ui";

interface WalletModalContextType {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client";
import { WalletModalProvider as SolanaWalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { WalletModalProvider } from "../WalletModalProvider";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
"use client";
import { useEffect, useState } from "react";
import { WalletProvider as SolanaWalletProvider } from "@solana/wallet-adapter-react";
import { WalletProvider } from "../WalletProvider";

Expand Down
4 changes: 2 additions & 2 deletions app/src/components/AppSidebar/Inset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export function Inset({

return (
<SidebarInset>
<header className="sticky top-0 z-50 flex h-16 shrink-0 items-center gap-2 border-b backdrop-blur transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4 w-full">
<header className="sticky top-0 z-50 flex h-16 shrink-0 items-center justify-center gap-2 px-8 border-b backdrop-blur transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 max-w-[1400px] w-full">
<SidebarTrigger className="-ml-1" side="left" />
<Separator orientation="vertical" className="mr-2 h-4" />
{pageTitle == "Editor" && configurationId ? (
Expand Down
5 changes: 5 additions & 0 deletions app/src/components/ContractsTable/ContractsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import { useContracts } from "@/chain/client";
import { DataTable } from "../ListTable/DataTable";
import { Contract } from "@/lib/types";
import { ContractInfoColumns } from "../ListTable/ContractInfoColumns";
import { useRouter } from "next/navigation";

export function ContractsTable() {
const { contracts, loading } = useContracts();
const router = useRouter();

return (
<div className="max-w-[1400px] w-full">
<DataTable<Contract>
columns={ContractInfoColumns()}
data={contracts}
isLoading={loading}
onRowClick={(row) =>
router.push(`/marketplace/gas/collection?id=${row.address}`)
}
/>
</div>
);
Expand Down
Loading