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: Add keplr wallet support and shared utils package #1147

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c5087d0
initial keplr set up
0xSero Jun 12, 2024
5829861
move over keplr set up
0xSero Jun 12, 2024
4fd666f
more set up
0xSero Jun 12, 2024
60304a3
init
0xSero Jun 12, 2024
0ec44d3
keplr extension install
0xSero Jun 13, 2024
97091d6
selectors
0xSero Jun 13, 2024
101c9ad
playwright keplr set up
0xSero Jun 13, 2024
a5a6762
build set up
0xSero Jun 13, 2024
e8d0bd8
prepare test
0xSero Jun 13, 2024
e1179ad
prepare test
0xSero Jun 13, 2024
b39afcd
set up cache
0xSero Jun 13, 2024
acdf38c
more cache more setup with pages
0xSero Jun 13, 2024
68a6094
loading cache into browser for tests
0xSero Jun 14, 2024
769344d
build cache and unlock wallet from keplr (;
0xSero Jun 14, 2024
8d38cb2
clean up and port remaining functions
0xSero Jun 14, 2024
500669d
Merge branch 'new-dawn' into feat/keplr-wallet
0xSero Jun 17, 2024
c5cec19
make dynamic
0xSero Jun 17, 2024
fd48092
hashing
0xSero Jun 17, 2024
3cf42d9
s
0xSero Jun 17, 2024
5baf24d
h
0xSero Jun 17, 2024
d02d242
made a new /utils package to handle shared utility functions, as the …
0xSero Jun 17, 2024
76334cc
finish utils package
0xSero Jun 18, 2024
fb076ac
2
0xSero Jun 18, 2024
5ed5ca2
figured out the pesky cache
0xSero Jun 18, 2024
af0855e
improve keplr set up
0xSero Jun 18, 2024
d2c807c
debug cypress
0xSero Jun 19, 2024
8f6e1e4
add import test
0xSero Jun 19, 2024
834d458
unlock for fixture (: keplr
0xSero Jun 19, 2024
b18ed70
copy
0xSero Jun 19, 2024
b897897
clean up selector and fix an input i broke, also finish first test case
0xSero Jun 19, 2024
5671ba2
linting
0xSero Jun 19, 2024
093385b
new test
0xSero Jun 20, 2024
452498a
submitted keplr createWallet setup
0xSero Jun 21, 2024
2d38d89
bunch of tests
0xSero Jun 21, 2024
54b0b7c
hm
0xSero Jun 21, 2024
c962372
reject and accept access
0xSero Jun 21, 2024
386ae15
hm
0xSero Jun 21, 2024
f40fdf2
hm
0xSero Jun 21, 2024
138c7a1
Merge branch 'new-dawn' into feat/keplr-wallet
0xSero Jun 21, 2024
850b037
auto sorty
0xSero Jun 21, 2024
7991b42
v
0xSero Jun 24, 2024
988f112
fix
0xSero Jun 24, 2024
4a7f4e5
remove unnecessary test
0xSero Jun 24, 2024
e191940
clean up
0xSero Jun 24, 2024
d9b0332
Merge branch 'new-dawn' into feat/keplr-wallet
0xSero Jun 24, 2024
c10a1ff
clean up
0xSero Jun 24, 2024
4e5d50a
more clean up
0xSero Jun 24, 2024
c48386b
fix
0xSero Jun 24, 2024
cf89cc9
fix and clean
0xSero Jun 24, 2024
3d65aea
lint
0xSero Jun 24, 2024
630298b
done
0xSero Jun 24, 2024
1622f45
lint
0xSero Jun 24, 2024
25559c8
remove unused
0xSero Jun 24, 2024
a0da307
Merge branch 'new-dawn' into feat/keplr-wallet
drptbl Jul 1, 2024
7376f3e
update selectors, need to publish versions on NPM
0xSero Jul 1, 2024
97a91fc
remove
0xSero Jul 1, 2024
6f6c783
lint
0xSero Jul 1, 2024
e428447
test
0xSero Jul 1, 2024
78d6de9
Merge branch 'new-dawn' into feat/keplr-wallet
0xSero Jul 5, 2024
49257de
Merge branch 'new-dawn' into feat/keplr-wallet
0xSero Jul 5, 2024
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
16 changes: 16 additions & 0 deletions wallets/keplr/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineConfig } from 'cypress'
import { installSynpress } from './src/cypress'

export default defineConfig({
chromeWebSecurity: false,
e2e: {
baseUrl: 'http://localhost:9999',
specPattern: 'test/cypress/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'src/cypress/support/e2e.{js,jsx,ts,tsx}',
fixturesFolder: 'src/cypress/fixtures',
testIsolation: false,
async setupNodeEvents(on, config) {
return installSynpress(on, config)
}
}
})
19 changes: 19 additions & 0 deletions wallets/keplr/environment.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
CI: boolean
HEADLESS: boolean
}
}
}

declare global {
interface Window {
ethereum: import('ethers').Eip1193Provider
}

// biome-ignore lint/suspicious/noExplicitAny: Web3Mock is a mock object
const Web3Mock: any
}

