Skip to content

Commit

Permalink
feat: add event emitter to the provider so that we can listen to conn…
Browse files Browse the repository at this point in the history
…ected events in dapps (#65)
  • Loading branch information
moldy530 committed Aug 3, 2023
1 parent 90c48f1 commit 35ee990
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 43 deletions.
25 changes: 15 additions & 10 deletions packages/alchemy/src/__tests__/provider.test.ts
Expand Up @@ -24,16 +24,21 @@ describe("Alchemy Provider Tests", () => {
apiKey: "test",
chain,
entryPointAddress: "0xENTRYPOINT_ADDRESS",
}).connect(
(provider) =>
new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain,
owner,
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient: provider,
})
);
}).connect((provider) => {
const account = new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain,
owner,
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient: provider,
});

account.getAddress = vi.fn(
async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"
);

return account;
});

it("should correctly sign the message", async () => {
expect(
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Expand Up @@ -51,7 +51,8 @@
"vitest": "^0.31.0"
},
"dependencies": {
"abitype": "^0.8.3"
"abitype": "^0.8.3",
"eventemitter3": "^5.0.1"
},
"peerDependencies": {
"viem": "^1.1.7"
Expand Down
36 changes: 20 additions & 16 deletions packages/core/src/account/__tests__/simple.test.ts
@@ -1,12 +1,12 @@
import { polygonMumbai } from "viem/chains";
import { describe, it } from "vitest";
import { SmartAccountProvider } from "../../provider/base.js";
import { LocalAccountSigner } from "../../signer/local-account.js";
import type { BatchUserOperationCallData } from "../../types.js";
import {
SimpleSmartContractAccount,
type SimpleSmartAccountOwner,
} from "../simple.js";
import { LocalAccountSigner } from "../../signer/local-account.js";
import type { BatchUserOperationCallData } from "../../types.js";
import { SmartAccountProvider } from "../../provider/base.js";

describe("Account Simple Tests", () => {
const dummyMnemonic =
Expand All @@ -16,18 +16,23 @@ describe("Account Simple Tests", () => {
const chain = polygonMumbai;
const signer = new SmartAccountProvider(
`${chain.rpcUrls.alchemy.http[0]}/${"test"}`,
"0xENTRYPOINT_ADDRESS",
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
chain
).connect(
(provider) =>
new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain,
owner,
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient: provider,
})
);
).connect((provider) => {
const account = new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain,
owner,
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient: provider,
});

account.getAddress = vi.fn(
async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"
);

return account;
});

it("should correctly sign the message", async () => {
expect(
Expand All @@ -41,7 +46,6 @@ describe("Account Simple Tests", () => {
});

it("should correctly encode batch transaction data", async () => {
const account = signer.account;
const data = [
{
target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
Expand All @@ -53,7 +57,7 @@ describe("Account Simple Tests", () => {
},
] satisfies BatchUserOperationCallData;

expect(await account.encodeBatchExecute(data)).toMatchInlineSnapshot(
expect(await signer.account.encodeBatchExecute(data)).toMatchInlineSnapshot(
'"0x18dfb3c7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004deadbeef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cafebabe00000000000000000000000000000000000000000000000000000000"'
);
});
Expand Down
52 changes: 50 additions & 2 deletions packages/core/src/provider/__tests__/base.test.ts
@@ -1,4 +1,4 @@
import type { Transaction } from "viem";
import { type Transaction } from "viem";
import { polygonMumbai } from "viem/chains";
import {
afterEach,
Expand All @@ -8,8 +8,8 @@ import {
vi,
type SpyInstance,
} from "vitest";
import { SmartAccountProvider } from "../base.js";
import type { UserOperationReceipt } from "../../types.js";
import { SmartAccountProvider } from "../base.js";

describe("Base Tests", () => {
let retryMsDelays: number[] = [];
Expand Down Expand Up @@ -95,4 +95,52 @@ describe("Base Tests", () => {
getUserOperationReceiptMock
);
});

it("should emit connected event on connected", async () => {
const spy = vi.spyOn(providerMock, "emit");
const account = {
chain: polygonMumbai,
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
rpcClient: providerMock.rpcClient,
getAddress: async () => "0xMOCK_ADDRESS",
} as any;

// This says the await is not important... it is. the method is not marked sync because we don't need it to be,
// but the address is emited from an async method so we want to await that
await providerMock.connect(() => account);

expect(spy.mock.calls).toMatchInlineSnapshot(`
[
[
"connect",
{
"chainId": "0x13881",
},
],
[
"accountsChanged",
[
"0xMOCK_ADDRESS",
],
],
]
`);
});

it("should emit disconnected event on disconnect", async () => {
const spy = vi.spyOn(providerMock, "emit");
providerMock.disconnect();

expect(spy.mock.calls).toMatchInlineSnapshot(`
[
[
"disconnect",
],
[
"accountsChanged",
[],
],
]
`);
});
});
31 changes: 29 additions & 2 deletions packages/core/src/provider/base.ts
@@ -1,5 +1,7 @@
import { default as EventEmitter } from "eventemitter3";
import {
fromHex,
toHex,
type Address,
type Chain,
type Hash,
Expand Down Expand Up @@ -37,6 +39,7 @@ import type {
GasEstimatorMiddleware,
ISmartAccountProvider,
PaymasterAndDataMiddleware,
ProviderEvents,
SendUserOperationResult,
} from "./types.js";

Expand Down Expand Up @@ -77,8 +80,10 @@ export type ConnectedSmartAccountProvider<
};

export class SmartAccountProvider<
TTransport extends SupportedTransports = Transport
> implements ISmartAccountProvider<TTransport>
TTransport extends SupportedTransports = Transport
>
extends EventEmitter<ProviderEvents>
implements ISmartAccountProvider<TTransport>
{
private txMaxRetries: number;
private txRetryIntervalMs: number;
Expand All @@ -94,6 +99,8 @@ export class SmartAccountProvider<
readonly account?: BaseSmartContractAccount<TTransport>,
opts?: SmartAccountProviderOpts
) {
super();

this.txMaxRetries = opts?.txMaxRetries ?? 5;
this.txRetryIntervalMs = opts?.txRetryIntervalMs ?? 2000;
this.txRetryMulitplier = opts?.txRetryMulitplier ?? 1.5;
Expand Down Expand Up @@ -411,9 +418,29 @@ export class SmartAccountProvider<
): this & { account: BaseSmartContractAccount } {
const account = fn(this.rpcClient);
defineReadOnly(this, "account", account);

this.emit("connect", {
chainId: toHex(this.chain.id),
});

account
.getAddress()
.then((address) => this.emit("accountsChanged", [address]));

return this as this & { account: typeof account };
}

disconnect(): this & { account: undefined } {
if (this.account) {
this.emit("disconnect");
this.emit("accountsChanged", []);
}

defineReadOnly(this, "account", undefined);

return this as this & { account: undefined };
}

isConnected(): this is ConnectedSmartAccountProvider<TTransport> {
return this.account !== undefined;
}
Expand Down
22 changes: 21 additions & 1 deletion packages/core/src/provider/types.ts
@@ -1,5 +1,5 @@
import type { Address } from "abitype";
import type { Hash, RpcTransactionRequest, Transport } from "viem";
import type { Hash, Hex, RpcTransactionRequest, Transport } from "viem";
import type { BaseSmartContractAccount } from "../account/base.js";
import type {
PublicErc4337Client,
Expand All @@ -17,6 +17,19 @@ import type {
type WithRequired<T, K extends keyof T> = Required<Pick<T, K>>;
type WithOptional<T, K extends keyof T> = Pick<Partial<T>, K>;

export type ConnectorData = {
chainId?: Hex;
};

export interface ProviderEvents {
chainChanged(chainId: Hex): void;
accountsChanged(accounts: Address[]): void;
connect(data: ConnectorData): void;
message({ type, data }: { type: string; data?: unknown }): void;
disconnect(): void;
error(error: Error): void;
}

export type SendUserOperationResult = {
hash: string;
request: UserOperationRequest;
Expand Down Expand Up @@ -204,4 +217,11 @@ export interface ISmartAccountProvider<
connect(
fn: (provider: PublicErc4337Client<TTransport>) => BaseSmartContractAccount
): this & { account: BaseSmartContractAccount };

/**
* Allows for disconnecting the account from the provider so you can connect the provider to another account instance
*
* @returns the provider with the account disconnected
*/
disconnect(): this & { account: undefined };
}
25 changes: 15 additions & 10 deletions packages/ethers/src/__tests__/provider-adapter.test.ts
Expand Up @@ -17,16 +17,21 @@ describe("Simple Account Tests", async () => {
const signer = EthersProviderAdapter.fromEthersProvider(
alchemyProvider,
"0xENTRYPOINT_ADDRESS"
).connectToAccount(
(rpcClient) =>
new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain: getChain(alchemyProvider.network.chainId),
owner: convertWalletToAccountSigner(owner),
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient,
})
);
).connectToAccount((rpcClient) => {
const account = new SimpleSmartContractAccount({
entryPointAddress: "0xENTRYPOINT_ADDRESS",
chain: getChain(alchemyProvider.network.chainId),
owner: convertWalletToAccountSigner(owner),
factoryAddress: "0xSIMPLE_ACCOUNT_FACTORY_ADDRESS",
rpcClient,
});

account.getAddress = vi.fn(
async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"
);

return account;
});

it("should correctly sign the message", async () => {
expect(
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Expand Up @@ -8,7 +8,7 @@
integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ==

"@alchemy/aa-core@file:packages/core":
version "0.1.0-alpha.17"
version "0.1.0-alpha.18"
dependencies:
abitype "^0.8.3"

Expand Down Expand Up @@ -6805,6 +6805,11 @@ eventemitter3@^4.0.4, eventemitter3@^4.0.7:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==

eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==

events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
Expand Down

0 comments on commit 35ee990

Please sign in to comment.