Skip to content

Commit

Permalink
feat: lightweight plugin dependencies + manual test results (#14)
Browse files Browse the repository at this point in the history
* feat: lightweight plugin dependencies
* feat: manual test results (#15)
* chore: update gh actions
  • Loading branch information
krzysu authored Oct 31, 2024
1 parent cac42c8 commit a749244
Show file tree
Hide file tree
Showing 24 changed files with 405 additions and 118 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: node
package-name: release-please-action
changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":true}]'

- uses: actions/checkout@v3
- uses: actions/checkout@v4
if: ${{ steps.release.outputs.release_created }}

- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
cache: 'yarn'
node-version: 18
Expand Down
101 changes: 101 additions & 0 deletions WALLET_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Result of manual tests with wallets

## Browser extension wallets

| Method | Metamask | Coinbase Wallet | Trust Wallet | Enkrypt | Rainbow | Rabby Wallet | Exodus | Phantom |
| -------------------------- | -------- | --------------- | ------------ | ------- | ------- | ------------ | ------ | ------- |
| wallet_addEthereumChain || ✓ (\*) ||| ✓ (\*) || x (\*) | x (\*) |
| wallet_switchEthereumChain || ✓ (\*) ||||| ✓ (\*) | ✓ (\*) |
| wallet_watchAsset | ✓ (\*) || ✓ (\*) | x | ✓ (\*) | ✓ (\*) | x | ✓ (\*) |
| wallet_requestPermissions | ✓ (\*) | x || x (\*) | x ||||
| wallet_getPermissions || x || x | x ||||
| wallet_revokePermissions || x || x | x || ✓ (\*) | x |

- (✓) Supported
- (x) Not supported
- (\*) See additional remarks below

### Comments / Remarks

#### Expected behavior

- `wallet_addEthereumChain`: Adds a new chain to the wallet if it’s not already recognized, and switches to it.
- `wallet_switchEthereumChain`: Switches to a recognized chain; fails if the chain is not already added to the wallet.
- `wallet_watchAsset`: Adds a new asset to the wallet if it’s not already recognized.
- `wallet_requestPermissions`: Requests specific permissions from the wallet, triggering a modal for user approval.
- `wallet_getPermissions`: Retrieves the list of permissions the dApp currently has from the wallet.
- `wallet_revokePermissions`: Revokes previously granted permissions from the dApp.

#### Metamask

- `wallet_requestPermissions` and `wallet_getPermissions`: Returns a full response containing all permission fields according to the specification.
- `wallet_watchAsset`: There’s an issue with Web3RequestManager, causing an error with the wallet's response:

```
ResponseError: Returned error: Cannot destructure property 'tokenId' of 'n' as it is undefined.
at Web3RequestManager.<anonymous> (web3_request_manager.ts:178:1)
at Generator.next (<anonymous>)
at fulfilled (web3_request_manager.ts:1:1)`
```

#### Coinbase Wallet

- `wallet_addEthereumChain`: It does not fully adhere to the specification, as it changes the network temporarily but doesn’t save it. As a result, if the wallet does not natively support the network, following up with `wallet_switchEthereumChain` for the same network will fail.
- `wallet_watchAsset`: The wallet responds with a modal with the following message: "Coinbase Wallet automatically tracks token ownerships on the Ethereum network. There is no need to import USDC."

### Trust Wallet

- General Behavior is very similarly to MetaMask.
- `wallet_watchAsset`: Produces an error due to an issue with destructuring the wallet’s response:

```
WatchAsset.tsx:45 TypeError: Cannot destructure property 'address' of 'A' as it is undefined.
at wi.request (inpage.js:247:201226)
at inpage.js:247:206755
at yu.handleStaticRequests (inpage.js:247:207086)
at yu.request (inpage.js:247:206709)
at web3_request_manager.ts:271:1
at new Promise (<anonymous>)
at Web3RequestManager.<anonymous> (web3_request_manager.ts:250:1)
at Generator.next (<anonymous>)
at web3_request_manager.ts:1:1
at new Promise (<anonymous>)
```

### Enkrypt

- `wallet_requestPermissions`: Returns a limited response object, but no permissions appear to be granted, and no modal is shown to request user approval.

### Rainbow

- `wallet_addEthereumChain`: Adds a chain but does not switch to it; fails if the network is already known to the wallet.
- `wallet_watchAsset`: Fails with the error `"Internal error: Cannot read properties of undefined (reading 'address')"`.

### Rabby Wallet

- `wallet_requestPermissions` and `wallet_getPermissions`: Returns a response object containing only `parentCapability`, but functions as expected.
- `wallet_watchAsset`: Opens a modal with an “Add Custom Token” header, but it remains stuck and does not complete the action.

### Exodus

- `wallet_addEthereumChain` and `wallet_switchEthereumChain`: Only work with chains natively supported by the wallet; new chains cannot be added.
- `wallet_requestPermissions` and `wallet_getPermissions`: Returns a response object containing only `parentCapability`, but functions as expected.
- `wallet_revokePermissions`: Revokes permissions but also triggers an error: `DisconnectedError: The provider is disconnected from all chains`. This appears to be an issue with the wallet itself.

### Phantom

- `wallet_addEthereumChain` and `wallet_switchEthereumChain`: Only work with chains natively supported by the wallet; new chains cannot be added.
- `wallet_watchAsset`: Fails with an error:

```
WatchAsset.tsx:45 Ir: Missing or invalid parameters.
at r.request (chrome-extension://bfnaelmomeimhlpmgjnjophhpkkoljpa/evmAsk.js:7:5094)
at Web3RequestManager.<anonymous> (http://localhost:3000/static/js/bundle.js:80237:25)
at Generator.next (<anonymous>)
at http://localhost:3000/static/js/bundle.js:80100:67
at new Promise (<anonymous>)
```

## Mobile wallets using WalletConnect

TBD
77 changes: 15 additions & 62 deletions packages/example-react-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useContext, useEffect, useState } from 'react';
import type { EIP6963ProviderDetail, ProviderChainId } from 'web3';
import type { EIP6963ProviderDetail } from 'web3';

import { Accounts } from './components/Accounts';
import { ChainId } from './components/ChainId';
import { ProviderButton } from './components/ProviderButton';
import { AddEthereumChain } from './wallet-components/AddEthereumChain';
import { Permissions } from './wallet-components/Permissions';
Expand All @@ -12,61 +13,12 @@ import { type IWeb3Context, Web3Context } from './web3/Web3Context';

function App() {
const web3Context: IWeb3Context = useContext(Web3Context);

const [hasProviders, setHasProviders] = useState<boolean>(false);

useEffect(() => {
setHasProviders(web3Context.providers.length > 0);
}, [web3Context.providers.length]);

const [chainId, setChainId] = useState<bigint | undefined>(undefined);
const [networkId, setNetworkId] = useState<bigint | undefined>(undefined);
useEffect(() => {
if (web3Context.currentProvider === undefined) {
return;
}

const { provider } = web3Context.currentProvider;

function updateChainId(newId: bigint) {
setChainId(newId);
}

function updateProviderIds(newId: ProviderChainId) {
setChainId(BigInt(newId));

web3Context.web3.eth.net
.getId()
.then(setNetworkId)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}

web3Context.web3.eth
.getChainId()
.then(updateChainId)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});

web3Context.web3.eth.net
.getId()
.then(setNetworkId)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});

provider.on('chainChanged', updateProviderIds);

// eslint-disable-next-line consistent-return
return () => {
provider.removeListener('chainChanged', updateProviderIds);
};
}, [web3Context.currentProvider, web3Context.web3.eth]);

return (
<main>
<h1>Web3.js Wallet RPC Methods Demonstration dApp</h1>
Expand All @@ -87,6 +39,17 @@ function App() {

{hasProviders && web3Context.currentProvider && (
<>
<h2>Current Provider</h2>
<div style={{ display: 'flex', alignItems: 'center' }}>
<img
src={web3Context.currentProvider.info.icon}
alt={web3Context.currentProvider.info.name}
width="35"
/>
<span>{web3Context.currentProvider.info.name}</span>
</div>
<ChainId />

{web3Context.providers.length > 1 && (
<>
<h2>Switch Provider</h2>
Expand All @@ -96,18 +59,14 @@ function App() {
}

return (
<div key={provider.info.uuid}>
<div key={provider.info.uuid} style={{ display: 'inline-block' }}>
<ProviderButton provider={provider} />
</div>
);
})}
</>
)}

<h2>Network Details</h2>
{chainId && <div>Chain ID: {`${chainId}`}</div>}
{networkId && <div>Network ID: {`${networkId}`}</div>}

<AccountProvider>
<Accounts />
</AccountProvider>
Expand All @@ -121,12 +80,6 @@ function App() {
</div>
</>
)}

<br />
<i>
This project was bootstrapped with{' '}
<a href="https://github.com/facebook/create-react-app">Create React App</a>.
</i>
</main>
);
}
Expand Down
47 changes: 40 additions & 7 deletions packages/example-react-app/src/components/AccountDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { type IWeb3Context, Web3Context } from '../web3/Web3Context';

export function AccountDetail({ address }: { address: string }) {
const web3Context: IWeb3Context = useContext(Web3Context);

const [balance, setBalance] = useState<number>(NaN);
const subscriptionId: MutableRefObject<string | undefined> = useRef(undefined);

Expand All @@ -14,16 +13,50 @@ export function AccountDetail({ address }: { address: string }) {
setBalance(parseFloat(web3Context.web3.utils.fromWei(newBalance, 'ether')));
}

useEffect(() => {
void updateBalance();
}, []);

useEffect(() => {
if (web3Context.currentProvider === undefined) {
return;
}

const { provider } = web3Context.currentProvider;

function onChainChanged() {
void updateBalance();
}

provider.on('chainChanged', onChainChanged);

// eslint-disable-next-line consistent-return
return () => {
// not all wallet providers implement removeListener
try {
provider.removeListener('chainChanged', onChainChanged);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
}, [web3Context.currentProvider]);

useEffect(() => {
async function subscribeToNewBlockHeaders() {
const newBlockSubscription =
await web3Context.web3.eth.subscribe('newBlockHeaders');
try {
const newBlockSubscription =
await web3Context.web3.eth.subscribe('newBlockHeaders');

subscriptionId.current = newBlockSubscription.id;
subscriptionId.current = newBlockSubscription.id;

newBlockSubscription.on('data', () => {
void updateBalance();
});
newBlockSubscription.on('data', () => {
void updateBalance();
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}

void subscribeToNewBlockHeaders();
Expand Down
52 changes: 52 additions & 0 deletions packages/example-react-app/src/components/ChainId.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useContext, useEffect, useState } from 'react';
import { type ProviderChainId } from 'web3';

import { Web3Context } from '../web3/Web3Context';

export function ChainId() {
const { currentProvider } = useContext(Web3Context);
const [chainId, setChainId] = useState<bigint | undefined>(undefined);

useEffect(() => {
currentProvider?.provider
.request({ method: 'eth_chainId' })
.then((id) => {
setChainId(BigInt(id as string));
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}, [currentProvider]);

useEffect(() => {
if (currentProvider === undefined) {
return;
}

const { provider } = currentProvider;

function onChainChanged(newId: ProviderChainId) {
setChainId(BigInt(newId));
}

provider.on('chainChanged', onChainChanged);

// eslint-disable-next-line consistent-return
return () => {
// not all wallet providers implement removeListener
try {
provider.removeListener('chainChanged', onChainChanged);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
}, [currentProvider]);

if (!chainId) {
return <div>Loading...</div>;
}

return <div>Chain ID: {`${chainId}`}</div>;
}
4 changes: 0 additions & 4 deletions packages/example-react-app/src/components/ProviderButton.css

This file was deleted.

12 changes: 7 additions & 5 deletions packages/example-react-app/src/components/ProviderButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import type { EIP6963ProviderDetail } from 'web3';
import type { IWeb3Context } from '../web3/Web3Context';
import { Web3Context } from '../web3/Web3Context';

import './ProviderButton.css';

export function ProviderButton({ provider }: { provider: EIP6963ProviderDetail }) {
const web3Context: IWeb3Context = useContext(Web3Context);

return (
<button type="button" onClick={() => web3Context.setCurrentProvider(provider)}>
<img src={provider.info.icon} alt={provider.info.name} width="35" />
<span> {provider.info.name}</span>
<button
type="button"
onClick={() => web3Context.setCurrentProvider(provider)}
style={{ cursor: 'pointer', display: 'flex', alignItems: 'center' }}
>
<img src={provider.info.icon} alt={provider.info.name} width="35" height="35" />
<span>{provider.info.name}</span>
</button>
);
}
Loading

0 comments on commit a749244

Please sign in to comment.