Skip to content

Commit

Permalink
Merge branch 'master' into wallet_sign_tx_url
Browse files Browse the repository at this point in the history
  • Loading branch information
denbite committed Jan 22, 2024
2 parents 6b5fd2f + ab4f8d3 commit 41b242d
Show file tree
Hide file tree
Showing 36 changed files with 1,219 additions and 400 deletions.
24 changes: 12 additions & 12 deletions docs/README_TYPEDOC.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"jest": "26.0.1",
"near-hello": "0.5.1",
"ts-jest": "26.5.6",
"typescript": "4.9.4"
"typescript": "4.9.4",
"near-workspaces": "3.4.0"
},
"files": [
"lib"
Expand Down
46 changes: 32 additions & 14 deletions packages/accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,14 @@ export class Account {
}

/**
* Sign a transaction to preform a list of actions and broadcast it using the RPC API.
* Sign a transaction to perform a list of actions and broadcast it using the RPC API.
* @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider }
*
* @param options The options for signing and sending the transaction.
* @param options.receiverId The NEAR account ID of the transaction receiver.
* @param options.actions The list of actions to be performed in the transaction.
* @param options.returnError Whether to return an error if the transaction fails.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the transaction.
*/
async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise<FinalExecutionOutcome> {
let txHash, signedTx;
Expand Down Expand Up @@ -382,9 +388,19 @@ export class Account {
return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]);
}

