Skip to content

Commit

Permalink
feat: client interfaces for interacting with NEP standard contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
mpeterdev committed Oct 22, 2024
1 parent aba00c1 commit 35ce6c8
Show file tree
Hide file tree
Showing 14 changed files with 2,570 additions and 51 deletions.
6 changes: 6 additions & 0 deletions .changeset/four-swans-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@near-js/client": minor
"@near-js/cookbook": minor
---

Introduce interfaces for interacting with contracts which implement certain NEP standards
4 changes: 3 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"@near-js/types": "workspace:*",
"@near-js/utils": "workspace:*",
"@noble/hashes": "1.3.3",
"isomorphic-fetch": "3.0.0"
"isomorphic-fetch": "3.0.0",
"near-contract-standards": "^2.0.0",
"near-sdk-js": "^2.0.0"
},
"keywords": [],
"author": "",
Expand Down
122 changes: 122 additions & 0 deletions packages/client/src/contract-standards/fungible-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Interface for Fungible Token contracts covering the following standards:
* - Fungible Token Standard: https://github.com/near/NEPs/blob/master/neps/nep-0141.md
* - Fungible Token Metadata: https://github.com/near/NEPs/blob/master/neps/nep-0148.md
*/
import { functionCall } from "../transactions/actions";
import type {
FungibleTokenMetadata,
FungibleTokenMetadataProvider,
FungibleTokenCore,
} from "near-contract-standards/lib";
import type { FunctionCallParams, ViewParams } from "../interfaces";
import type { FinalExecutionOutcome } from "@near-js/types";
import { view } from "../view";

type FungibleTokenParams<T extends keyof (FungibleTokenCore)> = Omit<
FunctionCallParams,
"method" | "args"
> & {
args: Parameters<(FungibleTokenCore)[T]>[0];
};

type FungibleTokenViewParams<T extends keyof (FungibleTokenCore & FungibleTokenMetadataProvider)> = Omit<
ViewParams,
"method" | "args"
> & {
args: Parameters<(FungibleTokenCore & FungibleTokenMetadataProvider)[T]>[0];
};

type FungibleTokenReturn<T extends keyof (FungibleTokenCore)> = {
outcome: FinalExecutionOutcome;
result: ReturnType<(FungibleTokenCore)[T]>;
};

type FungibleTokenViewReturn<T extends keyof (FungibleTokenCore & FungibleTokenMetadataProvider)> =
ReturnType<(FungibleTokenCore & FungibleTokenMetadataProvider)[T]>;

export async function ftTransfer(
params: FungibleTokenParams<"ft_transfer">
): Promise<FungibleTokenReturn<"ft_transfer">> {
// bigints go over the wire as strings
const {args, ...otherParams} = params;
const convertedArgs = {
...args,
amount: args.amount ? args.amount.toString() : undefined,
};
const { outcome } = await functionCall({
args: convertedArgs,
...otherParams,
method: "ft_transfer",
deposit: 1n,
});

return {
outcome,
result: undefined,
};
}

/**
* TODO This function is untested
*/
export async function ftTransferCall(
params: FungibleTokenParams<"ft_transfer_call">
): Promise<FungibleTokenReturn<"ft_transfer_call">> {
// bigints go over the wire as strings
const {args, ...otherParams} = params;
const convertedArgs = {
...args,
amount: args.amount ? args.amount.toString() : undefined,
};
const { outcome, result } = await functionCall({
args: convertedArgs,
...otherParams,
method: "ft_transfer_call",
deposit: 1n,
});

return {
outcome,
// result: result as string,
result: BigInt(result as string),
};
}

export async function ftTotalSupply(
params: FungibleTokenViewParams<"ft_total_supply">
): Promise<FungibleTokenViewReturn<"ft_total_supply">> {
const result = await view({
...params,
method: "ft_total_supply",
});

return BigInt(result as string);
}

export async function ftBalanceOf(
params: FungibleTokenViewParams<"ft_balance_of">
): Promise<FungibleTokenViewReturn<"ft_balance_of">> {
const result = await view({
...params,
method: "ft_balance_of",
});

return BigInt(result as string);
}

/**
* Fungible Token Metadata is techically a separate NEP standard, but it's
* included here for convenience
* NEP: https://github.com/near/NEPs/blob/master/neps/nep-0148.md
*/
export async function ftMetadata(
params: FungibleTokenViewParams<"ft_metadata">
): Promise<FungibleTokenViewReturn<"ft_metadata">> {
const result = await view({
...params,
method: "ft_metadata",
});

return result as FungibleTokenMetadata;
}
3 changes: 3 additions & 0 deletions packages/client/src/contract-standards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './storage-management';
export * from './fungible-token';
export * from './non-fungible-token';
194 changes: 194 additions & 0 deletions packages/client/src/contract-standards/non-fungible-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* Interface for Non-Fungible Token contracts covering the following standards:
* - Non Fungible Token Standard: https://github.com/near/NEPs/blob/master/neps/nep-0171.md
* - Non Fungible Token Metadata: https://github.com/near/NEPs/blob/master/neps/nep-0177.md
* - Non Fungible Token Enumeration: https://github.com/near/NEPs/blob/master/neps/nep-0181.md
*/
import { functionCall } from "../transactions/actions";
import type {
NonFungibleTokenCore,
NonFungibleTokenEnumeration,
NonFungibleTokenMetadataProvider,
NFTContractMetadata,
// TokenMetadata,
Token
} from "near-contract-standards/lib";
import type {
FunctionCallParams,
ViewParams
} from "../interfaces";
import type { FinalExecutionOutcome } from "@near-js/types";
import { view } from "../view";

