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

[WIP] dynamic connector in repo #224

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@eslint/plugin-kit": ">=0.2.3",
"publint": "^0.2.12",
"tsc-watch": "^6.2.0",
"ts-node": "^10.9.2",
"typescript-eslint": "^8.5.0"
}
}
41 changes: 41 additions & 0 deletions packages/agw-dynamic-connector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Abstract Global Wallet EVM

## Integrating with the Dynamic SDK

### Install the connector

Make sure to install the connector with the correct version (@3 for sdk v3, @4 for sdk v4, etc...):

```
npm install @dynamic-labs-connectors/abstract-global-wallet-evm@3
```

### Use the connector

To integrate with the Dynamic SDK, you just need to pass `SafeEvmConnectors` to the `walletConnectors` prop of the `DynamicContextProvider` component.

```tsx
import { DynamicContextProvider } from '@dynamic-labs/sdk-react-score';
import { AbstractEvmWalletConnector } from '@dynamic-labs-connectors/abstract-global-wallet-evm';

const App = () => {
return (
<DynamicContextProvider
settings={{
environmentId: 'REPLACE-WITH-YOUR-ENVIRONMENT-ID',
walletConnectors: [AbstractEvmWalletConnector],
}}
>
<DynamicWidget />
</DynamicContextProvider>
);
};
```

## Building

Run `nx build @dynamic-labs-connectors/abstract-global-wallet-evm` to build the library.

## Running unit tests

Run `nx test @dynamic-labs-connectors/abstract-global-wallet-evm` to execute the unit tests via [Jest](https://jestjs.io).
19 changes: 19 additions & 0 deletions packages/agw-dynamic-connector/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Config } from 'jest';

// Uncomment if using global setup/teardown files being transformed via swc
// https://nx.dev/nx-api/jest/documents/overview#global-setupteardown-with-nx-libraries
// jest needs EsModule Interop to find the default exported setup/teardown functions
// swcJestConfig.module.noInterop = false;
const esmModules = ['@simplewebauthn', '@wagmi', 'wagmi'];

const config: Config = {
preset: 'ts-jest',
displayName: '@dynamic-labs-connectors/abstract-global-wallet',
transformIgnorePatterns: [
`node_modules/(?!(?:.pnpm/)?(${esmModules.join('|')}))`,
],
moduleFileExtensions: ['ts', 'js', 'html'],
testEnvironment: 'node',
};

export default config;
43 changes: 43 additions & 0 deletions packages/agw-dynamic-connector/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@abstract-foundation/agw-dynamic-connector",
"version": "1.4.0",
"type": "module",
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc -p tsconfig.lib.json",
"test": "jest"
},
"private": false,
"exports": {
".": {
"import": "./src/index.js",
"require": "./src/index.js",
"types": "./src/index.d.ts"
}
},
"dependencies": {
"@abstract-foundation/agw-client": "^1.3.1"
},
"peerDependencies": {
"@dynamic-labs/ethereum": "^4.0.0",
"@dynamic-labs/ethereum-core": "^4.0.0",
"@dynamic-labs/utils": "^4.0.0",
"@dynamic-labs/wallet-connector-core": "^4.0.0",
"@privy-io/cross-app-connect": "^0.1.5",
"viem": "^2.22.17"
},
"devDependencies": {
"@dynamic-labs/utils": "^4.0.0",
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.5.5",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type EthereumWalletConnectorOpts } from '@dynamic-labs/ethereum-core';
import { beforeEach, describe, expect, it, jest } from '@jest/globals';

import { AbstractEvmWalletConnector } from './AbstractEvmWalletConnector.js';

jest.mock('@dynamic-labs/wallet-connector-core', () => ({
// @ts-expect-error this is an object so we can use spread type
...jest.requireActual('@dynamic-labs/wallet-connector-core'),
logger: {
debug: jest.fn(),
},
}));

const walletConnectorProps: EthereumWalletConnectorOpts = {
walletBook: {} as any,
evmNetworks: [],
} as any as EthereumWalletConnectorOpts;

describe('AbstractEvmWalletConnector', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('findProvider', () => {
it('should return the provider', () => {
const connector = new AbstractEvmWalletConnector(walletConnectorProps);
expect(connector.findProvider()).toBeDefined();
});
});
});
132 changes: 132 additions & 0 deletions packages/agw-dynamic-connector/src/AbstractEvmWalletConnector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { transformEIP1193Provider } from '@abstract-foundation/agw-client';
import {
EthereumInjectedConnector,
type IEthereum,
} from '@dynamic-labs/ethereum';
import { type EthereumWalletConnectorOpts } from '@dynamic-labs/ethereum-core';
import { DynamicError } from '@dynamic-labs/utils';
import { logger } from '@dynamic-labs/wallet-connector-core';
import { toPrivyWalletProvider } from '@privy-io/cross-app-connect';
import { type Chain as ViemChain, EIP1193Provider, toHex } from 'viem';
import { abstract, abstractTestnet } from 'viem/chains';
const AGW_APP_ID = 'cm04asygd041fmry9zmcyn5o5';

