Skip to content

Commit

Permalink
solana rpc impl for nfts for address (#4224)
Browse files Browse the repository at this point in the history
  • Loading branch information
callensm authored Jun 23, 2023
1 parent 071d3e8 commit 69e841f
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 146 deletions.
24 changes: 12 additions & 12 deletions backend/native/backpack-api/src/routes/graphql/clients/hasura.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { GraphQLError } from "graphql";

import { NodeBuilder } from "../nodes";
import { getProviderForId } from "../providers";
import {
type Friend,
type FriendRequest,
FriendRequestType,
type Notification,
type NotificationConnection,
type NotificationFiltersInput,
type ProviderId,
type User,
type Wallet,
type WalletConnection,
type WalletFiltersInput,
import type {
Friend,
FriendRequest,
Notification,
NotificationConnection,
NotificationFiltersInput,
ProviderId,
User,
Wallet,
WalletConnection,
WalletFiltersInput,
} from "../types";
import { FriendRequestType } from "../types";
import { createConnection, inferProviderIdFromString } from "../utils";

type HasuraOptions = {
Expand Down
20 changes: 10 additions & 10 deletions backend/native/backpack-api/src/routes/graphql/providers/bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { ethers } from "ethers";

import type { ApiContext } from "../context";
import { NodeBuilder } from "../nodes";
import {
type BalanceFiltersInput,
type Balances,
type NftConnection,
type NftFiltersInput,
ProviderId,
type TokenBalance,
type Transaction,
type TransactionConnection,
type TransactionFiltersInput,
import type {
BalanceFiltersInput,
Balances,
NftConnection,
NftFiltersInput,
TokenBalance,
Transaction,
TransactionConnection,
TransactionFiltersInput,
} from "../types";
import { ProviderId } from "../types";
import { calculateBalanceAggregate, createConnection } from "../utils";

import type { BlockchainDataProvider } from ".";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import { ethers } from "ethers";
import type { CoinGeckoPriceData } from "../clients/coingecko";
import type { ApiContext } from "../context";
import { NodeBuilder } from "../nodes";
import {
type BalanceFiltersInput,
type Balances,
type Nft,
type NftAttribute,
type NftConnection,
type NftFiltersInput,
ProviderId,
type TokenBalance,
type TransactionConnection,
type TransactionFiltersInput,
import type {
BalanceFiltersInput,
Balances,
Nft,
NftAttribute,
NftConnection,
NftFiltersInput,
TokenBalance,
TransactionConnection,
TransactionFiltersInput,
} from "../types";
import { ProviderId } from "../types";
import { calculateBalanceAggregate, createConnection } from "../utils";

import type { BlockchainDataProvider } from ".";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { ApiContext } from "../context";
import {
type BalanceFiltersInput,
type Balances,
type NftConnection,
type NftFiltersInput,
ProviderId,
type TransactionConnection,
type TransactionFiltersInput,
import type {
BalanceFiltersInput,
Balances,
NftConnection,
NftFiltersInput,
TransactionConnection,
TransactionFiltersInput,
} from "../types";
import { ProviderId } from "../types";

import { Bitcoin } from "./bitcoin";
import { Ethereum } from "./ethereum";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,93 +184,100 @@ export class Solana extends SolanaRpc implements BlockchainDataProvider {
): Promise<NftConnection> {
if (!this.ctx) {
throw new Error("API context object not available");
} else if (this.ctx.network.rpc) {
return super.getNftsForAddress(address, filters);
}

// Get the list of digital assets (NFTs) owned by the argued address from Helius DAS API.
const response = await this.ctx.dataSources.helius.rpc.getAssetsByOwner(
address
);

// Optionally filter for only argued NFT mints if provided
let { items } = response.result;
if (filters?.addresses) {
items = items.filter((i) => filters.addresses?.includes(i.id));
}

if (items.length === 0) {
return createConnection([], false, false);
}

// Get active listings for the argued wallet address and assign empty
// listing data array if the request fails
let listings: TensorActingListingsResponse;
try {
listings = await this.ctx.dataSources.tensor.getActiveListingsForWallet(
// Get the list of digital assets (NFTs) owned by the argued address from Helius DAS API.
const response = await this.ctx.dataSources.helius.rpc.getAssetsByOwner(
address
);
} catch (err) {
console.error(err);
listings = { data: { userActiveListings: { txs: [] } } };
}

// Create a map of collection address to name and image for reference
const collectionMap = await _getCollectionMetadatas(this.ctx, items);
// Optionally filter for only argued NFT mints if provided
let { items } = response.result;
if (filters?.addresses) {
items = items.filter((i) => filters.addresses?.includes(i.id));
}

// Create a map of associated token account addresses
const atas = getATAAddressesSync({
mints: items.reduce<Record<string, PublicKey>>((acc, curr) => {
acc[curr.id] = new PublicKey(curr.id);
return acc;
}, {}),
owner: new PublicKey(address),
});
if (items.length === 0) {
return createConnection([], false, false);
}

// Map all NFT metadatas into their return type with possible collection data
const nodes: Nft[] = items.map((item) => {
const collection = _parseCollectionMetadata(
this.id(),
collectionMap,
item.grouping.find((g) => g.group_key === "collection")?.group_value
);
// Get active listings for the argued wallet address and assign empty
// listing data array if the request fails
let listings: TensorActingListingsResponse;
try {
listings = await this.ctx.dataSources.tensor.getActiveListingsForWallet(
address
);
} catch (err) {
console.error(err);
listings = { data: { userActiveListings: { txs: [] } } };
}

const attributes: NftAttribute[] | undefined =
item.content.metadata?.attributes?.map((x) => ({
trait: x.trait_type,
value: x.value,
}));
// Create a map of collection address to name and image for reference
const collectionMap = await _getCollectionMetadatas(this.ctx, items);

let listing: Listing | undefined = undefined;
const tensorListing = listings.data.userActiveListings.txs.find(
(t) => t.tx.mintOnchainId === item.id
);
// Create a map of associated token account addresses
const atas = getATAAddressesSync({
mints: items.reduce<Record<string, PublicKey>>((acc, curr) => {
acc[curr.id] = new PublicKey(curr.id);
return acc;
}, {}),
owner: new PublicKey(address),
});

if (tensorListing) {
listing = NodeBuilder.tensorListing(item.id, {
amount: ethers.utils.formatUnits(
tensorListing.tx.grossAmount,
this.decimals()
),
source: tensorListing.tx.source,
url: this.ctx!.dataSources.tensor.getListingUrl(item.id),
});
}
// Map all NFT metadatas into their return type with possible collection data
const nodes: Nft[] = items.map((item) => {
const collection = _parseCollectionMetadata(
this.id(),
collectionMap,
item.grouping.find((g) => g.group_key === "collection")?.group_value
);

const attributes: NftAttribute[] | undefined =
item.content.metadata?.attributes?.map((x) => ({
trait: x.trait_type,
value: x.value,
}));

let listing: Listing | undefined = undefined;
const tensorListing = listings.data.userActiveListings.txs.find(
(t) => t.tx.mintOnchainId === item.id
);

if (tensorListing) {
listing = NodeBuilder.tensorListing(item.id, {
amount: ethers.utils.formatUnits(
tensorListing.tx.grossAmount,
this.decimals()
),
source: tensorListing.tx.source,
url: this.ctx!.dataSources.tensor.getListingUrl(item.id),
});
}

return NodeBuilder.nft(this.id(), {
address: item.id,
attributes,
collection,
compressed: item.compression?.compressed ?? false,
description: item.content?.metadata?.description || undefined,
image: item.content?.files?.at(0)?.uri || undefined,
listing,
metadataUri: item.content?.json_uri || undefined,
name: item.content?.metadata?.name || undefined,
owner: address,
token: atas.accounts[item.id].address.toBase58(),
return NodeBuilder.nft(this.id(), {
address: item.id,
attributes,
collection,
compressed: item.compression?.compressed ?? false,
description: item.content?.metadata?.description || undefined,
image: item.content?.files?.at(0)?.uri || undefined,
listing,
metadataUri: item.content?.json_uri || undefined,
name: item.content?.metadata?.name || undefined,
owner: address,
token: atas.accounts[item.id].address.toBase58(),
});
});
});

return createConnection(nodes, false, false);
return createConnection(nodes, false, false);
} catch (err) {
console.error(`Falling back to Solana RPC: ${err}`);
return super.getNftsForAddress(address, filters);
}
}

/**
Expand Down
Loading

1 comment on commit 69e841f

@vercel
Copy link

@vercel vercel bot commented on 69e841f Jun 23, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.