export {}
46 changes: 46 additions & 0 deletions wallets/keplr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@synthetixio/keplr",
"version": "0.0.1-alpha.0",
"type": "module",
"exports": {
"types": "./types/index.d.ts",
"default": "./dist/index.js"
},
"main": "./dist/index.js",
"types": "./types/index.d.ts",
"files": [
"dist",
"src",
"types"
],
"scripts": {
"build": "pnpm run clean && pnpm run build:dist && pnpm run build:types",
"build:dist": "tsup --tsconfig tsconfig.build.json",
"build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json",
"clean": "rimraf dist types",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e:headful": "playwright test",
"test:e2e:headless": "HEADLESS=true playwright test",
"test:e2e:headless:ui": "HEADLESS=true playwright test --ui",
"test:watch": "vitest watch",
"types:check": "tsc --noEmit"
},
"dependencies": {
"@synthetixio/synpress-cache": "workspace:*",
"@synthetixio/synpress-core": "workspace:*"
},
"devDependencies": {
"@synthetixio/synpress-tsconfig": "workspace:*",
"@types/node": "20.11.17",
"@vitest/coverage-v8": "1.2.2",
"cypress": "13.9.0",
"rimraf": "5.0.5",
"tsup": "8.0.2",
"typescript": "5.3.3",
"vitest": "1.2.2"
},
"peerDependencies": {
"@playwright/test": "1.44.0"
}
}
50 changes: 50 additions & 0 deletions wallets/keplr/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { defineConfig, devices } from '@playwright/test'

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
// Look for test files in the "test/e2e" directory, relative to this configuration file.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks the same as for the metamask - can be imported from one place for both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rolled back the utils package we can tackle this in a separate issue so that the package is self contained

testDir: './test/playwright',

// We're increasing the timeout to 60 seconds to allow all traces to be recorded.
// Sometimes it threw an error saying that traces were not recorded in the 30 seconds timeout limit.
timeout: 60_000,

// Run all tests in parallel.
fullyParallel: true,

// Fail the build on CI if you accidentally left test.only in the source code.
forbidOnly: !!process.env.CI,

// Fail all remaining tests on CI after the first failure. We want to reduce the feedback loop on CI to minimum.
maxFailures: process.env.CI ? 1 : 0,

// Opt out of parallel tests on CI since it supports only 1 worker.
workers: process.env.CI ? 1 : undefined,

// Concise 'dot' for CI, default 'html' when running locally.
// See https://playwright.dev/docs/test-reporters.
reporter: process.env.CI
? [['html', { open: 'never', outputFolder: `playwright-report-${process.env.HEADLESS ? 'headless' : 'headful'}` }]]
: 'html',

// Shared settings for all the projects below.
// See https://playwright.dev/docs/api/class-testoptions.
use: {
// We are using locally deployed MetaMask Test Dapp.
baseURL: 'http://localhost:9999',

// Collect all traces on CI, and only traces for failed tests when running locally.
// See https://playwright.dev/docs/trace-viewer.
trace: process.env.CI ? 'on' : 'retain-on-failure'
},

// Configure projects for major browsers.
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
}
]
})
143 changes: 143 additions & 0 deletions wallets/keplr/src/KeplrWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type { Page } from '@playwright/test'
export type Keplr = 'keplr'

