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

Wallet urls methods. #1

Merged
merged 11 commits into from
Nov 1, 2022
46 changes: 4 additions & 42 deletions packages/near-api-js/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import { Connection } from './connection';
import { baseDecode, baseEncode } from 'borsh';
import { PublicKey } from './utils/key_pair';
import { logWarning, PositionalArgsError } from './utils/errors';
import { parseRpcError, parseResultError } from './utils/rpc_errors';
import { ServerError } from './utils/rpc_errors';
import { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from './utils/logging';
import { parseResultError } from './utils/rpc_errors';
import { DEFAULT_FUNCTION_CALL_GAS } from './constants';

import exponentialBackoff from './utils/exponential-backoff';
Expand Down Expand Up @@ -123,12 +123,6 @@ export interface ViewFunctionCallOptions extends FunctionCallOptions {
blockQuery?: BlockReference;
}

interface ReceiptLogWithFailure {
receiptIds: [string];
logs: [string];
failure: ServerError;
}

interface StakedBalance {
validatorId: string;
amount?: string;
Expand Down Expand Up @@ -177,28 +171,6 @@ export class Account {
});
}

/** @hidden */
private printLogsAndFailures(contractId: string, results: [ReceiptLogWithFailure]) {
if (!process.env['NEAR_NO_LOGS']) {
for (const result of results) {
console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`);
this.printLogs(contractId, result.logs, '\t');
if (result.failure) {
console.warn(`\tFailure [${contractId}]: ${result.failure}`);
}
}
}
}

/** @hidden */
private printLogs(contractId: string, logs: string[], prefix = '') {
if (!process.env['NEAR_NO_LOGS']) {
for (const log of logs) {
console.log(`${prefix}Log [${contractId}]: ${log}`);
}
}
}

/**
* Create a signed transaction which can be broadcast to the network
* @param receiverId NEAR account receiving the transaction
Expand Down Expand Up @@ -254,17 +226,7 @@ export class Account {
throw new TypedError('nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.', 'RetriesExceeded');
}

const flatLogs = [result.transaction_outcome, ...result.receipts_outcome].reduce((acc, it) => {
if (it.outcome.logs.length ||
(typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object')) {
return acc.concat({
'receiptIds': it.outcome.receipt_ids,
'logs': it.outcome.logs,
'failure': typeof it.outcome.status.Failure != 'undefined' ? parseRpcError(it.outcome.status.Failure) : null
});
} else return acc;
}, []);
this.printLogsAndFailures(signedTx.transaction.receiverId, flatLogs);
printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result });

// Should be falsy if result.status.Failure is null
if (!returnError && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) {
Expand Down Expand Up @@ -537,7 +499,7 @@ export class Account {
});

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

return result.result && result.result.length > 0 && parse(Buffer.from(result.result));
Expand Down
69 changes: 69 additions & 0 deletions packages/near-api-js/src/utils/logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { FinalExecutionOutcome } from '../providers';
import { parseRpcError } from './rpc_errors';

const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS;

/**
* Parse and print details from a query execution response
* @param params
* @param params.contractId ID of the account/contract which made the query
* @param params.outcome the query execution response
*/
export function printTxOutcomeLogsAndFailures({
contractId,
outcome,
}: { contractId: string, outcome: FinalExecutionOutcome }) {
if (SUPPRESS_LOGGING) {
return;
}

const flatLogs = [outcome.transaction_outcome, ...outcome.receipts_outcome]
.reduce((acc, it) => {
const isFailure = typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object';
if (it.outcome.logs.length || isFailure) {
return acc.concat({
receiptIds: it.outcome.receipt_ids,
logs: it.outcome.logs,
failure: typeof it.outcome.status === 'object' && it.outcome.status.Failure !== undefined
? parseRpcError(it.outcome.status.Failure)
: null
});
} else {
return acc;
}
}, []);

for (const result of flatLogs) {
console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`);
printTxOutcomeLogs({
contractId,
logs: result.logs,
prefix: '\t',
});

if (result.failure) {
console.warn(`\tFailure [${contractId}]: ${result.failure}`);
}
}
}

