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

Extend Contract class to enable view function calls without Account #1325

Merged
merged 4 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/little-chefs-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@near-js/accounts": minor
---

Extend Contract class to accept Connection object
109 changes: 10 additions & 99 deletions packages/accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ import {
FinalExecutionOutcome,
TypedError,
ErrorContext,
ViewStateResult,
AccountView,
AccessKeyView,
AccessKeyViewRaw,
CodeResult,
AccessKeyList,
AccessKeyInfoView,
FunctionCallPermissionView,
Expand All @@ -31,11 +29,12 @@ import {
Logger,
parseResultError,
DEFAULT_FUNCTION_CALL_GAS,
printTxOutcomeLogs,
printTxOutcomeLogsAndFailures,
} from '@near-js/utils';

import { Connection } from './connection';
import { viewFunction, viewState } from './utils';
import { ChangeFunctionCallOptions, IntoConnection, ViewFunctionCallOptions } from './interface';

const {
addKey,
Expand Down Expand Up @@ -91,50 +90,6 @@ export interface SignAndSendTransactionOptions {
returnError?: boolean;
}

/**
* Options used to initiate a function call (especially a change function call)
* @see {@link Account#viewFunction | viewFunction} to initiate a view function call
*/
export interface FunctionCallOptions {
/** The NEAR account id where the contract is deployed */
contractId: string;
/** The name of the method to invoke */
methodName: string;
/**
* named arguments to pass the method `{ messageText: 'my message' }`
*/
args?: object;
/** max amount of gas that method call can use */
gas?: bigint;
/** amount of NEAR (in yoctoNEAR) to send together with the call */
attachedDeposit?: bigint;
/**
* Convert input arguments into bytes array.
*/
stringify?: (input: any) => Buffer;
/**
* Is contract from JS SDK, automatically encodes args from JS SDK to binary.
*/
jsContract?: boolean;
}

export interface ChangeFunctionCallOptions extends FunctionCallOptions {
/**
* Metadata to send the NEAR Wallet if using it to sign transactions.
* @see RequestSignTransactionsOptions
*/
walletMeta?: string;
/**
* Callback url to send the NEAR Wallet if using it to sign transactions.
* @see RequestSignTransactionsOptions
*/
walletCallbackUrl?: string;
}
export interface ViewFunctionCallOptions extends FunctionCallOptions {
parse?: (response: Uint8Array) => any;
blockQuery?: BlockReference;
}

interface StakedBalance {
validatorId: string;
amount?: string;
Expand All @@ -153,18 +108,10 @@ interface SignedDelegateOptions {
receiverId: string;
}

function parseJsonFromRawResponse(response: Uint8Array): any {
return JSON.parse(Buffer.from(response).toString());
}

function bytesJsonStringify(input: any): Buffer {
return Buffer.from(JSON.stringify(input));
}

/**
* This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}.
*/
export class Account {
export class Account implements IntoConnection {
readonly connection: Connection;
readonly accountId: string;

Expand All @@ -173,6 +120,10 @@ export class Account {
this.accountId = accountId;
}

getConnection(): Connection {
return this.connection;
}

/**
* Returns basic NEAR account information via the `view_account` RPC query method
* @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account)
Expand Down Expand Up @@ -544,38 +495,8 @@ export class Account {
* @returns {Promise<any>}
*/

async viewFunction({
contractId,
methodName,
args = {},
parse = parseJsonFromRawResponse,
stringify = bytesJsonStringify,
jsContract = false,
blockQuery = { finality: 'optimistic' }
}: ViewFunctionCallOptions): Promise<any> {
let encodedArgs;

this.validateArgs(args);

if (jsContract) {
encodedArgs = this.encodeJSContractArgs(contractId, methodName, Object.keys(args).length > 0 ? JSON.stringify(args) : '');
} else {
encodedArgs = stringify(args);
}

const result = await this.connection.provider.query<CodeResult>({
request_type: 'call_function',
...blockQuery,
account_id: jsContract ? this.connection.jsvmAccountId : contractId,
method_name: jsContract ? 'view_js_contract' : methodName,
args_base64: encodedArgs.toString('base64')
});

if (result.logs) {
printTxOutcomeLogs({ contractId, logs: result.logs });
}

return result.result && result.result.length > 0 && parse(Buffer.from(result.result));
async viewFunction(options: ViewFunctionCallOptions): Promise<any> {
return await viewFunction(this.connection, options);
}

/**
Expand All @@ -587,17 +508,7 @@ export class Account {
* @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized).
*/
async viewState(prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' }): Promise<Array<{ key: Buffer; value: Buffer }>> {
const { values } = await this.connection.provider.query<ViewStateResult>({
request_type: 'view_state',
...blockQuery,
account_id: this.accountId,
prefix_base64: Buffer.from(prefix).toString('base64')
});

return values.map(({ key, value }) => ({
key: Buffer.from(key, 'base64'),
value: Buffer.from(value, 'base64')
}));
return await viewState(this.connection, this.accountId, prefix, blockQuery);
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/accounts/src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Signer, InMemorySigner } from '@near-js/signers';
import { Provider, JsonRpcProvider } from '@near-js/providers';
import { IntoConnection } from './interface';

/**
* @param config Contains connection info details
Expand Down Expand Up @@ -32,7 +33,7 @@ function getSigner(config: any): Signer {
/**
* Connects an account to a given network via a given provider
*/
export class Connection {
export class Connection implements IntoConnection {
readonly networkId: string;
readonly provider: Provider;
readonly signer: Signer;
Expand All @@ -45,6 +46,10 @@ export class Connection {
this.jsvmAccountId = jsvmAccountId;
}

getConnection(): Connection {
return this;
}

/**
* @param config Contains connection info details
*/
Expand Down
43 changes: 36 additions & 7 deletions packages/accounts/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
ArgumentSchemaError,
ConflictingOptions,
} from "./errors";
import { IntoConnection } from "./interface";
import { Connection } from "./connection";
import { viewFunction } from "./utils";

// Makes `function.name` return given name
function nameFunction(name: string, body: (args?: any[]) => any) {
Expand Down Expand Up @@ -84,6 +87,7 @@ const isObject = (x: any) =>
Object.prototype.toString.call(x) === "[object Object]";

interface ChangeMethodOptions {
signerAccount?: Account;
args: object;
methodName: string;
gas?: bigint;
Expand Down Expand Up @@ -153,7 +157,9 @@ export interface ContractMethods {
* ```
*/
export class Contract {
readonly account: Account;
/** @deprecated */
readonly account?: Account;
readonly connection: Connection;
readonly contractId: string;
readonly lve: LocalViewExecution;

Expand All @@ -163,13 +169,22 @@ export class Contract {
* @param options NEAR smart contract methods that your application will use. These will be available as `contract.methodName`
*/
constructor(
account: Account,
connection: IntoConnection,
contractId: string,
options: ContractMethods
) {
this.account = account;
this.connection = connection.getConnection();
if (connection instanceof Account) {
const deprecate = depd(
"new Contract(account, contractId, options)"
);
deprecate(
"use `new Contract(connection, contractId, options)` instead"
);
this.account = connection;
}
this.contractId = contractId;
this.lve = new LocalViewExecution(account);
this.lve = new LocalViewExecution(connection);
const {
viewMethods = [],
changeMethods = [],
Expand Down Expand Up @@ -235,7 +250,16 @@ export class Contract {
}
}

return this.account.viewFunction({
if (this.account) {
return this.account.viewFunction({
contractId: this.contractId,
methodName: name,
args,
...options,
});
}

return viewFunction(this.connection, {
contractId: this.contractId,
methodName: name,
args,
Expand Down Expand Up @@ -263,7 +287,7 @@ export class Contract {
"contract.methodName(args, gas, amount)"
);
deprecate(
"use `contract.methodName({ args, gas?, amount?, callbackUrl?, meta? })` instead"
"use `contract.methodName({ signerAccount, args, gas?, amount?, callbackUrl?, meta? })` instead"
);
args[0] = {
args: args[0],
Expand All @@ -283,6 +307,7 @@ export class Contract {
}

private async _changeMethod({
signerAccount,
args,
methodName,
gas,
Expand All @@ -292,7 +317,11 @@ export class Contract {
}: ChangeMethodOptions) {
validateBNLike({ gas, amount });

const rawResult = await this.account.functionCall({
const account = this.account || signerAccount;

if (!account) throw new Error(`signerAccount must be specified`);

const rawResult = await account.functionCall({
contractId: this.contractId,
methodName,
args,
Expand Down
10 changes: 6 additions & 4 deletions packages/accounts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ export {
Account,
AccountBalance,
AccountAuthorizedApp,
SignAndSendTransactionOptions,
FunctionCallOptions,
ChangeFunctionCallOptions,
ViewFunctionCallOptions,
SignAndSendTransactionOptions
} from './account';
export { Account2FA } from './account_2fa';
export {
Expand Down Expand Up @@ -37,3 +34,8 @@ export {
MultisigDeleteRequestRejectionError,
MultisigStateStatus,
} from './types';
export {
FunctionCallOptions,
ChangeFunctionCallOptions,
ViewFunctionCallOptions,
} from './interface';
50 changes: 50 additions & 0 deletions packages/accounts/src/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { BlockReference } from "@near-js/types";
import type { Connection } from "./connection";

export interface IntoConnection {
getConnection(): Connection;
}

/**
* Options used to initiate a function call (especially a change function call)
* @see {@link Account#viewFunction | viewFunction} to initiate a view function call
*/
export interface FunctionCallOptions {
/** The NEAR account id where the contract is deployed */
contractId: string;
/** The name of the method to invoke */
methodName: string;
/**
* named arguments to pass the method `{ messageText: 'my message' }`
*/
args?: object;
/** max amount of gas that method call can use */
gas?: bigint;
/** amount of NEAR (in yoctoNEAR) to send together with the call */
attachedDeposit?: bigint;
/**
* Convert input arguments into bytes array.
*/
stringify?: (input: any) => Buffer;
/**
* Is contract from JS SDK, automatically encodes args from JS SDK to binary.
*/
jsContract?: boolean;
}

export interface ChangeFunctionCallOptions extends FunctionCallOptions {
/**
* Metadata to send the NEAR Wallet if using it to sign transactions.
* @see RequestSignTransactionsOptions
*/
walletMeta?: string;
/**
* Callback url to send the NEAR Wallet if using it to sign transactions.
* @see RequestSignTransactionsOptions
*/
walletCallbackUrl?: string;
}
export interface ViewFunctionCallOptions extends FunctionCallOptions {
parse?: (response: Uint8Array) => any;
blockQuery?: BlockReference;
}
Loading
Loading