Skip to content

Commit 816772b

Browse files
authored
Add BaseNft and Nft classes (#3)
1 parent 148e938 commit 816772b

12 files changed

+810
-241
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module.exports = {
4848
'import/no-duplicates': 'error',
4949
'unused-imports/no-unused-imports-ts': 'error',
5050
'default-case': 'error',
51+
'@typescript-eslint/member-ordering': 'error',
5152
'@typescript-eslint/no-floating-promises': 'error'
5253
}
5354
};

exploring-pioneer-1.0.0.tgz

17.2 KB
Binary file not shown.

src/api/nft-api.ts

+391
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
import {
2+
BaseNftsForCollection,
3+
GetNftsParams,
4+
GetOwnersForTokenResponse,
5+
NftMetadata,
6+
NftsForCollection,
7+
NftTokenType,
8+
OwnedBaseNft,
9+
OwnedBaseNftsResponse,
10+
OwnedNft,
11+
OwnedNftsResponse
12+
} from '../types/types';
13+
import { Alchemy } from './alchemy';
14+
import { paginateEndpoint, requestHttpWithBackoff } from '../internal/dispatch';
15+
import { fromHex, isHex } from '../util/util';
16+
import { BaseNft, Nft } from './nft';
17+
import {
18+
RawBaseNft,
19+
RawCollectionBaseNft,
20+
RawCollectionNft,
21+
RawGetBaseNftsForCollectionResponse,
22+
RawGetBaseNftsResponse,
23+
RawGetNftsForCollectionResponse,
24+
RawGetNftsResponse,
25+
RawNft,
26+
RawOwnedBaseNft,
27+
RawOwnedNft
28+
} from '../internal/raw-interfaces';
29+
30+
/**
31+
* Get the NFT metadata associated with the provided NFT.
32+
*
33+
* @param alchemy The Alchemy SDK instance.
34+
* @param baseNft The base NFT object to be used for the request.
35+
* @public
36+
*/
37+
export function getNftMetadata(
38+
alchemy: Alchemy,
39+
baseNft: BaseNft
40+
): Promise<NftMetadata>;
41+
42+
/**
43+
* Get the NFT metadata associated with the provided parameters.
44+
*
45+
* @param alchemy The Alchemy SDK instance.
46+
* @param contractAddress The contract address of the NFT.
47+
* @param tokenId Token id of the NFT as a hex string or integer.
48+
* @param tokenType Optionally specify the type of token to speed up the query.
49+
* @public
50+
*/
51+
export function getNftMetadata(
52+
alchemy: Alchemy,
53+
contractAddress: string,
54+
tokenId: number | string,
55+
tokenType?: NftTokenType
56+
): Promise<NftMetadata>;
57+
export function getNftMetadata(
58+
alchemy: Alchemy,
59+
contractAddressOrBaseNft: string | BaseNft,
60+
tokenId?: string | number,
61+
tokenType?: NftTokenType
62+
): Promise<NftMetadata> {
63+
if (typeof contractAddressOrBaseNft === 'string') {
64+
validateContractAddress(contractAddressOrBaseNft);
65+
return requestHttpWithBackoff(alchemy, 'getNFTMetadata', {
66+
contractAddress: contractAddressOrBaseNft,
67+
tokenId: normalizeTokenIdToNumber(tokenId!),
68+
tokenType: tokenType !== NftTokenType.UNKNOWN ? tokenType : undefined
69+
});
70+
} else {
71+
return requestHttpWithBackoff(alchemy, 'getNFTMetadata', {
72+
contractAddress: contractAddressOrBaseNft.address,
73+
tokenId: fromHex(contractAddressOrBaseNft.tokenId),
74+
tokenType:
75+
contractAddressOrBaseNft.tokenType !== NftTokenType.UNKNOWN
76+
? contractAddressOrBaseNft.tokenType
77+
: undefined
78+
});
79+
}
80+
}
81+
82+
/**
83+
* Fetches all NFTs for a given owner and yields them in an async iterable.
84+
*
85+
* This method returns the base NFTs that omit the associated metadata. To get
86+
* all NFTs with their associated metadata, use {@link getNftsPaginated}.
87+
*
88+
* This method pages through all page keys until all NFTs have been fetched.
89+
*
90+
* @param alchemy The Alchemy SDK instance.
91+
* @param params The parameters to use for the request.
92+
* Limit to 20 addresses.
93+
*/
94+
export function getBaseNftsPaginated(
95+
alchemy: Alchemy,
96+
params: GetNftsParams
97+
): AsyncIterable<OwnedBaseNft> {
98+
const paramsCopy = { ...params, withMetadata: false };
99+
return _getNftsPaginated(alchemy, paramsCopy);
100+
}
101+
102+
/**
103+
* Fetches all NFTs for a given owner and yields them in an async iterable.
104+
*
105+
* This method returns the full NFT for the owner. To get all NFTs without their
106+
* associated metadata, use {@link getBaseNftsPaginated}.
107+
*
108+
* This method pages through all page keys until all NFTs have been fetched.
109+
*
110+
* @param alchemy The Alchemy SDK instance.
111+
* @param params The parameters to use for the request.
112+
* Limit to 20 addresses.
113+
*/
114+
export function getNftsPaginated(
115+
alchemy: Alchemy,
116+
params: GetNftsParams
117+
): AsyncIterable<OwnedNft> {
118+
const paramsCopy = { ...params, withMetadata: true };
119+
return _getNftsPaginated(alchemy, paramsCopy) as AsyncIterable<OwnedNft>;
120+
}
121+
122+
/**
123+
* Fetches all NFTs for a given owner and yields them in an async iterable.
124+
*
125+
* @internal
126+
*/
127+
async function* _getNftsPaginated(
128+
alchemy: Alchemy,
129+
params: GetNftsParams
130+
): AsyncIterable<OwnedBaseNft | OwnedNft> {
131+
for await (const response of paginateEndpoint(
132+
alchemy,
133+
'getNFTs',
134+
'pageKey',
135+
params
136+
)) {
137+
for (const ownedNft of response.ownedNfts as
138+
| RawOwnedNft[]
139+
| RawOwnedBaseNft[]) {
140+
yield {
141+
nft: nftFromGetNftResponse(ownedNft),
142+
balance: parseInt(ownedNft.balance)
143+
};
144+
}
145+
}
146+
}
147+
148+
/**
149+
* Get all NFTs for an owner.
150+
*
151+
* This method returns the base NFTs that omit the associated metadata. To get
152+
* all NFTs with their associated metadata, use {@link getNfts}.
153+
*
154+
* @param alchemy The Alchemy SDK instance.
155+
* @param params The parameters to use for the request.
156+
* @public
157+
*/
158+
export async function getBaseNfts(
159+
alchemy: Alchemy,
160+
params: GetNftsParams
161+
): Promise<OwnedBaseNftsResponse> {
162+
const paramsCopy = { ...params, withMetadata: false };
163+
const response = await requestHttpWithBackoff<
164+
GetNftsParams,
165+
RawGetBaseNftsResponse
166+
>(alchemy, 'getNFTs', paramsCopy);
167+
return {
168+
ownedNfts: response.ownedNfts.map(res => ({
169+
nft: nftFromGetNftResponse(res),
170+
balance: parseInt(res.balance)
171+
})),
172+
pageKey: response.pageKey,
173+
totalCount: response.totalCount
174+
};
175+
}
176+
177+
/**
178+
* Get all NFTs for an owner.
179+
*
180+
* This method returns the full NFT for the owner. To get all NFTs without their
181+
* associated metadata, use {@link getBaseNfts}.
182+
*
183+
* @param alchemy The Alchemy SDK instance.
184+
* @param params The parameters to use for the request.
185+
* @public
186+
*/
187+
export async function getNfts(
188+
alchemy: Alchemy,
189+
params: GetNftsParams
190+
): Promise<OwnedNftsResponse> {
191+
const paramsCopy = { ...params, withMetadata: false };
192+
const response = await requestHttpWithBackoff<
193+
GetNftsParams,
194+
RawGetNftsResponse
195+
>(alchemy, 'getNFTs', paramsCopy);
196+
return {
197+
ownedNfts: response.ownedNfts.map(res => ({
198+
nft: nftFromGetNftResponse(res) as Nft,
199+
balance: parseInt(res.balance)
200+
})),
201+
pageKey: response.pageKey,
202+
totalCount: response.totalCount
203+
};
204+
}
205+
206+
/**
207+
* Get all base NFTs for a given contract address.
208+
*
209+
* This method returns the base NFTs that omit the associated metadata. To get
210+
* all NFTs with their associated metadata, use {@link getNftsForCollection}.
211+
*
212+
* @param alchemy The Alchemy SDK instance.
213+
* @param contractAddress The collection contract address to get all NFTs for.
214+
* @param pageKey Optional page key from an existing {@link BaseNftsForCollection}
215+
* or {@link NftsForCollection} response.
216+
* @beta
217+
*/
218+
// TODO: Add pagination for this endpoint.
219+
export async function getBaseNftsForCollection(
220+
alchemy: Alchemy,
221+
contractAddress: string,
222+
pageKey?: string
223+
): Promise<BaseNftsForCollection> {
224+
const response = await requestHttpWithBackoff<
225+
GetNftsForCollectionParams,
226+
RawGetBaseNftsForCollectionResponse
227+
>(alchemy, 'getNFTsForCollection', {
228+
contractAddress,
229+
startToken: pageKey,
230+
withMetadata: false
231+
});
232+
233+
return {
234+
nfts: response.nfts.map(res =>
235+
nftFromGetNftCollectionResponse(res, contractAddress)
236+
),
237+
pageKey: response.nextToken
238+
};
239+
}
240+
241+
/**
242+
* Get all NFTs for a given contract address.
243+
*
244+
* This method returns the full NFTs in the contract. To get all NFTs without
245+
* their associated metadata, use {@link getBaseNftsForCollection}.
246+
*
247+
* @param alchemy The Alchemy SDK instance.
248+
* @param contractAddress The collection contract address to get all NFTs for.
249+
* @param pageKey Optional page key from an existing {@link BaseNftsForCollection}
250+
* or {@link NftsForCollection} response.
251+
* @beta
252+
*/
253+
// TODO: add pagination for this endpoint.
254+
export async function getNftsForCollection(
255+
alchemy: Alchemy,
256+
contractAddress: string,
257+
pageKey?: string
258+
): Promise<NftsForCollection> {
259+
const response = await requestHttpWithBackoff<
260+
GetNftsForCollectionParams,
261+
RawGetNftsForCollectionResponse
262+
>(alchemy, 'getNFTsForCollection', {
263+
contractAddress,
264+
startToken: pageKey,
265+
withMetadata: true
266+
});
267+
268+
return {
269+
nfts: response.nfts.map(
270+
res => nftFromGetNftCollectionResponse(res, contractAddress) as Nft
271+
),
272+
pageKey: response.nextToken
273+
};
274+
}
275+
276+
/**
277+
* Gets all the owners for a given NFT contract address and token ID.
278+
*
279+
* @param alchemy The Alchemy SDK instance.
280+
* @param contractAddress The NFT contract address.
281+
* @param tokenId Token id of the NFT as a hex string or integer.
282+
* @beta
283+
*/
284+
export function getOwnersForToken(
285+
alchemy: Alchemy,
286+
contractAddress: string,
287+
tokenId: number | string
288+
): Promise<GetOwnersForTokenResponse>;
289+
290+
/**
291+
* Gets all the owners for a given NFT.
292+
*
293+
* @param alchemy The Alchemy SDK instance.
294+
* @param nft The NFT object to get the owners for.
295+
* @beta
296+
*/
297+
export function getOwnersForToken(
298+
alchemy: Alchemy,
299+
nft: BaseNft
300+
): Promise<GetOwnersForTokenResponse>;
301+
export function getOwnersForToken(
302+
alchemy: Alchemy,
303+
contractAddressOrNft: string | BaseNft,
304+
tokenId?: number | string
305+
): Promise<GetOwnersForTokenResponse> {
306+
if (typeof contractAddressOrNft === 'string') {
307+
validateContractAddress(contractAddressOrNft);
308+
return requestHttpWithBackoff(alchemy, 'getOwnersForToken', {
309+
contractAddress: contractAddressOrNft,
310+
tokenId: normalizeTokenIdToNumber(tokenId!)
311+
});
312+
} else {
313+
return requestHttpWithBackoff(alchemy, 'getOwnersForToken', {
314+
contractAddress: contractAddressOrNft.address,
315+
tokenId: fromHex(contractAddressOrNft.tokenId)
316+
});
317+
}
318+
}
319+
320+
/**
321+
* Helper method to convert a NFT response received from Alchemy backend to an
322+
* SDK NFT type.
323+
*
324+
* @internal
325+
*/
326+
function nftFromGetNftResponse(
327+
ownedNft: RawOwnedBaseNft | RawOwnedNft
328+
): Nft | BaseNft {
329+
if (isNftWithMetadata(ownedNft)) {
330+
return Nft.fromResponse(ownedNft, ownedNft.contract.address);
331+
} else {
332+
return BaseNft.fromResponse(ownedNft, ownedNft.contract.address);
333+
}
334+
}
335+
336+
/**
337+
* Helper method to convert a NFT response received from Alchemy backend to an
338+
* SDK NFT type.
339+
*
340+
* @internal
341+
*/
342+
function nftFromGetNftCollectionResponse(
343+
ownedNft: RawCollectionBaseNft | RawCollectionNft,
344+
contractAddress: string
345+
): Nft | BaseNft {
346+
if (isNftWithMetadata(ownedNft)) {
347+
return Nft.fromResponse(ownedNft, contractAddress);
348+
} else {
349+
return BaseNft.fromResponse(ownedNft, contractAddress);
350+
}
351+
}
352+
353+
/**
354+
* @internal
355+
*/
356+
// TODO: more comprehensive type check
357+
function isNftWithMetadata(response: RawBaseNft | RawNft): response is RawNft {
358+
return response.hasOwnProperty('title');
359+
}
360+
361+
/**
362+
* Helper method that returns the token ID input as an integer.
363+
*
364+
* @param tokenId The token ID as an integer or hex string.
365+
* @internal
366+
*/
367+
function normalizeTokenIdToNumber(tokenId: string | number): number {
368+
if (typeof tokenId === 'string') {
369+
if (isHex(tokenId)) {
370+
return fromHex(tokenId);
371+
} else {
372+
throw new Error(
373+
`${tokenId} is not a valid token ID number or hex string`
374+
);
375+
}
376+
} else {
377+
return tokenId;
378+
}
379+
}
380+
381+
// TODO: Port over validation from NFT API code, since backend error validation
382+
// doesn't always get surfaced properly.
383+
function validateContractAddress(contractAddress: string) {
384+
console.log('validating contract address', contractAddress);
385+
}
386+
387+
interface GetNftsForCollectionParams {
388+
contractAddress: string;
389+
startToken?: string;
390+
withMetadata: boolean;
391+
}

0 commit comments

Comments
 (0)