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
5 changes: 5 additions & 0 deletions app/src/app/marketplace/configuration/collection/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { ConfigurationContractClient } from "@/chain/client";
import { useSearchParams } from "next/navigation";
import { NftProvider } from "@/chain/client";
import { ConfigurationCards } from "@/components/ConfigurationCards/ConfigurationCards";
import { MintConfigurationDialog } from "@/components/MintConfigurationDialog";

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

if (!contractId) return;

return (
Expand All @@ -21,6 +23,9 @@ export function Content() {
}}
contractId={contractId}
>
<div className="flex ml-auto mb-4">
<MintConfigurationDialog contractId={contractId} />
</div>
<ConfigurationCards id={contractId} />
</NftProvider>
);
Expand Down
98 changes: 84 additions & 14 deletions app/src/chain/client/cosmos/components/NftProvider/NftProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import React, {
useState,
useCallback,
} from "react";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import {
CosmWasmClient,
SigningCosmWasmClient,
} from "@cosmjs/cosmwasm-stargate";
import { useToast } from "@/hooks/use-toast";
import { Nft } from "@/lib/types";
import {
NftContextType,
NftProviderProps,
useNetwork,
useWallet,
} from "@/chain/client/cosmos";
import { useChain } from "@cosmos-kit/react";
import { GasPrice } from "@cosmjs/stargate";

const NftContext = createContext<NftContextType<unknown> | undefined>(
undefined,
Expand All @@ -25,22 +31,35 @@ export function NftProvider<T>({
children,
}: NftProviderProps<T>) {
const [client, setClient] = useState<CosmWasmClient | null>(null);
const [signingClient, setSigningClient] =
useState<SigningCosmWasmClient | null>(null);
const { toast } = useToast();
const { networkMeta } = useNetwork();
const { getOfflineSigner, address, isWalletConnected } = useChain(
networkMeta.chain_name,
);
const { balance } = useWallet();
const rpcEndpoint = networkMeta.apis.rpc[0].address;

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

useEffect(() => {
const rpcEndpoint = networkMeta.apis.rpc[0].address;
if (isWalletConnected) {
const gasPrice = GasPrice.fromString("0ovk");

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

const getCollectionNft = useCallback(
async (
Expand Down Expand Up @@ -87,11 +106,62 @@ export function NftProvider<T>({
return { nft: [] };
}
},
[client, toast],
[client],
);

const mintNft = useCallback(
async ({
contractAddress,
tokenId,
tokenUri = "",
}: {
contractAddress: string;
tokenId: string;
tokenUri?: string;
}) => {
if (!signingClient || !address) {
toast({
title: "Error",
description: "Failed to mint NFT",
variant: "destructive",
});
return;
}

if (balance && Number(balance.amount) > 0) {
try {
const msg = {
mint: {
token_id: tokenId,
owner: address,
token_uri: tokenUri,
},
};
await signingClient.execute(address, contractAddress, msg, "auto");
toast({
title: "Success",
description: `NFT minted successfully!`,
});
} catch {
toast({
title: "Error",
description: "Failed to mint NFT",
variant: "destructive",
});
}
} else {
toast({
title: "Error",
description: "You don't have enough tokens",
variant: "destructive",
});
}
},
[signingClient, address, balance],
);

return (
<NftContext.Provider value={{ getCollectionNft }}>
<NftContext.Provider value={{ getCollectionNft, mintNft }}>
{children}
</NftContext.Provider>
);
Expand Down
5 changes: 5 additions & 0 deletions app/src/chain/client/cosmos/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export interface NftContextType<T> {
nft: Nft<T>[];
nextStartAfter?: string;
}>;
mintNft: (params: {
contractAddress: string;
tokenId: string;
tokenUri?: string;
}) => Promise<void>;
}

export interface ContractsProviderProps<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ export function NftProvider<T>({ children }: NftProviderProps<T>) {
return { nft: [] };
}, []);

const mintNft = async () => {};

return (
<NftContext.Provider value={{ getCollectionNft }}>
<NftContext.Provider value={{ getCollectionNft, mintNft }}>
{children}
</NftContext.Provider>
);
Expand Down
29 changes: 29 additions & 0 deletions app/src/chain/client/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,32 @@ export * from "./hooks";
export { WalletProviderWrapper } from "./components/WalletProviderWrapper";
export { useWallet } from "./components/WalletProvider";
export { useNft, NftProvider } from "./components/NftProvider";
export class GasContractClient {
nftInfo = async ({}: {
tokenId: string;
}): Promise<{ token_uri?: string | null | undefined }> => {
return { token_uri: undefined };
};

allTokens = async ({}: {
limit?: number;
startAfter?: string;
}): Promise<{ tokens: string[] }> => {
return { tokens: [] };
};
}

export class ConfigurationContractClient {
nftInfo = async ({}: {
tokenId: string;
}): Promise<{ token_uri?: string | null | undefined }> => {
return { token_uri: undefined };
};

allTokens = async ({}: {
limit?: number;
startAfter?: string;
}): Promise<{ tokens: string[] }> => {
return { tokens: [] };
};
}
129 changes: 129 additions & 0 deletions app/src/components/MintConfigurationDialog/MintConfigurationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"use client";

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { CirclePlus } from "lucide-react";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNft, useWallet, useWalletModal } from "@/chain/client";

const MintConfigurationSchema = z.object({
name: z.string().min(2),
crossplaneVersion: z.string().min(2),
configurationImage: z.string().min(2),
});

type MintForm = z.infer<typeof MintConfigurationSchema>;

export function MintConfigurationDialog({
contractId,
}: {
contractId: string;
}) {
const form = useForm<MintForm>({
resolver: zodResolver(MintConfigurationSchema),
defaultValues: { name: "", crossplaneVersion: "", configurationImage: "" },
});
const { connected } = useWallet();
const { setVisible } = useWalletModal();
const { mintNft } = useNft<MintForm>();

const onSubmit = async () => {
const tokenId = crypto.randomUUID();

await mintNft({ contractAddress: contractId, tokenId });
form.reset();
};

return (
<>
{connected ? (
<Dialog>
<DialogTrigger asChild>
<Button>
Mint <CirclePlus className="ml-2 h-4 w-4" />
</Button>
</DialogTrigger>
<DialogContent aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>Mint configuration</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 max-w-xl mx-auto w-full"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="crossplaneVersion"
render={({ field }) => (
<FormItem>
<FormLabel>Crossplane Version</FormLabel>
<FormControl>
<Input placeholder="Crossplane Version" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configurationImage"
render={({ field }) => (
<FormItem>
<FormLabel>Configuration Image</FormLabel>
<FormControl>
<Input placeholder="Configuration Image" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
variant="outline"
type="submit"
disabled={!form.formState.isDirty}
>
Save
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
) : (
<Button onClick={() => setVisible(true)}>
Mint <CirclePlus className="ml-2 h-4 w-4" />
</Button>
)}
</>
);
}
1 change: 1 addition & 0 deletions app/src/components/MintConfigurationDialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MintConfigurationDialog } from "./MintConfigurationDialog";
8 changes: 8 additions & 0 deletions app/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,11 @@ export interface Nft<T = unknown> {
tokenId: string;
metadata: T;
}

export interface WalletContextType {
disconnect: () => Promise<void>;
connected: boolean;
address: string | null;
balance: Coin | null;
fetchBalance: () => Promise<void>;
}
Loading