/**
* Format and print log output from a query execution response
* @param params
* @param params.contractId ID of the account/contract which made the query
* @param params.logs log output from a query execution response
* @param params.prefix string to append to the beginning of each log
*/
export function printTxOutcomeLogs({
contractId,
logs,
prefix = '',
}: { contractId: string, logs: string[], prefix?: string }) {
if (SUPPRESS_LOGGING) {
return;
}

for (const log of logs) {
console.log(`${prefix}Log [${contractId}]: ${log}`);
}
}
78 changes: 60 additions & 18 deletions packages/near-api-js/src/wallet-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,17 @@ export class WalletConnection {
}

/**
* Redirects current page to the wallet authentication page.
* @param options An optional options object
* @param options.contractId The NEAR account where the contract is deployed
* @param options.successUrl URL to redirect upon success. Default: current url
* @param options.failureUrl URL to redirect upon failure. Default: current url
* Returns the sign in url for the wallet.
* @see {@link requestSignIn} For accepted options.
*
* @example
* ```js
* const wallet = new WalletConnection(near, 'my-app');
* // redirects to the NEAR Wallet
* wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' });
* // returns the url to the NEAR Wallet
* const signInLink = await wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' });
* ```
*/
async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) {
async requestSignInLink({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) {
const currentUrl = new URL(window.location.href);
const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX);
newUrl.searchParams.set('success_url', successUrl || currentUrl.href);
Expand All @@ -196,13 +193,33 @@ export class WalletConnection {
});
}

return newUrl.toString();
}

/**
* Redirects current page to the wallet authentication page.
* @param options An optional options object
* @param options.contractId The NEAR account where the contract is deployed
* @param options.successUrl URL to redirect upon success. Default: current url
* @param options.failureUrl URL to redirect upon failure. Default: current url
*
* @example
* ```js
* const wallet = new WalletConnection(near, 'my-app');
* // redirects to the NEAR Wallet
* wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' });
* ```
*/
async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) {
const newUrl = await this.requestSignInLink({ contractId, methodNames, successUrl, failureUrl });

window.location.assign(newUrl.toString());
}

/**
* Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet.
* Returns the url (to NEAR wallet) for the user to quickly sign for a transaction or batch of transactions.
*/
async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise<void> {
async requestSignTransactionsLink({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise<string> {
const currentUrl = new URL(window.location.href);
const newUrl = new URL('sign', this._walletBaseUrl);

Expand All @@ -213,30 +230,55 @@ export class WalletConnection {
newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href);
if (meta) newUrl.searchParams.set('meta', meta);

return newUrl.toString();
}

/**
* Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet.
*/
async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise<void> {
const newUrl = await this.requestSignTransactionsLink({ transactions, meta, callbackUrl });

window.location.assign(newUrl.toString());
}

/**
* @hidden
* Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet.
* Complete sign in for a given account id and public key.
* To be invoked by the developer in case you used {@link requestSignInLink} and you grabbed the callback manually.
* Parameters should come in the callback url from the wallet.
*/
async _completeSignInWithAccessKey() {
const currentUrl = new URL(window.location.href);
const publicKey = currentUrl.searchParams.get('public_key') || '';
const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(',');
const accountId = currentUrl.searchParams.get('account_id') || '';
async completeSignInWithAccessKeyLink(publicKey: string, allKeys: string, accountId: string) {
const allKeysArr = allKeys.split(',');
// TODO: Handle errors during login
if (accountId) {
const authData = {
accountId,
allKeys
allKeysArr
};
window.localStorage.setItem(this._authDataKey, JSON.stringify(authData));
if (publicKey) {
await this._moveKeyFromTempToPermanent(accountId, publicKey);
}
this._authData = authData;
}
}

/**
* @hidden
* Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet.
*/
async _completeSignInWithAccessKey() {
const currentUrl = new URL(window.location.href);
const publicKey = currentUrl.searchParams.get('public_key') || '';
const allKeys = currentUrl.searchParams.get('all_keys') || '';
const accountId = currentUrl.searchParams.get('account_id') || '';

await this.completeSignInWithAccessKeyLink(
publicKey,
allKeys,
accountId
);

currentUrl.searchParams.delete('public_key');
currentUrl.searchParams.delete('all_keys');
currentUrl.searchParams.delete('account_id');
Expand Down