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

fix: signer typescript issues #158

Open
wants to merge 5 commits into
base: develop
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
13 changes: 3 additions & 10 deletions src/sdk/account/toNexusAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {
type Chain,
type ClientConfig,
type Hex,
type LocalAccount,
type OneOf,
type Prettify,
type PublicClient,
type RpcSchema,
Expand Down Expand Up @@ -68,16 +66,16 @@ import {
// Utils
import type { Call } from "./utils/Types"
import {
type EthersWallet,
type TypedDataWith712,
type ValidSigner,
addressEquals,
eip712WrapHash,
getAccountDomainStructFields,
getTypesForEIP712Domain,
isNullOrUndefined,
typeToString
} from "./utils/Utils"
import { type EthereumProvider, type Signer, toSigner } from "./utils/toSigner"
import { type Signer, toSigner } from "./utils/toSigner"

/**
* Parameters for creating a Nexus Smart Account
Expand All @@ -88,12 +86,7 @@ export type ToNexusSmartAccountParameters = {
/** The transport configuration */
transport: ClientConfig["transport"]
/** The signer account or address */
signer: OneOf<
| EthereumProvider
| WalletClient<Transport, Chain | undefined, Account>
| LocalAccount
| EthersWallet
>
signer: ValidSigner
/** Optional index for the account */
index?: bigint | undefined
/** Optional active validation module */
Expand Down
46 changes: 37 additions & 9 deletions src/sdk/account/utils/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import {
type Account,
type Address,
type Chain,
type Client,
type Hash,
type Hex,
type LocalAccount,
type PublicClient,
type Transport,
type TypedData,
type TypedDataDomain,
type TypedDataParameter,
type WalletClient,
concat,
decodeFunctionResult,
encodeAbiParameters,
Expand Down Expand Up @@ -402,15 +407,6 @@ export const getTenderlyDetails = (): TenderlyDetails | null => {
export const safeMultiplier = (bI: bigint, multiplier: number): bigint =>
BigInt(Math.round(Number(bI) * multiplier))

export type EthersWallet = {
signTransaction: (...args: AnyData[]) => Promise<AnyData>
signMessage: (...args: AnyData[]) => Promise<AnyData>
signTypedData: (...args: AnyData[]) => Promise<AnyData>
getAddress: () => Promise<AnyData>
address: Address | string
provider: AnyData
}

export const getAllowance = async (
client: PublicClient,
accountAddress: Address,
Expand All @@ -426,6 +422,38 @@ export const getAllowance = async (
return approval as bigint
}

type EthersWalletSigner = {
signTransaction: (...args: AnyData) => AnyData
signMessage: (...args: AnyData) => AnyData
signTypedData: (...args: AnyData) => AnyData
getAddress: () => Promise<AnyData>
provider: unknown
}

type JsonRpcSigner = {
signTransaction: (...args: AnyData) => AnyData
signMessage: (...args: AnyData) => AnyData
_signTypedData: (...args: AnyData) => AnyData
getAddress: () => Promise<AnyData>
provider: unknown
}

type LocalAccountSigner = {
type: "local"
} & LocalAccount

type WalletClientSigner = WalletClient<Transport, Chain | undefined, Account>

type ProviderSigner = {
request(...args: AnyData): Promise<AnyData>
}

export type ValidSigner =
| EthersWalletSigner
| LocalAccountSigner
| WalletClientSigner
| ProviderSigner
| JsonRpcSigner
export function parseRequestArguments(input: string[]) {
const fieldsToOmit = [
"callGasLimit",
Expand Down
13 changes: 10 additions & 3 deletions src/sdk/account/utils/toSigner.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { JsonRpcProvider, JsonRpcSigner, ethers } from "ethers"
import { JsonRpcProvider, ethers } from "ethers"
import { http, type Address, type Hex, createWalletClient } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { afterAll, beforeAll, describe, expect, it } from "vitest"
import { toNetwork, toNetworks } from "../../../test/testSetup"
import { type NetworkConfig, killNetwork, pKey } from "../../../test/testUtils"
import { type EthersWallet, addressEquals } from "./Utils"
import { addressEquals } from "./Utils"
import { toSigner } from "./toSigner"

const TEST_TYPED_DATA = {
Expand Down Expand Up @@ -72,8 +72,15 @@ describe("utils.toSigner", () => {
})

it("should work with ethers Wallet", async () => {
const wallet = new ethers.Wallet(pKey) as EthersWallet
const wallet = new ethers.Wallet(pKey)
const signer = await toSigner({ signer: wallet })
expect(signer.address).toBe(wallet.address)
})

it("should work with ethers JsonRpcSigner", async () => {
const provider = new JsonRpcProvider(network.rpcUrl)
const signer = await provider.getSigner()
const nexusSigner = await toSigner({ signer })
expect(nexusSigner.address).toBe(await signer.getAddress())
})
})
177 changes: 93 additions & 84 deletions src/sdk/account/utils/toSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
type Chain,
type Hex,
type LocalAccount,
type OneOf,
type Transport,
type WalletClient,
createWalletClient,
Expand All @@ -17,7 +16,7 @@ import { toAccount } from "viem/accounts"
import { signTypedData } from "viem/actions"
import { getAction } from "viem/utils"
import type { AnyData } from "../../modules/utils/Types"
import type { EthersWallet } from "./Utils"
import type { ValidSigner } from "./Utils"

export type EthereumProvider = { request(...args: AnyData): Promise<AnyData> }

Expand All @@ -36,103 +35,113 @@ export type Signer = LocalAccount
* @throws {Error} When signTransaction is called (not supported)
* @throws {Error} When address is required but not provided
*/
export async function toSigner<
provider extends EthereumProvider,
wallet extends EthersWallet
>({
export async function toSigner({
signer,
address
}: {
signer: OneOf<
| provider
| wallet
| WalletClient<Transport, Chain | undefined, Account>
| LocalAccount
>
signer: ValidSigner
address?: Address
}): Promise<LocalAccount> {
if ("provider" in signer) {
const wallet = signer as EthersWallet
const address = await wallet.getAddress()
return toAccount({
address: getAddress(address),
async signMessage({ message }): Promise<Hex> {
if (typeof message === "string") {
return await wallet.signMessage(message)
}
if (typeof message?.raw === "string") {
return await wallet.signMessage(hexToBytes(message.raw))
if (typeof signer === "object") {
if ("provider" in signer) {
const wallet = signer
const address = await wallet.getAddress()
return toAccount({
address: getAddress(address),
async signMessage({ message }): Promise<Hex> {
if (typeof message === "string") {
return await wallet.signMessage(message)
}
if (typeof message?.raw === "string") {
return await wallet.signMessage(hexToBytes(message.raw))
}
return await wallet.signMessage(message.raw)
},
async signTransaction(_) {
throw new Error("Not supported")
},
async signTypedData(typedData) {
if ("_signTypedData" in wallet) {
return wallet._signTypedData(
typedData.domain,
typedData.types,
typedData.message
)
}
return wallet.signTypedData(
typedData.domain,
typedData.types,
typedData.message
)
}
return await wallet.signMessage(message.raw)
},
async signTransaction(_) {
throw new Error("Not supported")
},
async signTypedData(typedData) {
return wallet.signTypedData(
typedData.domain,
typedData.types,
typedData.message
)
}
})
}
})
}

if ("type" in signer && signer.type === "local") {
return signer as LocalAccount
}
if ("type" in signer && signer.type === "local") {
return signer as LocalAccount
}

let walletClient:
| WalletClient<Transport, Chain | undefined, Account>
| undefined = undefined
let walletClient:
| WalletClient<Transport, Chain | undefined, Account>
| undefined = undefined

if ("request" in signer) {
if (!address) {
try {
;[address] = await (signer as EthereumProvider).request({
method: "eth_requestAccounts"
})
} catch {
;[address] = await (signer as EthereumProvider).request({
method: "eth_accounts"
})
if ("request" in signer) {
if (!address) {
try {
;[address] = await (signer as EthereumProvider).request({
method: "eth_requestAccounts"
})
} catch {
;[address] = await (signer as EthereumProvider).request({
method: "eth_accounts"
})
}
}
if (!address) {
// For TS to be happy
throw new Error("address is required")
}
walletClient = createWalletClient({
account: address,
transport: custom(signer as EthereumProvider)
})
}
if (!address) {
// For TS to be happy
throw new Error("address is required")

if (!walletClient) {
walletClient = signer as WalletClient<
Transport,
Chain | undefined,
Account
>
}
walletClient = createWalletClient({
account: address,
transport: custom(signer as EthereumProvider)
})
}

if (!walletClient) {
walletClient = signer as WalletClient<Transport, Chain | undefined, Account>
}
const addressFromWalletClient =
walletClient?.account?.address ??
(await walletClient?.getAddresses())?.[0]

const addressFromWalletClient =
walletClient?.account?.address ?? (await walletClient?.getAddresses())?.[0]
if (!addressFromWalletClient) {
throw new Error("address not found in wallet client")
}

if (!addressFromWalletClient) {
throw new Error("address not found in wallet client")
return toAccount({
address: addressFromWalletClient,
async signMessage({ message }) {
return walletClient.signMessage({ message })
},
async signTypedData(typedData) {
return getAction(
walletClient,
signTypedData,
"signTypedData"
)(typedData as AnyData)
},
async signTransaction(_) {
throw new Error(
"Smart account signer doesn't need to sign transactions"
)
}
})
}

return toAccount({
address: addressFromWalletClient,
async signMessage({ message }) {
return walletClient.signMessage({ message })
},
async signTypedData(typedData) {
return getAction(
walletClient,
signTypedData,
"signTypedData"
)(typedData as AnyData)
},
async signTransaction(_) {
throw new Error("Smart account signer doesn't need to sign transactions")
}
})
throw new Error("Signer must be an non empty object")
}
5 changes: 2 additions & 3 deletions src/sdk/account/utils/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ParamType, ethers } from "ethers"
import { type AbiParameter, encodeAbiParameters } from "viem"
import { generatePrivateKey } from "viem/accounts"
import { describe, expect, test } from "vitest"
import type { EthersWallet } from "./Utils"
import { toSigner } from "./toSigner"

describe("utils", async () => {
Expand Down Expand Up @@ -62,14 +61,14 @@ describe("utils", async () => {
)

test.concurrent("should support ethers Wallet", async () => {
const wallet = new ethers.Wallet(privKey) as EthersWallet
const wallet = new ethers.Wallet(privKey)
const signer = await toSigner({ signer: wallet })
const sig = await signer.signMessage({ message: "test" })
expect(sig).toBeDefined()
})

test.concurrent("should support ethers Wallet signTypedData", async () => {
const wallet = new ethers.Wallet(privKey) as EthersWallet
const wallet = new ethers.Wallet(privKey)
const signer = await toSigner({ signer: wallet })
const appDomain = {
chainId: 1,
Expand Down
Loading
Loading