/**
* Execute function call
* @returns {Promise<FinalExecutionOutcome>}
/**
* Execute a function call.
* @param options The options for the function call.
* @param options.contractId The NEAR account ID of the smart contract.
* @param options.methodName The name of the method to be called on the smart contract.
* @param options.args The arguments to be passed to the method.
* @param options.gas The maximum amount of gas to be used for the function call.
* @param options.attachedDeposit The amount of NEAR tokens to be attached to the function call.
* @param options.walletMeta Metadata for wallet integration.
* @param options.walletCallbackUrl The callback URL for wallet integration.
* @param options.stringify A function to convert input arguments into bytes array
* @param options.jsContract Whether the contract is from JS SDK, automatically encodes args from JS SDK to binary.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the function call.
*/
async functionCall({ contractId, methodName, args = {}, gas = DEFAULT_FUNCTION_CALL_GAS, attachedDeposit, walletMeta, walletCallbackUrl, stringify, jsContract }: ChangeFunctionCallOptions): Promise<FinalExecutionOutcome> {
this.validateArgs(args);
Expand Down Expand Up @@ -461,9 +477,10 @@ export class Account {
/**
* Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance
*
* @param actions Actions to be included in the meta transaction
* @param blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction
* @param receiverId Receiver account of the meta transaction
* @param options Options for the transaction.
* @param options.actions Actions to be included in the meta transaction
* @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction
* @param options.receiverId Receiver account of the meta transaction
*/
async signedDelegate({
actions,
Expand Down Expand Up @@ -517,13 +534,14 @@ export class Account {
* Invoke a contract view function using the RPC API.
* @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function)
*
* @param viewFunctionCallOptions.contractId NEAR account where the contract is deployed
* @param viewFunctionCallOptions.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code
* @param viewFunctionCallOptions.args Any arguments to the view contract method, wrapped in JSON
* @param viewFunctionCallOptions.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json.
* @param viewFunctionCallOptions.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON.
* @param viewFunctionCallOptions.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary.
* @param viewFunctionCallOptions.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized).
* @param options Function call options.
* @param options.contractId NEAR account where the contract is deployed
* @param options.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code
* @param options.args Any arguments to the view contract method, wrapped in JSON
* @param options.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json.
* @param options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON.
* @param options.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary.
* @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized).
* @returns {Promise<any>}
*/

Expand Down
60 changes: 59 additions & 1 deletion packages/accounts/src/account_2fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export class Account2FA extends AccountMultisig {
/**
* Sign a transaction to preform a list of actions and broadcast it using the RPC API.
* @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction}
*
* @param options Options for the transaction.
* @param options.receiverId The NEAR account ID of the transaction receiver.
* @param options.actions The list of actions to be included in the transaction.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the transaction.
*/
async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise<FinalExecutionOutcome> {
await super.signAndSendTransaction({ receiverId, actions });
Expand All @@ -62,6 +67,11 @@ export class Account2FA extends AccountMultisig {

// default helpers for CH deployments of multisig

/**
* Deploy a multisig contract with 2FA and handle the deployment process.
* @param contractBytes - The bytecode of the multisig contract.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the deployment.
*/
async deployMultisig(contractBytes: Uint8Array) {
const { accountId } = this;

Expand Down Expand Up @@ -100,6 +110,13 @@ export class Account2FA extends AccountMultisig {
}
}

/**
* Disable 2FA with the option to clean up contract state.
* @param options Options for disabling 2FA.
* @param options.contractBytes The bytecode of the contract to deploy.
* @param options.cleanupContractBytes The bytecode of the cleanup contract (optional).
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the operation.
*/
async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) {
let cleanupActions = [];
if(cleanupContractBytes) {
Expand All @@ -123,6 +140,11 @@ export class Account2FA extends AccountMultisig {
return this.signAndSendTransactionWithAccount(this.accountId, actions);
}

/**
* Retrieves cleanup actions for disabling 2FA.
* @param cleanupContractBytes - The bytecode of the cleanup contract.
* @returns {Promise<Action[]>} - A promise that resolves to an array of cleanup actions.
*/
async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) {
const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => {
const cause = error.cause && error.cause.name;
Expand All @@ -141,6 +163,10 @@ export class Account2FA extends AccountMultisig {
] : [];
}

/**
* Retrieves key conversion actions for disabling 2FA.
* @returns {Promise<Action[]>} - A promise that resolves to an array of key conversion actions.
*/
async get2faDisableKeyConversionActions() {
const { accountId } = this;
const accessKeys = await this.getAccessKeys();
Expand All @@ -163,7 +189,8 @@ export class Account2FA extends AccountMultisig {
/**
* This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param)
* @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true}
* @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true}
* @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-manipulation/res/state_cleanup.wasm?raw=true}
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the operation.
*/
async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) {
const { stateStatus } = await this.checkMultisigCodeAndStateStatus();
Expand Down Expand Up @@ -193,6 +220,10 @@ export class Account2FA extends AccountMultisig {
});
}

/**
* Default implementation for sending the 2FA code.
* @returns {Promise<string>} - A promise that resolves to the request ID.
*/
async sendCodeDefault() {
const { accountId } = this;
const { requestId } = this.getRequest();
Expand All @@ -209,6 +240,10 @@ export class Account2FA extends AccountMultisig {
throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".');
}

/**
* Prompts the user to enter and verify the 2FA code.
* @returns {Promise<any>} - A promise that resolves to the verification result.
*/
async promptAndVerify() {
const method = await this.get2faMethod();
const securityCode = await this.getCode(method);
Expand All @@ -227,6 +262,11 @@ export class Account2FA extends AccountMultisig {
}
}

/**
* Verify the 2FA code using the default method.
* @param securityCode - The security code to verify.
* @returns {Promise<any>} A promise that resolves to the verification result.
*/
async verifyCodeDefault(securityCode: string) {
const { accountId } = this;
const request = this.getRequest();
Expand All @@ -241,6 +281,10 @@ export class Account2FA extends AccountMultisig {
});
}

/**
* Retrieves recovery methods for the account.
* @returns {Promise<{ accountId: string, data: any }>} - A promise that resolves to recovery methods data.
*/
async getRecoveryMethods() {
const { accountId } = this;
return {
Expand All @@ -249,6 +293,10 @@ export class Account2FA extends AccountMultisig {
};
}

/**
* Gets the 2FA method (kind and detail).
* @returns {Promise<{ kind: string, detail: string }>} A promise that resolves to the 2FA method.
*/
async get2faMethod() {
let { data } = await this.getRecoveryMethods();
if (data && data.length) {
Expand All @@ -259,6 +307,10 @@ export class Account2FA extends AccountMultisig {
return { kind, detail };
}

/**
* Generates a signature for the latest finalized block.
* @returns {Promise<{ blockNumber: string, blockNumberSignature: string }>} - A promise that resolves to the signature information.
*/
async signatureFor() {
const { accountId } = this;
const block = await this.connection.provider.block({ finality: 'final' });
Expand All @@ -268,6 +320,12 @@ export class Account2FA extends AccountMultisig {
return { blockNumber, blockNumberSignature };
}

/**
* Sends a signed JSON request to a specified path.
* @param path - The path for the request.
* @param body - The request body.
* @returns {Promise<any>} - A promise that resolves to the response from the helper.
*/
async postSignedJson(path, body) {
return await fetchJson(this.helperUrl + path, JSON.stringify({
...body,
Expand Down
38 changes: 37 additions & 1 deletion packages/accounts/src/account_multisig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,37 @@ export class AccountMultisig extends Account {
public storage: any;
public onAddRequestResult: (any) => any;

/**
* Constructs an instance of the `AccountMultisig` class.
* @param connection The NEAR connection object.
* @param accountId The NEAR account ID.
* @param options Additional options for the multisig account.
* @param options.storage Storage to store data related to multisig operations.
* @param options.onAddRequestResult Callback function to handle the result of adding a request.
*/
constructor(connection: Connection, accountId: string, options: any) {
super(connection, accountId);
this.storage = options.storage;
this.onAddRequestResult = options.onAddRequestResult;
}

/**
* Sign and send a transaction with the multisig account as the sender.
* @param receiverId - The NEAR account ID of the transaction receiver.
* @param actions - The list of actions to be included in the transaction.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the transaction.
*/
async signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise<FinalExecutionOutcome> {
return super.signAndSendTransaction({ receiverId, actions });
}

/**
* Sign and send a multisig transaction to add a request and confirm it.
* @param options Options for the multisig transaction.
* @param options.receiverId The NEAR account ID of the transaction receiver.
* @param options.actions The list of actions to be included in the transaction.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the transaction.
*/
async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise<FinalExecutionOutcome> {
const { accountId } = this;

Expand Down Expand Up @@ -91,10 +112,12 @@ export class AccountMultisig extends Account {
return result;
}

/*
/**
* This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state
* and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index.
* a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt.
* @param contractBytes The bytecode of the multisig contract.
* @returns {Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }>} A promise that resolves to the status of the code and state.
*/
async checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }> {
const u32_max = 4_294_967_295;
Expand Down Expand Up @@ -128,20 +151,33 @@ export class AccountMultisig extends Account {
}
}

/**
* Delete a multisig request by its ID.
* @param request_id The ID of the multisig request to be deleted.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the deletion.
*/
deleteRequest(request_id) {
return super.signAndSendTransaction({
receiverId: this.accountId,
actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)]
});
}

/**
* Delete all multisig requests associated with the account.
* @returns {Promise<void>} A promise that resolves when all requests are deleted.
*/
async deleteAllRequests() {
const request_ids = await this.getRequestIds();
if(request_ids.length) {
await Promise.all(request_ids.map((id) => this.deleteRequest(id)));
}
}

/**
* Delete unconfirmed multisig requests associated with the account.
* @returns {Promise<void>} A promise that resolves when unconfirmed requests are deleted.
*/
async deleteUnconfirmedRequests () {
// TODO: Delete in batch, don't delete unexpired
// TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically)
Expand Down
Loading

0 comments on commit 41b242d

Please sign in to comment.