Skip to content

Commit

Permalink
Merge pull request #459 from xmtp/nmolnar/support-viem-signer
Browse files Browse the repository at this point in the history
Viem Support
  • Loading branch information
neekolas authored Sep 25, 2023
2 parents af369fb + 89fe00f commit a12ecdf
Show file tree
Hide file tree
Showing 13 changed files with 1,811 additions and 747 deletions.
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 type { 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,
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

0 comments on commit a12ecdf

Please sign in to comment.