export class AbstractEvmWalletConnector extends EthereumInjectedConnector {
/**
* The name of the wallet connector
* @override Required override from the base connector class
*/
override name = 'Abstract';

abstractNetworks: ViemChain[];

static initHasRun = false;

/**
* The constructor for the connector, with the relevant metadata
* @param props The options for the connector
*/
constructor(props: EthereumWalletConnectorOpts) {
super({
...props,
metadata: {
id: 'abstract',
name: 'Abstract',
icon: 'https://abstract-assets.abs.xyz/icons/light.png',
},
});

this.abstractNetworks = [];
for (const network of props.evmNetworks) {
if (network.chainId === abstractTestnet.id) {
this.abstractNetworks.push(abstractTestnet);
}
if (network.chainId === abstract.id) {
this.abstractNetworks.push(abstract);
}
}
}

/**
* Returns false because we only support Abstract
*/
override supportsNetworkSwitching(): boolean {
return false;
}

override isInstalledOnBrowser(): boolean {
return true;
}

override async init(): Promise<void> {
// this function can be called multiple times, so you must have a flag that indicates if the connector is already initialized
// (can't be an instance variable, because it will be reset every time the connector is instantiated)
// once the provider is initialized, you should emit the providerReady event once, and only once
if (AbstractEvmWalletConnector.initHasRun) {
return;
}
// if there are no abstract networks configured, we can't initialize the connector
if (this.abstractNetworks.length === 0) {
return;
}
AbstractEvmWalletConnector.initHasRun = true;

logger.debug('[AbstractEvmWalletConnector] onProviderReady');
this.walletConnectorEventsEmitter.emit('providerReady', {
connector: this,
});
}

override findProvider(): IEthereum | undefined {
let chain: ViemChain | undefined = this.getActiveChain();
if (!chain) {
if (this.abstractNetworks.length === 1) {
// use the only configured abstract network from the connector options
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
chain = this.abstractNetworks[0]!;
} else {
chain = abstractTestnet;
}
}

const privyProvider = toPrivyWalletProvider({
providerAppId: AGW_APP_ID,
chains: [chain],
});

const provider = transformEIP1193Provider({
provider: privyProvider as EIP1193Provider,
isPrivyCrossApp: true,
chain,
});
// Casting to IEthereum because the provider implements the eip-1193 interface
// and that the expected type for the parent class EthereumInjectedConnector
return provider as unknown as IEthereum;
}

override async getAddress(): Promise<string | undefined> {
const accounts = await this.findProvider()?.request({
method: 'eth_requestAccounts',
});
return accounts?.[0] as string | undefined;
}

override async getConnectedAccounts(): Promise<string[]> {
return (
(await this.findProvider()?.request({ method: 'eth_requestAccounts' })) ??
[]
);
}

override async signMessage(message: string): Promise<string> {
const provider = this.findProvider();
if (!provider) {
throw new DynamicError('No provider found');
}
const address = await this.getAddress();
return (await provider.request({
method: 'personal_sign',
params: [toHex(message), address],
})) as unknown as string;
}
}
8 changes: 8 additions & 0 deletions packages/agw-dynamic-connector/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type WalletConnectorConstructor } from '@dynamic-labs/wallet-connector-core';

import { AbstractEvmWalletConnector } from './AbstractEvmWalletConnector.js';

export const AbstractEvmWalletConnectors = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_props: any,
): WalletConnectorConstructor[] => [AbstractEvmWalletConnector];
43 changes: 43 additions & 0 deletions packages/agw-dynamic-connector/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"compilerOptions": {
"rootDir": ".",
"baseUrl": ".",
"sourceMap": false,
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strict": true,
"useDefineForClassFields": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"allowJs": false,
"checkJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"module": "ES2022",
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"types": ["node", "jest"]
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"exclude": ["node_modules", "tmp", "**/*.spec.ts"]
}
10 changes: 10 additions & 0 deletions packages/agw-dynamic-connector/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "dist/",
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
12 changes: 12 additions & 0 deletions packages/agw-dynamic-connector/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
Loading
Loading