Skip to content

Commit

Permalink
Merge pull request #44 from sei-protocol/internal/wallet-refactor
Browse files Browse the repository at this point in the history
Wallet Provider Refactor
  • Loading branch information
codebycarson authored Jun 20, 2023
2 parents 8861bf4 + 2acb936 commit 4c5ff54
Show file tree
Hide file tree
Showing 36 changed files with 976 additions and 512 deletions.
6 changes: 6 additions & 0 deletions .changeset/curvy-mangos-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sei-js/react': major
'@sei-js/core': minor
---

Refactored wallet provider and UI components, added hook to programatically open wallet connect modal, implemented a new wallet interface, replaced custom styles with tint colors
10 changes: 9 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
{
"singleQuote": true
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": true,
"printWidth": 164,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "none",
"arrowParens": "always",
"useTabs": true
}
90 changes: 44 additions & 46 deletions packages/core/src/lib/wallet/connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,48 @@ import { connect } from './connect';
global.TextEncoder = TextEncoder;

describe('connect', () => {
let windowSpy: jest.SpyInstance;

beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get');
});

afterEach(() => {
windowSpy.mockRestore();
});

it('should throw an error if window is undefined', async () => {
windowSpy.mockImplementation(() => undefined);

const key = 'keplr';
const chainId = 'atlantic-1';
await expect(connect(key, chainId)).rejects.toThrowError();
});

it('should throw an error if no wallet is installed', async () => {
windowSpy.mockImplementation(() => ({}));

const key = 'keplr';
const chainId = 'atlantic-1';
await expect(connect(key, chainId)).rejects.toThrowError();
});

it('should return offlineSigner and accounts', async () => {
const offlineSigner = await DirectSecp256k1HdWallet.fromMnemonic(
'trip parent program index any save apple extra marble nothing please pulp'
);
const accounts = await offlineSigner.getAccounts();
windowSpy.mockImplementation(() => ({
keplr: {
getOfflineSignerAuto: () => offlineSigner,
experimentalSuggestChain: () => undefined,
enable: () => undefined,
},
}));

const key = 'keplr';
const chainId = 'atlantic-1';
await expect(connect(key, chainId)).resolves.toEqual({
offlineSigner,
accounts,
});
});
let windowSpy: jest.SpyInstance;

beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get');
});

afterEach(() => {
windowSpy.mockRestore();
});

it('should throw an error if window is undefined', async () => {
windowSpy.mockImplementation(() => undefined);

const key = 'keplr';
const chainId = 'atlantic-1';
await expect(connect(key, chainId)).rejects.toThrowError();
});

it('should throw an error if no wallet is installed', async () => {
windowSpy.mockImplementation(() => ({}));

const key = 'keplr';
const chainId = 'atlantic-2';
await expect(connect(key, chainId)).rejects.toThrowError();
});

it('should return offlineSigner and accounts', async () => {
const offlineSigner = await DirectSecp256k1HdWallet.fromMnemonic('trip parent program index any save apple extra marble nothing please pulp');
const accounts = await offlineSigner.getAccounts();
windowSpy.mockImplementation(() => ({
keplr: {
getOfflineSignerAuto: () => offlineSigner,
experimentalSuggestChain: () => undefined,
enable: () => undefined
}
}));

const key = 'keplr';
const chainId = 'atlantic-1';
await expect(connect(key, chainId)).resolves.toEqual({
offlineSigner,
accounts
});
});
});
46 changes: 19 additions & 27 deletions packages/core/src/lib/wallet/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,29 @@ import { Keplr as KeplrWindow } from '@keplr-wallet/types';
import { WalletConnect, WalletWindowInterface, WalletWindowKey } from './types';

declare global {
interface Window {
coin98?: {
keplr: KeplrWindow;
};
fin?: WalletWindowInterface;
falcon?: WalletWindowInterface;
keplr?: KeplrWindow;
leap?: WalletWindowInterface;
compass?: WalletWindowInterface;
}
interface Window {
compass?: WalletWindowInterface;
fin?: WalletWindowInterface;
keplr?: KeplrWindow;
leap?: WalletWindowInterface;
}
}

export const connect = async (
inputWallet: WalletWindowKey,
chainId: string
): Promise<WalletConnect> => {
if (typeof window === 'undefined' || !window) {
throw new Error('Window is undefined.');
}
export const connect = async (inputWallet: WalletWindowKey, chainId: string): Promise<WalletConnect> => {
if (typeof window === 'undefined' || !window) {
throw new Error('Window is undefined.');
}

const walletProvider =
inputWallet === 'coin98' ? window[inputWallet]?.keplr : window[inputWallet];
if (!walletProvider) {
throw new Error(`Wallet ${inputWallet} is not installed.`);
}
const walletProvider = inputWallet === 'coin98' ? window[inputWallet]?.keplr : window[inputWallet];
if (!walletProvider) {
throw new Error(`Wallet ${inputWallet} is not installed.`);
}

// Enable wallet before attempting to call any methods
await walletProvider.enable(chainId);
// Enable wallet before attempting to call any methods
await walletProvider.enable(chainId);

const offlineSigner = await walletProvider.getOfflineSignerAuto(chainId);
const accounts = await offlineSigner.getAccounts();
const offlineSigner = await walletProvider.getOfflineSignerAuto(chainId);
const accounts = await offlineSigner.getAccounts();

return { offlineSigner, accounts };
return { offlineSigner, accounts };
};
8 changes: 8 additions & 0 deletions packages/core/src/lib/wallet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OfflineSigner, AccountData } from '@cosmjs/proto-signing';

