Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .depcheckrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ ignores:
- '@lavamoat/allow-scripts'
- '@metamask/auto-changelog'
- '@metamask/create-release-branch'
- '@ts-bridge/cli'
- 'depcheck'
- 'eslint-interactive'
- 'jest'
- 'prettier-2'
- 'rimraf'
- 'simple-git-hooks'
- 'ts-node'
- 'typedoc'
- 'typescript'
# Ignore plugins for tools
- '@typescript-eslint/*'
- 'babel-jest'
Expand Down
41 changes: 20 additions & 21 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,26 @@ jobs:
echo "child-workspace-package-names=$(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(.name) | @json')" >> "$GITHUB_OUTPUT"
shell: bash

## TODO: Address as tech debt and re-introduce this step, making sure lint conventions followed in whole repo
# lint:
# name: Lint
# runs-on: ubuntu-latest
# needs: prepare
# strategy:
# matrix:
# node-version: [22.x]
# steps:
# - name: Checkout and setup environment
# uses: MetaMask/action-checkout-and-setup@v1
# with:
# is-high-risk-environment: false
# - run: yarn lint
# - name: Require clean working directory
# shell: bash
# run: |
# if ! git diff --exit-code; then
# echo "Working tree dirty at end of job"
# exit 1
# fi
lint:
name: Lint
runs-on: ubuntu-latest
needs: prepare
strategy:
matrix:
node-version: [22.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v1
with:
is-high-risk-environment: false
- run: yarn lint
- name: Require clean working directory
shell: bash
run: |
if ! git diff --exit-code; then
echo "Working tree dirty at end of job"
exit 1
fi

validate-changelog:
name: Validate changelog
Expand Down
8 changes: 0 additions & 8 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ If you're developing your project locally and want to test changes to a package,

1. First, you must build the monorepo, by running `yarn build`.
2. Next, you need to connect the package to your project by overriding the resolution logic in your package manager to replace the published version of the package with the local version.

1. Open `package.json` in the project and locate the dependency entry for the package.
2. Locate the section responsible for resolution overrides (or create it if it doesn't exist). If you're using Yarn, this is `resolutions`; if you're using NPM or any other package manager, this is `overrides`.
3. Add a line to this section that mirrors the dependency entry on the left-hand side and points to the local path on the right-hand side:
Expand Down Expand Up @@ -135,7 +134,6 @@ If you're a member of the MetaMask organization, you can create preview builds b
2. After a few minutes, the action should complete and you will see a new comment that lists the newly published packages along with their versions.

Note two things about each package:

- The name is scoped to `@metamask-previews` instead of `@metamask`.
- The ID of the last commit in the branch is appended to the version, e.g. `1.2.3-preview-e2df9b4` instead of `1.2.3`.

Expand All @@ -158,7 +156,6 @@ If you've forked this repository, you can create preview builds based on a branc
```

You should be able to see the published version of each package in the output. Note two things:

- The name is scoped to the NPM organization you entered instead of `@metamask`.
- The ID of the last commit in the branch is appended to the version, e.g. `1.2.3-preview-e2df9b4` instead of `1.2.3`.

Expand Down Expand Up @@ -208,20 +205,17 @@ Use the following process to release new packages in this repo:
2. **Select packages to release.**

The UI will show all packages with changes since their last release. For each package:

- Choose whether to include it in the release
- Select an appropriate version bump (patch, minor, or major) following SemVer rules
- The UI will automatically validate your selections and identify dependencies that need to be included

3. **Review and resolve dependency requirements.**

The UI automatically analyzes your selections and identifies potential dependency issues that need to be addressed before proceeding. You'll need to review and resolve these issues by either:

- Including the suggested additional packages
- Confirming that you want to skip certain packages (if you're certain they don't need to be updated)

Common types of dependency issues you might encounter:

- **Missing dependencies**: If you're releasing Package A that depends on Package B, the UI will prompt you to include Package B
- **Breaking change impacts**: If you're releasing Package B with breaking changes, the UI will identify packages that have peer dependencies on Package B that need to be updated
- **Version incompatibilities**: The UI will flag if your selected version bumps don't follow semantic versioning rules relative to dependent packages
Expand All @@ -231,15 +225,13 @@ Use the following process to release new packages in this repo:
4. **Confirm your selections.**

Once you're satisfied with your package selections and version bumps, confirm them in the UI. This will:

- Create a new branch named `release/<new release version>`
- Update the version in each package's `package.json`
- Add a new section to each package's `CHANGELOG.md` for the new version

5. **Review and update changelogs.**

Each selected package will have a new changelog section. Review these entries to ensure they are helpful for consumers:

- Categorize entries appropriately following the ["Keep a Changelog"](https://keepachangelog.com/en/1.0.0/) guidelines. Ensure that no changes are listed under "Uncategorized".
- Remove changelog entries that don't affect consumers of the package (e.g. lockfile changes or development environment changes). Exceptions may be made for changes that might be of interest despite not having an effect upon the published package (e.g. major test improvements, security improvements, improved documentation, etc.).
- Reword changelog entries to explain changes in terms that users of the package will understand (e.g., avoid referencing internal variables/concepts).
Expand Down
3 changes: 3 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ const config = createConfig([
allowDefaultProject: [
'./scripts/*.ts',
'packages/*/stencil.config.ts',
'packages/*/*.config.ts',
'playground/*/*.config.ts',
'integrations/wagmi/wagmi.config.ts',
],
},
},
Expand Down
89 changes: 66 additions & 23 deletions integrations/wagmi/metamask-connector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type -- Wagmi connector API */
/* eslint-disable no-restricted-globals -- Browser connector uses window */
/* eslint-disable @typescript-eslint/no-misused-promises -- Event handlers are async */
/* eslint-disable require-atomic-updates -- Race conditions are acceptable for caching */
/* eslint-disable id-denylist -- 'err' is clear in catch context */
/* eslint-disable id-length -- 'x' is clear in lambda context */
/* eslint-disable @typescript-eslint/no-shadow -- accounts shadow is intentional */
/* eslint-disable no-nested-ternary -- Ternary chain is clearer here */
/* eslint-disable jsdoc/require-param-description -- Wagmi connector API */
/* eslint-disable jsdoc/require-returns -- Wagmi connector API */
/* eslint-disable @typescript-eslint/no-non-null-assertion -- Provider is guaranteed after check */
import type {
createEVMClient,
EIP1193Provider,
Expand Down Expand Up @@ -46,6 +57,10 @@ export type MetaMaskParameters = UnionCompute<
type CreateEVMClientParameters = Parameters<typeof createEVMClient>[0];

metaMask.type = 'metaMask' as const;
/**
*
* @param parameters
*/
export function metaMask(parameters: MetaMaskParameters = {}) {
type Provider = EIP1193Provider;
type Properties = {
Expand All @@ -67,25 +82,28 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
const provider = instance.getProvider();

let accounts: readonly Address[] = [];
if (isReconnecting) accounts = await this.getAccounts().catch(() => []);
if (isReconnecting) {
accounts = await this.getAccounts().catch(() => []);
}

try {
let signResponse: string | undefined;
let connectWithResponse: unknown | undefined;
if (!accounts?.length) {
const chainIds = config.chains.map((chain) => chain.id);
if (parameters.connectAndSign || parameters.connectWith) {
if (parameters.connectAndSign)
if (parameters.connectAndSign) {
signResponse = await instance.connectAndSign({
chainIds,
message: parameters.connectAndSign,
});
else if (parameters.connectWith)
} else if (parameters.connectWith) {
connectWithResponse = await instance.connectWith({
chainIds,
method: parameters.connectWith.method,
params: parameters.connectWith.params,
});
}

accounts = await this.getAccounts();
} else {
Expand All @@ -94,27 +112,30 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
}
}
// Switch to chain if provided
let currentChainId = (await this.getChainId()) as number;
let currentChainId = await this.getChainId();
if (chainId && currentChainId !== chainId) {
const chain = await this.switchChain!({ chainId }).catch((error) => {
if (error.code === UserRejectedRequestError.code) throw error;
if (error.code === UserRejectedRequestError.code) {
throw error;
}
return { id: currentChainId };
});
currentChainId = chain?.id ?? currentChainId;
}

if (signResponse)
if (signResponse) {
provider.emit('connectAndSign', {
accounts,
chainId: currentChainId,
signResponse,
});
else if (connectWithResponse)
} else if (connectWithResponse) {
provider.emit('connectWith', {
accounts,
chainId: currentChainId,
connectWithResponse,
});
}

return {
// TODO(v3): Make `withCapabilities: true` default behavior
Expand All @@ -125,10 +146,12 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
};
} catch (err) {
const error = err as RpcError;
if (error.code === UserRejectedRequestError.code)
if (error.code === UserRejectedRequestError.code) {
throw new UserRejectedRequestError(error);
if (error.code === ResourceUnavailableRpcError.code)
}
if (error.code === ResourceUnavailableRpcError.code) {
throw new ResourceUnavailableRpcError(error);
}
throw error;
}
},
Expand All @@ -138,8 +161,9 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
},
async getAccounts() {
const instance = await this.getInstance();
if (instance.accounts.length)
if (instance.accounts.length) {
return instance.accounts.map((x) => getAddress(x));
}
// Fallback to provider if SDK doesn't return accounts
const provider = instance.getProvider();
const accounts = (await provider.request({
Expand All @@ -149,7 +173,9 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
},
async getChainId() {
const instance = await this.getInstance();
if (instance.getChainId()) return Number(instance.getChainId());
if (instance.getChainId()) {
return Number(instance.getChainId());
}
// Fallback to provider if SDK doesn't return chainId
const provider = instance.getProvider();
const chainId = await provider.request({ method: 'eth_chainId' });
Expand All @@ -165,25 +191,29 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
// JSON-RPC requests on page load
const timeout = 10;
const accounts = await withRetry(
() =>
async () =>
withTimeout(
async () => {
const accounts = await this.getAccounts();
if (!accounts.length) throw new Error('try again');
if (!accounts.length) {
throw new Error('try again');
}
return accounts;
},
{ timeout },
),
{ delay: timeout + 1, retryCount: 3 },
);
return !!accounts.length;
return Boolean(accounts.length);
} catch {
return false;
}
},
async switchChain({ addEthereumChainParameter, chainId }) {
const chain = config.chains.find(({ id }) => id === chainId);
if (!chain) throw new SwitchChainError(new ChainNotConfiguredError());
if (!chain) {
throw new SwitchChainError(new ChainNotConfiguredError());
}

try {
const instance = await this.getInstance();
Expand Down Expand Up @@ -211,8 +241,9 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
} catch (err) {
const error = err as RpcError;

if (error.code === UserRejectedRequestError.code)
if (error.code === UserRejectedRequestError.code) {
throw new UserRejectedRequestError(error);
}

throw new SwitchChainError(error);
}
Expand All @@ -228,7 +259,9 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
},
async onConnect(connectInfo) {
const accounts = await this.getAccounts();
if (accounts.length === 0) return;
if (accounts.length === 0) {
return;
}

const chainId = Number(connectInfo.chainId);
config.emitter.emit('connect', { accounts, chainId });
Expand All @@ -238,7 +271,9 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
// https://github.com/MetaMask/providers/pull/120
if (error && (error as RpcError<1013>).code === 1013) {
const provider = await this.getProvider();
if (provider && !!(await this.getAccounts()).length) return;
if (provider && Boolean((await this.getAccounts()).length)) {
return;
}
}

config.emitter.emit('disconnect');
Expand All @@ -249,7 +284,7 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
async getInstance() {
if (!metamask) {
if (!metamaskPromise) {
const { createEVMClient } = await (() => {
const { createEVMClient } = await (async () => {
try {
return import('@metamask/connect-evm');
} catch {
Expand All @@ -266,16 +301,24 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
),
},
dapp: (() => {
if (parameters.dappMetadata) return parameters.dappMetadata;
if (parameters.dapp) return parameters.dapp;
if (typeof window === 'undefined') return { name: 'wagmi' };
if (parameters.dappMetadata) {
return parameters.dappMetadata;
}
if (parameters.dapp) {
return parameters.dapp;
}
if (typeof window === 'undefined') {
return { name: 'wagmi' };
}
return {
name: window.location.hostname,
url: window.location.href,
};
})(),
debug: (() => {
if (parameters.logging) return true;
if (parameters.logging) {
return true;
}
return parameters.debug;
})(),
eventHandlers: {
Expand Down
2 changes: 1 addition & 1 deletion integrations/wagmi/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,4 @@ export const wagmiContractConfig = {
type: 'function',
},
],
} as const
} as const;
2 changes: 1 addition & 1 deletion integrations/wagmi/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/// <reference types="vite/client" />
// / <reference types="vite/client" />
Loading
Loading