export class KeplrWallet {
seedPhrase = ''
wallet: Keplr = 'keplr'

constructor(readonly page: Page) {
Fixed Show fixed Hide fixed
this.page = page
}

/**
* Imports wallet from secret phrase and password.
*
* @param secretWords. The secret words to import.
* @param password. The password to set.
*
* @returns true if the wallet was imported successfully.
*/
async importWallet(secretWords, password) {
await playwright.waitAndClickByText(
onboardingElements.createWalletButton,
await playwright.keplrWindow(),
);
await playwright.waitAndClickByText(
onboardingElements.importRecoveryPhraseButton,
await playwright.keplrWindow(),
);
await playwright.waitAndClickByText(
onboardingElements.useRecoveryPhraseButton,
await playwright.keplrWindow(),
);
await playwright.waitAndClickByText(
onboardingElements.phraseCount24,
await playwright.keplrWindow(),
);

for (const [index, word] of secretWords.split(' ').entries()) {
await playwright.waitAndTypeByLocator(
onboardingElements.textAreaSelector,
word,
index,
);
}

await playwright.waitAndClick(
onboardingElements.submitPhraseButton,
await playwright.keplrWindow(),
);

await playwright.waitAndType(
onboardingElements.walletInput,
onboardingElements.walletName,
);
await playwright.waitAndType(onboardingElements.passwordInput, password);
await playwright.waitAndType(
onboardingElements.confirmPasswordInput,
password,
);

await playwright.waitAndClick(
onboardingElements.submitWalletDataButton,
await playwright.keplrWindow(),
{ number: 1 },
);

await playwright.waitForByText(
onboardingElements.phraseSelectChain,
await playwright.keplrWindow(),
);

await playwright.waitAndClick(
onboardingElements.submitChainButton,
await playwright.keplrWindow(),
);

await playwright.waitForByText(
onboardingElements.phraseAccountCreated,
await playwright.keplrWindow(),
);

await playwright.waitAndClick(
onboardingElements.finishButton,
await playwright.keplrWindow(),
{ dontWait: true },
);

return true;
}

/**
* Adds a new account.
*
* @returns true if the account was added successfully.
*/
async acceptAccess() {
const notificationPage = await playwright.switchToKeplrNotification();
await playwright.waitAndClick(
notificationPageElements.approveButton,
notificationPage,
{ waitForEvent: 'close' },
);
return true;
}

/**
* Confirms a transaction.
*
* @returns true if the transaction was confirmed successfully.
*/
async confirmTransaction() {
const notificationPage = await playwright.switchToKeplrNotification();
await playwright.waitAndClick(
notificationPageElements.approveButton,
notificationPage,
{ waitForEvent: 'close' },
);
return true;
}

/**
* Does initial setup for the wallet.
*
* @param playwrightInstance. The playwright instance to use.
* @param secretWordsOrPrivateKey. The secret words or private key to import.
* @param password. The password to set.
*/
async setupWallet(
playwrightInstance,
{ secretWordsOrPrivateKey, password },
) {
if (playwrightInstance) {
await playwright.init(playwrightInstance);
} else {
await playwright.init();
}

await playwright.assignWindows();
await playwright.assignActiveTabName('keplr');
await module.exports.getExtensionDetails();
await module.exports.importWallet(secretWordsOrPrivateKey, password);
}
}
3 changes: 3 additions & 0 deletions wallets/keplr/src/cypress/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const NO_CONTEXT = 'No browser context found. Connect Playwright first - connectPlaywright()'
export const NO_PAGE = 'No page found. Use getPage()'
export const MISSING_INIT = 'Keplr not initialized. Use initKeplrWallet()'
1 change: 1 addition & 0 deletions wallets/keplr/src/cypress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as installSynpress } from './installSynpress'
77 changes: 77 additions & 0 deletions wallets/keplr/src/cypress/initKeplrWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { readFileSync } from 'fs'
import { type BrowserContext, type Page, chromium } from '@playwright/test'

import { KeplrWallet } from '../KeplrWallet'
import { SEED_PHRASE, mockEthereum, web3MockPath } from '../utils'
import { MISSING_INIT, NO_CONTEXT, NO_PAGE } from './errors'

let context: BrowserContext | undefined
let cypressPage: Page | undefined
let keplrWallet: KeplrWallet | undefined

let keplrLoaded = false

const getCypressPage = async () => {
if (!context) {
console.error(NO_CONTEXT)
return
}

cypressPage = context.pages()[0]

return cypressPage
}

export async function connectPlaywrightToChrome(port: number) {
const debuggerDetails = await fetch(`http://127.0.0.1:${port}/json/version`)

const debuggerDetailsConfig = (await debuggerDetails.json()) as {
webSocketDebuggerUrl: string
}

const browser = await chromium.connectOverCDP(debuggerDetailsConfig.webSocketDebuggerUrl)

context = browser.contexts()[0]

return browser.isConnected()
}

export async function initKeplrWallet(port: number) {
await connectPlaywrightToChrome(port)

if (!context) {
console.error(NO_CONTEXT)
return
}

await getCypressPage()

if (!cypressPage) {
console.error(NO_PAGE)
return
}

await context.addInitScript({
content: `${readFileSync(web3MockPath, 'utf-8')}\n(${mockEthereum.toString()})();`
Fixed Show fixed Hide fixed
})

// As we want to refresh the page after mocking the ethereum object
if (!keplrLoaded) {
await cypressPage.reload()
keplrLoaded = true
}

keplrWallet = new KeplrWallet(cypressPage)
await keplrWallet.setupWallet(SEED_PHRASE, PASSWORD)
}

export function getKeplrWallet() {
if (!context || !cypressPage || !keplrWallet) {
Fixed Show fixed Hide fixed
console.error(MISSING_INIT)
return
}

if (keplrWallet) return keplrWallet
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

return new KeplrWallet(cypressPage)
}
30 changes: 30 additions & 0 deletions wallets/keplr/src/cypress/installSynpress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ensureRdpPort } from '@synthetixio/synpress-core'

// import { initEthereumWalletMock } from './initEthereumWalletMock'
// import setupTasks from './setupTasks'

let port: number

export default function installSynpress(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
const browsers = config.browsers.filter((b) => b.name === 'chrome')
if (browsers.length === 0) {
throw new Error('No Chrome browser found in the configuration')
}

on('before:browser:launch', async (_, launchOptions) => {
// Enable debug mode to establish playwright connection
const args = Array.isArray(launchOptions) ? launchOptions : launchOptions.args
port = ensureRdpPort(args)
})

on('before:spec', async () => {
await initEthereumWalletMock(port)
})

setupTasks(on)

return {
...config,
browsers
}
}
Loading
Loading