export type WalletWindowInterface = {
enable: (chainId: string) => Promise<void>;
disable: (chainId: string) => Promise<void>;
getOfflineSigner: (chainId: string) => Promise<OfflineSigner>;
// Will return a signer that only supports Amino if the account is a Ledger-based account,
// and returns a signer that is compatible for both Amino and Protobuf otherwise
Expand Down Expand Up @@ -39,9 +40,16 @@ export type SupportedWallet = {
windowKey: WalletWindowKey;
};

type GasPriceStep = {
low: number;
average: number;
high: number;
};

export type ChainInfo = {
chainName?: string;
chainId?: string;
restUrl?: string;
rpcUrl?: string;
gasPriceStep?: GasPriceStep;
};
62 changes: 59 additions & 3 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ const { offlineSigner, accounts, connectedWallet } = useWallet();
| Property | Type | Description |
|------------------|-----------|---------------------------------------------------------|
| connectedWallet | string? | The currently connected wallet |
| supportedWallets | string[] | List of supported wallets |
| installedWallets | string[] | List of wallets installed |
| chainId | string | Sei chain id |
| restUrl | string | The rest url associated with the connected wallet |
| rpcUrl | string | The rpc url associated with the connected wallet |
Expand Down Expand Up @@ -96,10 +94,68 @@ const { cosmWasmClient } = useSeiCosmWasmClient();
|----------------|-----------------|-----------------------------------------|
| cosmWasmClient | CosmWasmClient? | A cosm wasm client for smart contracts. |

# UI Components
This package contains two helpful UI components for connecting to a wallet provider.

## \<WalletConnectButton />
This component renders a button that will open a modal to connect to a wallet provider.

```javascript
import React from "react";
import {useWallet, WalletConnectButton} from "../lib";

const Component = () => {
const { connectedWallet } = useWallet();

return (
<div>
<WalletConnectButton />
<p>Connected wallet: {connectedWallet?.walletInfo?.name || "---"}</p>
</div>
);
};

export default Component;

```
| Property | Type | Description |
|-----------------|--------------|-----------------------------------------------------------------------------|
| wallets | SeiWallet[]? | A stargate signing client. |
| buttonClassName | string | A css class name for styling the button |
| primaryColor | string | A hex value of the color you want to tint the text and icons with |
| secondaryColor | string | A secondary hex value of the color you want to tint the text and icons with |
| backgroundColor | string | A hex value of the color you want to use as a background |
*If your page has a <WalletConnectButton/> on the page it can be opened programmatically by calling the hook "useSelectWallet"*
## useSelectWallet()
This hook allows you to programmatically open and close the wallet modal.
```javascript
import React from "react";
import { useWallet, useSelectWallet } from "../lib";

const Component = () => {
const { connectedWallet } = useWallet();
const { openModal, closeModal } = useSelectWallet();

return (
<div>
<button onClick={openModal}>Open Modal</button>
<p>Connected wallet: {connectedWallet?.walletInfo?.name || "---"}</p>
</div>
);
};

export default Component;

```
### Other helpful packages
- [@sei-js/core](https://www.npmjs.com/package/@sei-js/core) - TypeScript library containing helper functions for wallet connection, transaction sig
ning, and RPC querying.
- [@sei-js/proto](https://www.npmjs.com/package/@sei-js/proto) - TypeScript library for Sei protobufs generated using Telescope
- [@sei-js/proto](https://www.npmjs.com/package/@sei-js/proto) - TypeScript library for Sei protobufs generated using Telescope
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"private": false,
"dependencies": {
"@sei-js/core": "1.3.6",
"react-outside-click-handler": "^1.3.0"
"react-outside-click-handler": "^1.3.0",
"react-icons": "4.9.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
Expand Down
Binary file removed packages/react/src/lib/assets/coin98.png
Binary file not shown.
3 changes: 0 additions & 3 deletions packages/react/src/lib/assets/default.svg

This file was deleted.

Binary file removed packages/react/src/lib/assets/falcon.png
Binary file not shown.
Binary file removed packages/react/src/lib/assets/keplr.png
Binary file not shown.
Binary file removed packages/react/src/lib/assets/leap.png
Binary file not shown.
Loading

0 comments on commit 4c5ff54

Please sign in to comment.