type NFTParams<T extends keyof (NonFungibleTokenCore)> = Omit<
FunctionCallParams,
"method" | "args"
> & {
args: Parameters<(NonFungibleTokenCore)[T]>[0];
};

type NFTViewParams<T extends keyof (NonFungibleTokenCore & NonFungibleTokenEnumeration & NonFungibleTokenMetadataProvider)> = Omit<
ViewParams,
"method" | "args"
> & {
args: Parameters<(NonFungibleTokenCore & NonFungibleTokenEnumeration & NonFungibleTokenMetadataProvider)[T]>[0];
};

type NFTReturn<T extends keyof (NonFungibleTokenCore)> = {
outcome: FinalExecutionOutcome;
result: ReturnType<(NonFungibleTokenCore)[T]>;
};

type NFTViewReturn<T extends keyof (NonFungibleTokenCore & NonFungibleTokenEnumeration & NonFungibleTokenMetadataProvider)> =
ReturnType<(NonFungibleTokenCore & NonFungibleTokenEnumeration & NonFungibleTokenMetadataProvider)[T]>;

export async function nftTransfer(
params: NFTParams<"nft_transfer">
): Promise<NFTReturn<"nft_transfer">> {
const { outcome } = await functionCall({
...params,
method: "nft_transfer",
deposit: 1n,
});

return {
outcome,
result: undefined,
};
}

export async function nftTransferCall(
params: NFTParams<"nft_transfer_call">
): Promise<NFTReturn<"nft_transfer_call">> {
const { outcome, result } = await functionCall({
...params,
method: "nft_transfer_call",
deposit: 1n,
});

return {
outcome,
result: result as string,
};
}

export async function nftToken(
params: NFTViewParams<"nft_token">
): Promise<NFTViewReturn<"nft_token">> {
const result = await view({
...params,
method: "nft_token",
});

return result as Token;
}

// ! NOTE: according to the NEP, this function should return a string but
// ! the contract standards lib incorrectly implements it as a number, therefore
// ! we break from usage of the lib and implement our own return type. We expect
// ! a string then cast to bigint for convenience
export async function nftTotalSupply(
params: NFTViewParams<"nft_total_supply">
): Promise<bigint> {
const result = await view({
...params,
method: "nft_total_supply",
});

if (typeof result === "string" || typeof result === "number") {
// technically we shouldn't allow number, but we will allow it in case
// the contract is built against the incorrect near-sdk-js spec
try {
const bi = BigInt(result);
return bi;
} catch (e) {
throw new Error("nft_total_supply result could not be converted to bigint");
}
}
throw new Error("nft_total_supply did not return a string or number");
}

// ! NOTE: according to the NEP, this function should return a string but
// ! the contract standards lib incorrectly implements it as a number, therefore
// ! we break from usage of the lib and implement our own return type. We expect
// ! a string then cast to bigint for convenience
export async function nftSupplyForOwner(
params: NFTViewParams<"nft_supply_for_owner">
): Promise<bigint> {
const result = await view({
...params,
method: "nft_supply_for_owner",
});

if (typeof result === "string" || typeof result === "number") {
// technically we shouldn't allow number, but we will allow it in case
// the contract is built against the incorrect near-sdk-js spec
try {
const bi = BigInt(result);
return bi;
} catch (e) {
throw new Error("nft_supply_for_owner result could not be converted to bigint");
}
}
throw new Error("nft_supply_for_owner did not return a string or number");
}

// ! Convert `from_index` to bigint | null to match the NEP
type NFTTokensParams = Omit<NFTViewParams<"nft_tokens">, "args"> & {
args: Omit<NFTViewParams<"nft_tokens">["args"], "from_index"> & {
from_index: bigint | null;
};
};
export async function nftTokens(
params: NFTTokensParams
): Promise<NFTViewReturn<"nft_tokens">> {
// bigints go over the wire as strings
const {args, ...otherParams} = params;
const convertedArgs = {
...args,
from_index: args.from_index ? args.from_index.toString() : null,
};
const result = await view({
args: convertedArgs,
...otherParams,
method: "nft_tokens",
});

return result as Token[];
}

// ! Convert `from_index` to bigint | null to match the NEP
type NFTTokensForOwnerParams = Omit<NFTViewParams<"nft_tokens_for_owner">, "args"> & {
args: Omit<NFTViewParams<"nft_tokens_for_owner">["args"], "from_index"> & {
from_index: bigint | null;
};
};

export async function nftTokensForOwner(
params: NFTTokensForOwnerParams
): Promise<NFTViewReturn<"nft_tokens_for_owner">> {
// bigints go over the wire as strings
const {args, ...otherParams} = params;
const convertedArgs = {
...args,
from_index: args.from_index ? args.from_index.toString() : null,
};
const result = await view({
args: convertedArgs,
...otherParams,
method: "nft_tokens_for_owner",
});

return result as Token[];
}

export async function nftMetadata(
params: NFTViewParams<"nft_metadata">
): Promise<NFTViewReturn<"nft_metadata">> {
const result = await view({
...params,
method: "nft_metadata",
});

return result as NFTContractMetadata;
}
Loading

0 comments on commit 35ce6c8

Please sign in to comment.