Skip to content

Commit

Permalink
Merge pull request #223 from near/account-with-balance-dont-use-relayer
Browse files Browse the repository at this point in the history
Implement Fallback to User Balance for Transactions When Relayer Fails
  • Loading branch information
Pessina authored Jun 11, 2024
2 parents a2fd2a5 + f3fda14 commit 67488cc
Show file tree
Hide file tree
Showing 20 changed files with 1,259 additions and 577 deletions.
2 changes: 1 addition & 1 deletion packages/near-fast-auth-signer-e2e-tests/test-app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function App() {
)}
<button
type="button"
data-test-id="sign-transaction-button"
data-testid="sign-transaction-button"
onClick={() => {
fastAuthWallet
.signAndSendTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import { getRandomEmailAndAccountId } from '../utils/email';

const testUserUidList: string[] = [];

let isAdminInitialized = false;

test.beforeAll(async () => {
if (isServiceAccountAvailable()) {
if (isServiceAccountAvailable() && !isAdminInitialized) {
initializeAdmin();
isAdminInitialized = true;
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Account, Connection, Contract } from '@near-js/accounts';
import { KeyPair } from '@near-js/crypto';
import { InMemoryKeyStore } from '@near-js/keystores';
import { test, expect } from '@playwright/test';
import { test, expect, Page } from '@playwright/test';

import { getFastAuthIframe } from '../../utils/constants';
import { createAccount, initializeAdmin, isServiceAccountAvailable } from '../../utils/createAccount';
Expand All @@ -10,16 +10,20 @@ import { TestDapp } from '../models/TestDapp';

const { describe, beforeAll } = test;

let page;
let page: Page;
const userFAK = KeyPair.fromRandom('ed25519');
const userLAK = KeyPair.fromRandom('ed25519');
let accountId;
let accountId: string;

let isAdminInitialized = false;

describe('Sign transaction', () => {
beforeAll(async ({ browser }, { workerIndex }) => {
if (isServiceAccountAvailable()) {
if (isServiceAccountAvailable() && !isAdminInitialized) {
initializeAdmin();
isAdminInitialized = true;
}

const context = await browser.newContext();
page = await context.newPage();
const testDapp = new TestDapp(page);
Expand Down Expand Up @@ -71,15 +75,17 @@ describe('Sign transaction', () => {
await page.goto('/');
const walletSelector = page.locator('#ws-loaded');
await expect(walletSelector).toBeVisible();
await page.locator('data-test-id=sign-transaction-button').click();
await page.getByTestId('sign-transaction-button').click();

await expect(getFastAuthIframe(page).getByText(accountId)).toBeVisible();
await expect(getFastAuthIframe(page).getByText('0.02 NEAR')).toBeVisible();

await getFastAuthIframe(page).locator('data-test-id=more-details-button').click();
await getFastAuthIframe(page).locator('data-test-id=function-call-button').click();
// Unstable due to GeckoAPI rate limits
// await expect(getFastAuthIframe(page).getByTestId('total-right-side-content')).not.toHaveText('$0.00');

await getFastAuthIframe(page).getByTestId('more-details-button').click();
await getFastAuthIframe(page).getByTestId('function-call-button').click();

await expect(getFastAuthIframe(page).getByText(/v1.social08.testnet/)).toBeVisible();
await expect(getFastAuthIframe(page).getByText(/v1.social08.testnet/)).toHaveCount(2);
await expect(getFastAuthIframe(page).getByText(/"fast-auth-e2e-test": "true"/)).toBeVisible();
});

Expand All @@ -94,7 +100,7 @@ describe('Sign transaction', () => {
retrievalKeypair: KeyPair.fromRandom('ed25519')
});

await page.locator('data-test-id=sign-transaction-button').click();
await page.getByTestId('sign-transaction-button').click();
await getFastAuthIframe(page).locator('data-test-id=confirm-transaction-button').click();

await expect(getFastAuthIframe(page).getByText('You are not authenticated or there has been an indexer failure')).toBeVisible();
Expand All @@ -111,7 +117,7 @@ describe('Sign transaction', () => {
retrievalKeypair: userFAK
});

await page.locator('data-test-id=sign-transaction-button').click();
await page.getByTestId('sign-transaction-button').click();
await getFastAuthIframe(page).locator('data-test-id=confirm-transaction-button').click();
const socialdbContract = new Contract(new Account(Connection.fromConfig({
networkId: 'testnet',
Expand All @@ -120,12 +126,12 @@ describe('Sign transaction', () => {
}), 'dontcare'), 'v1.social08.testnet', {
viewMethods: ['get'],
changeMethods: [],
}) as Contract & { get: (args) => Promise<string> };
}) as Contract & { get: (_args) => Promise<string> };

await expect(getFastAuthIframe(page).getByText('You are not authenticated or there has been an indexer failure')).not.toBeVisible();
await expect(page.locator('#nfw-connect-iframe')).not.toBeVisible();

const result = await new Promise((resolve) => { setTimeout(resolve, 5000); }).then(() => socialdbContract.get({ keys: [`${accountId}/**`] }));
expect(result).toEqual({ [accountId]: { 'fast-auth-e2e-test': 'true' } });
});
});
});
22 changes: 18 additions & 4 deletions packages/near-fast-auth-signer-e2e-tests/utils/createAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { sha256 } from 'js-sha256';
import {
KeyPair,
} from 'near-api-js';
import { NewAccountResponse } from 'near-fast-auth-signer/src/api/types';
import { CLAIM, getUserCredentialsFrpSignature } from 'near-fast-auth-signer/src/utils/mpc-service';

import { serviceAccount } from './serviceAccount';
Expand All @@ -26,6 +27,12 @@ export const deleteAccount = async (userUid: string) => {
}
};

const addAccountPublicKeyToFirestore = async (accountId: string, publicKey: string) => {
const docRef = admin.firestore().collection('publicKeys').doc(publicKey);

await docRef.set({ accountId });
};

export const createAccount = async ({
email, accountId, FAKs, LAKs, oidcKeyPair
}: {
Expand Down Expand Up @@ -112,10 +119,17 @@ export const createAccount = async ({
};

const createAccountResponse = await fetch('https://mpc-recovery-leader-testnet.api.pagoda.co/new_account', options);
return {
createAccountResponse: await createAccountResponse.json(),
userUid: testUserRecord.uid
};
const createAccountResponseJson: NewAccountResponse = await createAccountResponse.json();

if (createAccountResponseJson.type === 'ok') {
await Promise.all(createAccountResponseJson.create_account_options.full_access_keys
.map((publicKey) => addAccountPublicKeyToFirestore(createAccountResponseJson.near_account_id, publicKey)));
return {
createAccountResponse: createAccountResponseJson,
userUid: testUserRecord.uid
};
}
throw new Error(`Failed to create account: ${createAccountResponseJson.type}`);
};

export const generateKeyPairs = (count:number) => Array.from({ length: count }, () => Object.freeze(KeyPair.fromRandom('ED25519')));
Expand Down
11 changes: 9 additions & 2 deletions packages/near-fast-auth-signer/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ module.exports = {
files: ['*.ts', '*.tsx'],
}],
ignorePatterns: ['dist'],
plugins: [
'@typescript-eslint',
],
extends: ['airbnb', 'plugin:import/errors', 'plugin:import/typescript'],
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],

'linebreak-style': 0,
'react/require-default-props': 'off',
'react/prop-types': 'off',
Expand Down Expand Up @@ -92,6 +95,10 @@ module.exports = {
],
'arrow-parens': ['error', 'always'],
'key-spacing': ['warn', { align: 'value', mode: 'minimum' }],
'one-var-declaration-per-line': ['error', 'initializations']
'one-var-declaration-per-line': ['error', 'initializations'],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
},
};
15 changes: 8 additions & 7 deletions packages/near-fast-auth-signer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"crypto-browserify": "^3.12.0",
"data-loader": "^3.8.4",
"debug": "^4.3.4",
"eslint-plugin-jsx-a11y": "6.7.1",
"ethers": "^6.11.1",
"firebase": "^10.1.0",
"https-browserify": "^1.0.0",
Expand Down Expand Up @@ -85,7 +86,7 @@
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/eslint-parser": "^7.21.8",
"@babel/eslint-parser": "^7.24.1",
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.10.1",
"@sentry/webpack-plugin": "^2.14.0",
Expand All @@ -98,16 +99,16 @@
"@types/lodash.debounce": "^4.0.9",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"@typescript-eslint/eslint-plugin": "^5.59.6",
"@typescript-eslint/parser": "^5.59.6",
"@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/parser": "^7.5.0",
"babel-loader": "^9.1.2",
"css-loader": "^6.7.3",
"eslint": "^8.40.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.1",
Expand Down
6 changes: 4 additions & 2 deletions packages/near-fast-auth-signer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Devices from './components/Devices/Devices';
import Login from './components/Login/Login';
import RemoveTrailingSlash from './components/RemoveTrailingSlash/RemoveTrailingSlash';
import RpcRoute from './components/RpcRoute/RpcRoute';
import Sign from './components/Sign/Sign';
import SignTemplate from './components/Sign/SignTemplate';
import SignMultichain from './components/SignMultichain/SignMultichain';
import VerifyEmailPage from './components/VerifyEmail/verify-email';
import FastAuthController from './lib/controller';
Expand Down Expand Up @@ -67,7 +67,9 @@ export default function App() {
<Route path="rpc" element={<RpcRoute />} />
<Route path="create-account" element={<CreateAccount />} />
<Route path="add-device" element={<AddDevice />} />
<Route path="sign" element={<Sign />} />
{/* TODO: change the path for the delegates it's a breaking change that need to be done in sync with integrators */}
<Route path="sign" element={<SignTemplate signMethod="delegate" />} />
<Route path="sign-transaction" element={<SignTemplate signMethod="transaction" />} />
{/* TODO: This isn't available on mainnet, and isn't production ready, clean the code for production release */}
{environment.NETWORK_ID === 'testnet' && <Route path="sign-multichain" element={<SignMultichain />} />}
<Route path="verify-email" element={<VerifyEmailPage />} />
Expand Down
24 changes: 1 addition & 23 deletions packages/near-fast-auth-signer/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { captureException } from '@sentry/react';
import { KeyPair } from 'near-api-js';

import { LimitedAccessKey, NewAccountResponse } from './types';
import { withTimeout } from '../utils';
import { network } from '../utils/config';
import {
Expand Down Expand Up @@ -80,29 +81,6 @@ export const fetchAccountIdsFromTwoKeys = async (
return null;
};

type LimitedAccessKey = {
public_key: string,
receiver_id: string,
allowance: string,
method_names: string
}

type NewAccountResponse =
| {
type: 'ok',
create_account_options: {
full_access_keys: string[] | null,
limited_access_keys: LimitedAccessKey[] | null,
contract_bytes: string[] | null
},
user_recovery_public_key: string,
near_account_id: string
}
| {
type: 'err',
msg: string
};

/**
* This function creates a new account on the NEAR blockchain by sending a request to the /new_account endpoint of the MPC recovery service.
*
Expand Down
22 changes: 22 additions & 0 deletions packages/near-fast-auth-signer/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type LimitedAccessKey = {
public_key: string,
receiver_id: string,
allowance: string,
method_names: string
}

export type NewAccountResponse =
| {
type: 'ok',
create_account_options: {
full_access_keys: string[] | null,
limited_access_keys: LimitedAccessKey[] | null,
contract_bytes: string[] | null
},
user_recovery_public_key: string,
near_account_id: string
}
| {
type: 'err',
msg: string
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../../utils';
import { recordEvent } from '../../utils/analytics';
import { basePath } from '../../utils/config';
import { NEAR_MAX_ALLOWANCE } from '../../utils/constants';
import { checkFirestoreReady, firebaseAuth } from '../../utils/firebase';
import ErrorSvg from '../CreateAccount/icons/ErrorSvg';
import { FormContainer, StyledContainer } from '../Layout';
Expand Down Expand Up @@ -226,7 +227,7 @@ function AddDevicePage() {
window.fastAuthController.signAndSendAddKey({
contractId: contract_id,
methodNames,
allowance: new BN('250000000000000'),
allowance: new BN(NEAR_MAX_ALLOWANCE),
publicKey: public_key,
}).then((res) => res && res.json()).then((res) => {
const failure = res['Receipts Outcome'].find(({ outcome: { status } }) => Object.keys(status).some((k) => k === 'Failure'))?.outcome?.status?.Failure;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
decodeIfTruthy, inIframe, isUrlNotJavascriptProtocol
} from '../../utils';
import { basePath, networkId } from '../../utils/config';
import { NEAR_MAX_ALLOWANCE } from '../../utils/constants';
import {
checkFirestoreReady, firebaseAuth,
} from '../../utils/firebase';
Expand Down Expand Up @@ -48,7 +49,7 @@ const onCreateAccount = async ({
limitedAccessKeys: public_key_lak ? [{
public_key: public_key_lak,
receiver_id: contract_id,
allowance: '250000000000000',
allowance: NEAR_MAX_ALLOWANCE,
method_names: methodNames ?? '',
}] : [],
accessToken,
Expand Down Expand Up @@ -125,13 +126,13 @@ export const onSignIn = async ({
publicKeyLak: public_key_lak,
contractId: contract_id,
methodNames,
allowance: new BN('250000000000000'),
allowance: new BN(NEAR_MAX_ALLOWANCE),
}) : getAddKeyAction({
publicKeyLak: public_key_lak,
webAuthNPublicKey: publicKeyFak,
contractId: contract_id,
methodNames,
allowance: new BN('250000000000000'),
allowance: new BN(NEAR_MAX_ALLOWANCE),
});

return (window as any).fastAuthController.signAndSendActionsWithRecoveryKey({
Expand Down
Loading

0 comments on commit 67488cc

Please sign in to comment.