Skip to content

Commit

Permalink
add signer.toEthers for ethersv6 adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsdls committed Feb 16, 2024
1 parent 04bc889 commit b693937
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 7 deletions.
213 changes: 207 additions & 6 deletions packages/thirdweb/src/adapters/ethers6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type * as ethers5 from "ethers5";
import type * as ethers6 from "ethers6";
import * as universalethers from "ethers";
import type { Abi } from "abitype";
import type { Hex, TransactionSerializable } from "viem";
import type { AccessList, Hex, TransactionSerializable } from "viem";
import type { ThirdwebClient } from "../client/client.js";
import {
getRpcUrlForChain,
Expand All @@ -11,6 +11,9 @@ import {
} from "../chain/index.js";
import { getContract, type ThirdwebContract } from "../contract/index.js";
import type { Account } from "../wallets/interfaces/wallet.js";
import { normalizeChainId } from "../wallets/utils/normalizeChainId.js";
import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js";
import { uint8ArrayToHex } from "../utils/uint8-array.js";

type Ethers6 = typeof ethers6;

Expand Down Expand Up @@ -105,6 +108,21 @@ export const ethers6Adapter = /* @__PURE__ */ (() => {
* ```
*/
fromEthers: (signer: ethers6.Signer) => fromEthersSigner(signer),

/**
* Converts a Thirdweb account to an ethers.js signer.
* @param client - The Thirdweb client.
* @param chain - The blockchain chain.
* @param account - The Thirdweb account.
* @returns A promise that resolves to an ethers.js signer.
* @example
* ```ts
* import { ethers6Adapter } from "@thirdweb/adapters/erthers6";
* const signer = await ethers6Adapter.signer.toEthers(client, chain, account);
* ```
*/
toEthers: (client: ThirdwebClient, chain: Chain, account: Account) =>
toEthersSigner(ethers, client, chain, account),
},
};
})();
Expand Down Expand Up @@ -208,11 +226,12 @@ async function fromEthersSigner(signer: ethers6.Signer): Promise<Account> {
) as Promise<Hex>;
},
signTransaction: async (tx) => {
return signer.signTransaction(alignTx(tx)) as Promise<Hex>;
return signer.signTransaction(alignTxToEthers(tx)) as Promise<Hex>;
},
sendTransaction: async (tx) => {
const transactionHash = (await signer.sendTransaction(alignTx(tx)))
.hash as Hex;
const transactionHash = (
await signer.sendTransaction(alignTxToEthers(tx))
).hash as Hex;
return {
transactionHash,
};
Expand All @@ -230,12 +249,106 @@ async function fromEthersSigner(signer: ethers6.Signer): Promise<Account> {
}

/**
* Aligns a transaction object to fit the format expected by ethers5 library.
* Converts a Thirdweb account to an ethers.js signer.
* @param ethers - The ethers.js library.
* @param client - The Thirdweb client.
* @param chain - The blockchain chain.
* @param account - The Thirdweb account.
* @returns A promise that resolves to an ethers.js signer.
* @internal
*/
async function toEthersSigner(
ethers: Ethers6,
client: ThirdwebClient,
chain: Chain,
account: Account,
): Promise<ethers6.Signer> {
class ThirdwebAdapterSigner extends ethers.AbstractSigner<ethers6.JsonRpcProvider> {
address: string;
override provider: ethers6.ethers.JsonRpcProvider;
constructor(provider: ethers6.JsonRpcProvider, address: string) {
super(provider);
this.address = address;
this.provider = provider;
}

override async getAddress(): Promise<string> {
// needs to be a promise because ethers6 returns a promise
return this.address;
}
override connect(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_provider: ethers6.ethers.Provider | null,
): ethers6.ethers.Signer {
return this;
}
override async sendTransaction(
tx: ethers6.ethers.TransactionRequest & { chainId: number },
): Promise<ethers6.ethers.TransactionResponse> {
const alignedTx = await alignTxFromEthers(tx);
const result = await account.sendTransaction({
...alignedTx,
chainId: tx.chainId,
});

const txResponseParams: ethers6.TransactionResponseParams = {
...alignedTx,
blockHash: null,
from: this.address,
hash: result.transactionHash as string,
blockNumber: null,
index: 0,
gasLimit: alignedTx.gas as bigint,
// @ts-expect-error - we don't have this reliably so we'll just not include it
signature: null,
};

return new ethers.TransactionResponse(txResponseParams, this.provider);
}
override async signTransaction(
tx: ethers6.ethers.TransactionRequest,
): Promise<string> {
if (!account.signTransaction) {
throw new Error("Account does not support signing transactions");
}
const viemTx = await alignTxFromEthers(tx);

return account.signTransaction(viemTx);
}
override signMessage(message: string | Uint8Array): Promise<string> {
return account.signMessage({
message:
typeof message === "string" ? message : uint8ArrayToHex(message),
});
}
override signTypedData(
domain: ethers6.ethers.TypedDataDomain,
types: Record<string, ethers6.ethers.TypedDataField[]>,
value: Record<string, any>,
): Promise<string> {
return account.signTypedData({
// @ts-expect-error - types don't fully align here but works fine?
domain: domain ?? undefined,
types: types ?? undefined,
message: value,
});
}
}
return new ThirdwebAdapterSigner(
toEthersProvider(ethers, client, chain),
account.address,
);
}

/**
* Aligns a transaction object to fit the format expected by ethers6 library.
* @param tx - The transaction object to align.
* @returns The aligned transaction object.
* @internal
*/
function alignTx(tx: TransactionSerializable): ethers6.TransactionRequest {
function alignTxToEthers(
tx: TransactionSerializable,
): ethers6.TransactionRequest {
const { type: viemType, ...rest } = tx;

// massage "type" to fit ethers
Expand All @@ -260,3 +373,91 @@ function alignTx(tx: TransactionSerializable): ethers6.TransactionRequest {
}
return { ...rest, type };
}

async function alignTxFromEthers(
tx: ethers6.TransactionRequest,
): Promise<TransactionSerializable> {
const {
type: ethersType,
accessList,
chainId: ethersChainId,
to: ethersTo,
// unused here on purpose
// eslint-disable-next-line @typescript-eslint/no-unused-vars
from,
data,
nonce,
value,
gasPrice,
gasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
...rest
} = tx;
let chainId: number | undefined;
if (ethersChainId) {
chainId = normalizeChainId(ethersChainId);
}

// massage "type" to fit ethers
let type: string;
switch (ethersType) {
case 0: {
type = "legacy";
break;
}
case 1: {
type = "eip2930";
break;
}
case 2: {
type = "eip1559";
break;
}
default: {
// fall back to legacy
type = "legacy";
break;
}
}

const to = await resolveEthers6Address(ethersTo);

return {
...rest,
// access list is the same values just strictly typed
accessList: accessList as AccessList,
chainId,
type,
to,
data: (data ?? undefined) as Hex | undefined,
nonce: nonce ?? undefined,
value: value ? bigNumberIshToBigint(value) : undefined,
gasPrice: gasPrice ? bigNumberIshToBigint(gasPrice) : undefined,
gas: gasLimit ? bigNumberIshToBigint(gasLimit) : undefined,
maxFeePerGas: maxFeePerGas ? bigNumberIshToBigint(maxFeePerGas) : undefined,
maxPriorityFeePerGas: maxPriorityFeePerGas
? bigNumberIshToBigint(maxPriorityFeePerGas)
: undefined,
};
}

async function resolveEthers6Address(
address: ethers6.AddressLike | null | undefined,
): Promise<string | null | undefined> {
if (!address) {
return address;
}
address = await resolvePromisedValue(address);
if (typeof address === "string") {
return address;
}
return address.getAddress();
}

function bigNumberIshToBigint(value: ethers6.BigNumberish): bigint {
if (typeof value === "bigint") {
return value;
}
return BigInt(value);
}
5 changes: 4 additions & 1 deletion packages/thirdweb/src/wallets/utils/normalizeChainId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { hexToNumber, isHex } from "viem";
/**
* @internal
*/
export function normalizeChainId(chainId: string | number): number {
export function normalizeChainId(chainId: string | number | bigint): number {
if (typeof chainId === "number") {
return chainId;
}
if (isHex(chainId)) {
return hexToNumber(chainId);
}
if (typeof chainId === "bigint") {
return Number(chainId);
}
return parseInt(chainId, 10);
}

0 comments on commit b693937

Please sign in to comment.