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

Viem Support #459

Merged
merged 8 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
2,395 changes: 1,698 additions & 697 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 14 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"long": "^5.2.0"
},
"devDependencies": {
"@commitlint/cli": "^16.1.0",
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^16.0.0",
"@metamask/providers": "^11.1.1",
"@types/benchmark": "^2.1.2",
Expand All @@ -100,31 +100,32 @@
"@types/elliptic": "^6.4.14",
"@types/jest": "^28.1.3",
"@types/node": "^18.14.0",
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"benny": "^3.7.1",
"dd-trace": "^2.12.2",
"esbuild": "^0.17.16",
"esbuild-plugin-external-global": "^1.0.1",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^37.9.1",
"eslint": "^8.50.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsdoc": "^46.8.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"husky": "^7.0.4",
"jest": "^29.6.0",
"jest-environment-jsdom": "^28.1.3",
"prettier": "^2.4.0",
"prettier": "^3.0.3",
"rimraf": "^5.0.0",
"semantic-release": "^21.0.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tsup": "^6.7.0",
"typedoc": "^0.22.11",
"typescript": "^4.4.3"
"typedoc": "^0.25.1",
"typescript": "^5.2.2",
"viem": "^1.12.1"
},
"engines": {
"node": ">=18"
Expand Down
14 changes: 7 additions & 7 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EnvelopeMapper,
buildUserInviteTopic,
isBrowser,
getSigner,
} from './utils'
import { utils } from 'ethers'
import { Signer } from './types/Signer'
Expand Down Expand Up @@ -42,6 +43,7 @@ import {
import { hasMetamaskWithSnaps } from './keystore/snapHelpers'
import { version as snapVersion, package as snapPackage } from './snapInfo.json'
import { ExtractDecodedType } from './types/client'
import { WalletClient } from 'viem'
const { Compression } = proto

// eslint-disable @typescript-eslint/explicit-module-boundary-types
Expand Down Expand Up @@ -206,7 +208,6 @@ export type ClientOptions = Flatten<

/**
* Provide a default client configuration. These settings can be used on their own, or as a starting point for custom configurations
*
* @param opts additional options to override the default settings
*/
export function defaultOptions(opts?: Partial<ClientOptions>): ClientOptions {
Expand Down Expand Up @@ -302,23 +303,23 @@ export default class Client<ContentTypes = any> {

/**
* Create and start a client associated with given wallet.
*
* @param wallet the wallet as a Signer instance
* @param opts specify how to to connect to the network
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any
static async create<ContentCodecs extends ContentCodec<any>[] = []>(
wallet: Signer | null,
wallet: Signer | WalletClient | null,
opts?: Partial<ClientOptions> & { codecs?: ContentCodecs }
): Promise<
Client<
ExtractDecodedType<[...ContentCodecs, TextCodec][number]> | undefined
>
> {
const signer = getSigner(wallet)
const options = defaultOptions(opts)
const apiClient = options.apiClientFactory(options)
const keystore = await bootstrapKeystore(options, apiClient, wallet)
const keystore = await bootstrapKeystore(options, apiClient, signer)
const publicKeyBundle = new PublicKeyBundle(
await keystore.getPublicKeyBundle()
)
Expand All @@ -343,10 +344,10 @@ export default class Client<ContentTypes = any> {
* messages.
*/
static async getKeys<U>(
wallet: Signer | null,
wallet: Signer | WalletClient | null,
opts?: Partial<ClientOptions> & { codecs?: U }
): Promise<Uint8Array> {
const client = await Client.create(wallet, opts)
const client = await Client.create(getSigner(wallet), opts)
const keys = await client.keystore.getPrivateKeyBundle()
return new PrivateKeyBundleV1(keys).encode()
}
Expand Down Expand Up @@ -583,7 +584,6 @@ export default class Client<ContentTypes = any> {
* no pre-processing or encryption applied.
*
* Primarily used internally
*
* @param envelopes PublishParams[]
*/
async publishEnvelopes(envelopes: PublishParams[]): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/Compression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { content as proto } from '@xmtp/proto'
//
// Compression
//

export async function decompress(
encoded: proto.EncodedContent,
maxSize: number
Expand Down
6 changes: 1 addition & 5 deletions src/Stream.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
OnConnectionLostCallback,
SubscriptionManager,
UnsubscribeFn,
} from './ApiClient'
import { OnConnectionLostCallback, SubscriptionManager } from './ApiClient'
import Client from './Client'
import { messageApi } from '@xmtp/proto'

Expand Down
7 changes: 1 addition & 6 deletions src/codecs/Text.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
ContentTypeId,
ContentCodec,
EncodedContent,
CodecRegistry,
} from '../MessageContent'
import { ContentTypeId, ContentCodec, EncodedContent } from '../MessageContent'

// xmtp.org/text
//
Expand Down
2 changes: 1 addition & 1 deletion src/keystore/SnapKeystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async function getResponse<T extends keyof Keystore>(
req: Uint8Array | null,
meta: SnapMeta,
snapId: string
): Promise<typeof apiDefs[T]['res']> {
): Promise<(typeof apiDefs)[T]['res']> {
return snapRPC(method, apiDefs[method], req, meta, snapId)
}

Expand Down
13 changes: 0 additions & 13 deletions src/types/streams-polyfill/index.d.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './date'
export * from './bytes'
export * from './browser'
export * from './semver'
export * from './viem'
2 changes: 1 addition & 1 deletion src/utils/keystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { WithoutUndefined } from './typedefs'
export const getResultOrThrow = <
T extends
| keystore.DecryptResponse_Response
| keystore.EncryptResponse_Response
| keystore.EncryptResponse_Response,
>(
response: T
): WithoutUndefined<NonNullable<T['result']>> => {
Expand Down
36 changes: 36 additions & 0 deletions src/utils/viem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { WalletClient } from 'viem'
import { Signer } from '../types/Signer'

export function getSigner(wallet: Signer | WalletClient | null): Signer | null {
if (!wallet) {
return null
}
if (isWalletClient(wallet)) {
return convertWalletClientToSigner(wallet)
}
if (typeof wallet.getAddress !== 'function') {
throw new Error('Unknown wallet type')
}
return wallet
}

function isWalletClient(wallet: Signer | WalletClient): wallet is WalletClient {
return 'type' in wallet && wallet.type === 'walletClient'
}

export function convertWalletClientToSigner(
walletClient: WalletClient
): Signer {
const { account } = walletClient
if (!account || !account.address) {
throw new Error('WalletClient is not configured')
}

return {
getAddress: async () => account.address,
signMessage: async (message: string) => {
const signature = await walletClient.signMessage({ message, account })
return signature
},
}
}
50 changes: 50 additions & 0 deletions test/Client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { Wallet } from 'ethers'
import { NetworkKeystoreProvider } from '../src/keystore/providers'
import { PublishResponse } from '@xmtp/proto/ts/dist/types/message_api/v1/message_api.pb'
import LocalStoragePonyfill from '../src/keystore/persistence/LocalStoragePonyfill'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
import { generatePrivateKey } from 'viem/accounts'

type TestCase = {
name: string
Expand Down Expand Up @@ -427,4 +431,50 @@ describe('ClientOptions', () => {
expect(isSnapsReady).toBe(true)
})
})

describe('viem', () => {
it('allows you to use a viem WalletClient', async () => {
const privateKey = generatePrivateKey()
const account = privateKeyToAccount(privateKey)

const walletClient = createWalletClient({
account,
chain: mainnet,
neekolas marked this conversation as resolved.
Show resolved Hide resolved
transport: http(),
})

const c = await Client.create(walletClient)
expect(c).toBeDefined()
expect(c.address).toEqual(account.address)
})

it('creates an identical client between viem and ethers', async () => {
const randomWallet = Wallet.createRandom()
const privateKey = randomWallet.privateKey
const account = privateKeyToAccount(privateKey as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
})

const viemClient = await Client.create(walletClient)
const ethersClient = await Client.create(randomWallet)
expect(viemClient.address).toEqual(ethersClient.address)
expect(
viemClient.publicKeyBundle.equals(ethersClient.publicKeyBundle)
).toBe(true)
})

it('fails if you use a viem WalletClient without an account', async () => {
const walletClient = createWalletClient({
chain: mainnet,
transport: http(),
})

await expect(Client.create(walletClient)).rejects.toThrow(
'WalletClient is not configured'
)
})
})
})
4 changes: 1 addition & 3 deletions test/keystore/persistence/EncryptedPersistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ describe('EncryptedPersistence', () => {
const goodData = SignedEciesCiphertext.fromBytes(encryptedBytes!)
const signedBySomeoneElse = await SignedEciesCiphertext.create(
goodData.ciphertext,
(
await PrivateKeyBundleV1.generate()
).identityKey
(await PrivateKeyBundleV1.generate()).identityKey
)
await persistence.setItem(TEST_KEY, signedBySomeoneElse.toBytes())

Expand Down
Loading