Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: client interfaces for interacting with NEP standard contracts #1414

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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
124 changes: 124 additions & 0 deletions packages/client/src/contract-standards/fungible-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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
*
* See contract-standards/fungible-token.ts in @near-js/cookbook for example usage
*/
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';
196 changes: 196 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,196 @@
/**
* 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
*
* See contract-standards/non-fungible-token.ts in @near-js/cookbook for example usage
*/
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
Loading