diff --git a/package-lock.json b/package-lock.json
index ddb200a2..cde5ee6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@lottiefiles/svelte-lottie-player": "^0.3.0",
+ "@nifty-oss/asset": "^0.3.0",
"@onsol/tldparser": "^0.5.3",
"@solana/spl-account-compression": "^0.1.8",
"@solana/spl-token-registry": "^0.2.4574",
@@ -1755,6 +1756,69 @@
"debug": "^4.3.4"
}
},
+ "node_modules/@metaplex-foundation/umi": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi/-/umi-0.9.1.tgz",
+ "integrity": "sha512-IhHoOvp4vfO/++YL+78+iVuLM53+FDwUOZDYgH6lx0jYXyQ27BeaieeR5i+q3A9dz4KxQo5Nzc5aCA1109QGCQ==",
+ "peer": true,
+ "dependencies": {
+ "@metaplex-foundation/umi-options": "^0.8.9",
+ "@metaplex-foundation/umi-public-keys": "^0.8.9",
+ "@metaplex-foundation/umi-serializers": "^0.9.0"
+ }
+ },
+ "node_modules/@metaplex-foundation/umi-options": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-options/-/umi-options-0.8.9.tgz",
+ "integrity": "sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A==",
+ "peer": true
+ },
+ "node_modules/@metaplex-foundation/umi-public-keys": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-public-keys/-/umi-public-keys-0.8.9.tgz",
+ "integrity": "sha512-CxMzN7dgVGOq9OcNCJe2casKUpJ3RmTVoOvDFyeoTQuK+vkZ1YSSahbqC1iGuHEtKTLSjtWjKvUU6O7zWFTw3Q==",
+ "peer": true,
+ "dependencies": {
+ "@metaplex-foundation/umi-serializers-encodings": "^0.8.9"
+ }
+ },
+ "node_modules/@metaplex-foundation/umi-serializers": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers/-/umi-serializers-0.9.0.tgz",
+ "integrity": "sha512-hAOW9Djl4w4ioKeR4erDZl5IG4iJdP0xA19ZomdaCbMhYAAmG/FEs5khh0uT2mq53/MnzWcXSUPoO8WBN4Q+Vg==",
+ "peer": true,
+ "dependencies": {
+ "@metaplex-foundation/umi-options": "^0.8.9",
+ "@metaplex-foundation/umi-public-keys": "^0.8.9",
+ "@metaplex-foundation/umi-serializers-core": "^0.8.9",
+ "@metaplex-foundation/umi-serializers-encodings": "^0.8.9",
+ "@metaplex-foundation/umi-serializers-numbers": "^0.8.9"
+ }
+ },
+ "node_modules/@metaplex-foundation/umi-serializers-core": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-core/-/umi-serializers-core-0.8.9.tgz",
+ "integrity": "sha512-WT82tkiYJ0Qmscp7uTj1Hz6aWQPETwaKLAENAUN5DeWghkuBKtuxyBKVvEOuoXerJSdhiAk0e8DWA4cxcTTQ/w==",
+ "peer": true
+ },
+ "node_modules/@metaplex-foundation/umi-serializers-encodings": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-encodings/-/umi-serializers-encodings-0.8.9.tgz",
+ "integrity": "sha512-N3VWLDTJ0bzzMKcJDL08U3FaqRmwlN79FyE4BHj6bbAaJ9LEHjDQ9RJijZyWqTm0jE7I750fU7Ow5EZL38Xi6Q==",
+ "peer": true,
+ "dependencies": {
+ "@metaplex-foundation/umi-serializers-core": "^0.8.9"
+ }
+ },
+ "node_modules/@metaplex-foundation/umi-serializers-numbers": {
+ "version": "0.8.9",
+ "resolved": "https://registry.npmjs.org/@metaplex-foundation/umi-serializers-numbers/-/umi-serializers-numbers-0.8.9.tgz",
+ "integrity": "sha512-NtBf1fnVNQJHFQjLFzRu2i9GGnigb9hOm/Gfrk628d0q0tRJB7BOM3bs5C61VAs7kJs4yd+pDNVAERJkknQ7Lg==",
+ "peer": true,
+ "dependencies": {
+ "@metaplex-foundation/umi-serializers-core": "^0.8.9"
+ }
+ },
"node_modules/@ngraveio/bc-ur": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@ngraveio/bc-ur/-/bc-ur-1.1.6.tgz",
@@ -1769,6 +1833,14 @@
"sha.js": "^2.4.11"
}
},
+ "node_modules/@nifty-oss/asset": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@nifty-oss/asset/-/asset-0.3.0.tgz",
+ "integrity": "sha512-2pAzMLYxCF2s/2NdKSxL/KwMhw/X6U2l2fHYpVkJbeym9h7kKe53mStiwXvX/hmFKGuWpfCySk8Qz6JUtUEUhA==",
+ "peerDependencies": {
+ "@metaplex-foundation/umi": "^0.9.1"
+ }
+ },
"node_modules/@noble/curves": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
diff --git a/package.json b/package.json
index 72ef996c..a44df48d 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@lottiefiles/svelte-lottie-player": "^0.3.0",
+ "@nifty-oss/asset": "^0.3.0",
"@onsol/tldparser": "^0.5.3",
"@solana/spl-account-compression": "^0.1.8",
"@solana/spl-token-registry": "^0.2.4574",
diff --git a/src/lib/components/providers/nifty-asset-provider.svelte b/src/lib/components/providers/nifty-asset-provider.svelte
new file mode 100644
index 00000000..7b048179
--- /dev/null
+++ b/src/lib/components/providers/nifty-asset-provider.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+ {#if intersecting}
+
+ {/if}
+
+
diff --git a/src/lib/trpc/router.ts b/src/lib/trpc/router.ts
index eaeaf432..b88a4d17 100644
--- a/src/lib/trpc/router.ts
+++ b/src/lib/trpc/router.ts
@@ -9,6 +9,7 @@ import { balances } from "$lib/trpc/routes/balances";
import { concurrentMerkleTree } from "$lib/trpc/routes/concurrent-merkle-tree";
import { currentSlot } from "$lib/trpc/routes/current-slot";
import { deprecatedImage } from "./routes/deprecated-image";
+import { niftyAsset } from "$lib/trpc/routes/nifty-asset";
import { price } from "$lib/trpc/routes/price";
import { rawTransaction } from "$lib/trpc/routes/raw-transaction";
import { searchAssets } from "./routes/search-assets";
@@ -33,6 +34,7 @@ export const router = t.router({
concurrentMerkleTree,
currentSlot,
deprecatedImage,
+ niftyAsset,
price,
rawTransaction,
searchAssets,
diff --git a/src/lib/trpc/routes/nifty-asset.ts b/src/lib/trpc/routes/nifty-asset.ts
new file mode 100644
index 00000000..e9850c6f
--- /dev/null
+++ b/src/lib/trpc/routes/nifty-asset.ts
@@ -0,0 +1,32 @@
+import { z } from "zod";
+
+import { t } from "$lib/trpc/t";
+
+import { connect } from "$lib/xray";
+import { LAMPORTS_PER_SOL, PublicKey, Connection } from "@solana/web3.js";
+import { getRPCUrl } from "$lib/util/get-rpc-url";
+
+import { HELIUS_API_KEY } from "$env/static/private";
+import { getAssetAccountDataSerializer } from "@nifty-oss/asset";
+
+export const niftyAsset = t.procedure
+ .input(z.tuple([z.string(), z.boolean()]))
+ .query(async ({ input }) => {
+ const [address, isMainnet] = input;
+ const connection = new Connection(
+ getRPCUrl(`?api-key=${HELIUS_API_KEY}`, isMainnet),
+ "confirmed"
+ );
+
+ const pubKey = new PublicKey(address);
+
+ const accountInfo = await connection.getAccountInfo(pubKey);
+
+ if (accountInfo) {
+ return getAssetAccountDataSerializer().deserialize(
+ accountInfo.data
+ );
+ }
+
+ return null;
+ });
diff --git a/src/lib/types.ts b/src/lib/types.ts
index abd76c52..bbe874ad 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -5,8 +5,11 @@ import type { EnrichedTransaction } from "helius-sdk";
import type { ProtonTransaction, ProtonTransactionAction } from "$lib/xray";
import type { IconPaths, modals } from "$lib/config";
+
import type { SOL } from "$lib/xray";
+import type { Asset } from "@nifty-oss/asset";
+
export * from "$lib/config";
export interface UIConfig {
@@ -143,3 +146,6 @@ export type UISolAccountToken = {
balanceInUSD: number;
price: number;
};
+
+/** Used in the Nifty Asset pages. */
+export type UINiftyAsset = Asset & { json: any };
diff --git a/src/lib/util/stores/nifty-asset.ts b/src/lib/util/stores/nifty-asset.ts
new file mode 100644
index 00000000..325f5033
--- /dev/null
+++ b/src/lib/util/stores/nifty-asset.ts
@@ -0,0 +1,4 @@
+import type { UINiftyAsset } from "$lib/types";
+import { writable } from "svelte/store";
+
+export const niftyAssetStore = writable(null);
diff --git a/src/lib/xray/lib/search.ts b/src/lib/xray/lib/search.ts
index a3491b2c..e9eb4c61 100644
--- a/src/lib/xray/lib/search.ts
+++ b/src/lib/xray/lib/search.ts
@@ -8,6 +8,7 @@ import { TldParser } from "@onsol/tldparser";
import { browser } from "$app/environment";
import getJupiterTokens from "$lib/util/get-tokens";
+import { ASSET_PROGRAM_ID } from "@nifty-oss/asset";
export interface SearchResult {
url: string;
@@ -23,6 +24,7 @@ type SearchResultType =
| "transaction"
| "bonfida-domain"
| "ans-domain"
+ | "nifty-asset"
| null;
const searchDefaults: SearchResult = {
@@ -60,16 +62,19 @@ export const search = async (
const pubkey = new PublicKey(query);
const account = await connection.getAccountInfo(pubkey);
const program = account?.owner.toString();
-
- const probablyToken =
- program === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ||
- program === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" ||
- account === null;
-
- let addressType!: "token" | "account";
- if (probablyToken) {
- addressType = "token";
+ let addressType!: "token" | "account" | "nifty-asset";
+
+ if (account) {
+ if (
+ program === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ||
+ program === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+ ) {
+ addressType = "token";
+ } else if (program === ASSET_PROGRAM_ID) {
+ addressType = "nifty-asset";
+ }
}
+
addressType ??= "account";
return {
diff --git a/src/routes/account/[account]/assets/+page.svelte b/src/routes/account/[account]/assets/+page.svelte
index 0d873cd1..5ed52719 100644
--- a/src/routes/account/[account]/assets/+page.svelte
+++ b/src/routes/account/[account]/assets/+page.svelte
@@ -42,7 +42,9 @@
{#if asset.burnt !== true}
- {/if}
+ {/if}
{/each}
{/each}
diff --git a/src/routes/nifty-asset/[asset]/+page.svelte b/src/routes/nifty-asset/[asset]/+page.svelte
new file mode 100644
index 00000000..9b81edbd
--- /dev/null
+++ b/src/routes/nifty-asset/[asset]/+page.svelte
@@ -0,0 +1,893 @@
+
+
+
+
+
+ {#if loading}
+
+ {:else}
+
+
+
+
+ {asset.name}
+
+
+
+
+
+
+
+
+ {asset.mutable ? "mutable" : "immutable"}
+
+ {#if mediaType === "onchain"}
+
+ on-chain media
+
+ {/if}
+ {#if grouping}
+
group
+ {/if}
+ {#if asset.standard !== Standard.NonFungible}
+
+ {Standard[asset.standard]}
+
+ {/if}
+
+
+
+
+
+ {#if mediaType}
+
+
+ {#if mediaType === "video"}
+
+
+
+
+ {:else if mediaType === "image"}
+
+
+
+
+ {:else if mediaType === "onchain"}
+
+
+
+
+ {:else}
+
+
Loading...
+ {/if}
+
+
+ {/if}
+
+
+
+ {#if asset.json || metadata}
+
+
+
+
+ {#if asset.json && asset.json.description && asset.json.description.length > 0}
+ {asset.json.description}
+ {:else if metadata && metadata.description}
+ {metadata.description}
+ {/if}
+
+
+
+
+ {/if}
+
+ {#if royalties}
+
+
+
+ {asset.name ?? "The"} creator(s) currently expect to
+ take {basisPointsToPercentage(
+ Number(royalties.basisPoints)
+ )} of every secondary sale on this piece.
+
+
+
+
+ {#if royalties.constraint.type !== "Empty"}
+
+
+
+
+
+ {/if}
+ {/if}
+
+ {#if creators && creators.creators.length > 0}
+
+ {/if}
+
+ {#if asset.json}
+
+
+
+
+
+ {/if}
+
+ {#if hasExtensions}
+
+
+ List of extensions attached to this asset.
+ {#if attributes}
+
+
+
+ {#each attributes.traits as attribute, idx}
+
+
+ {attribute.traitType}
+
+
+ {attribute.value}
+
+
+ {/each}
+
+
+
+ {/if}
+ {#if blob}
+
+
+
+ {#if mediaType !== "onchain" && mediaBlob}
+
+
+
+ {/if}
+
+
+ Content-Type
+
+
+ {blob.contentType}
+
+
+
+
+ Data Size
+
+
+ {blob.data.length} bytes
+
+
+
+
+
+ {/if}
+ {#if creators}
+
+ {/if}
+ {#if grouping}
+
+
+
+
+
+ Size
+
+
+ {grouping.size}
+
+
+
+
+ Maximum Size
+
+
+ {grouping.maxSize}
+
+
+ {#if grouping.delegate}
+
+
+
+ {grouping.delegate}
+
+
+ {:else}
+
+ {/if}
+
+
+
+ {/if}
+ {#if links}
+
+ {/if}
+ {#if manager}
+
+ {/if}
+ {#if metadata}
+
+
+
+
+
+ Symbol
+
+
+ {metadata.symbol.length > 0
+ ? metadata.symbol
+ : "None"}
+
+
+
+
+ Description
+
+
+ {metadata.description.length > 0
+ ? metadata.description
+ : "None"}
+
+
+ {#if metadata.uri.length > 0}
+
+
+
+ {metadata.uri}
+
+
+ {:else}
+
+ {/if}
+
+
+
+ {/if}
+ {#if proxy}
+
+ {/if}
+ {#if royalties}
+
+
+
+
+
+ {royalties.basisPoints}
+
+
+ {#if royalties.constraint.type !== "Empty"}
+
+ {:else}
+
+ {/if}
+
+
+ {/if}
+
+
+ {/if}
+
+ {/if}
+
diff --git a/src/routes/nifty-asset/[asset]/_loader.svelte b/src/routes/nifty-asset/[asset]/_loader.svelte
new file mode 100644
index 00000000..5fdd9d28
--- /dev/null
+++ b/src/routes/nifty-asset/[asset]/_loader.svelte
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each [1, 2, 3, 4, 5] as i}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+ {#each [1, 2] as i}
+
+ {/each}
+
+
+
+
+