Compare commits

...
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
66b9bf3c6b Version Packages (#7694)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-24 02:00:52 -07:00
Joaquim VergesandGitHub cf22c23b45 [SDK] Optimize 4337 signature performance (#7693) 2025-07-24 00:47:45 -07:00
12 changed files with 70 additions and 217 deletions
+7
View File
@@ -1,5 +1,12 @@
# @thirdweb-dev/nebula
## 0.2.20
### Patch Changes
- Updated dependencies [[`cf22c23`](https://github.com/thirdweb-dev/js/commit/cf22c23b45e86adbe50a2b7b40e7a283f5222941)]:
- thirdweb@5.105.20
## 0.2.19
### Patch Changes
+1 -1
View File
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.19"
"version": "0.2.20"
}
+6
View File
@@ -1,5 +1,11 @@
# thirdweb
## 5.105.20
### Patch Changes
- [#7693](https://github.com/thirdweb-dev/js/pull/7693) [`cf22c23`](https://github.com/thirdweb-dev/js/commit/cf22c23b45e86adbe50a2b7b40e7a283f5222941) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Optimize 4337 signature performance
## 5.105.19
### Patch Changes
+1 -1
View File
@@ -404,5 +404,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.105.19"
"version": "5.105.20"
}
@@ -300,14 +300,14 @@ export async function getBuyWithCryptoQuote(
Number(
Value.format(quote.originAmount, firstStep.originToken.decimals),
) *
(firstStep.originToken.prices["USD"] || 0) *
(firstStep.originToken.prices.USD || 0) *
100,
amountWei: quote.originAmount.toString(),
token: {
chainId: firstStep.originToken.chainId,
decimals: firstStep.originToken.decimals,
name: firstStep.originToken.name,
priceUSDCents: (firstStep.originToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.originToken.prices.USD || 0) * 100,
symbol: firstStep.originToken.symbol,
tokenAddress: firstStep.originToken.address,
},
@@ -323,7 +323,7 @@ export async function getBuyWithCryptoQuote(
chainId: firstStep.originToken.chainId,
decimals: firstStep.originToken.decimals,
name: firstStep.originToken.name,
priceUSDCents: (firstStep.originToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.originToken.prices.USD || 0) * 100,
symbol: firstStep.originToken.symbol,
tokenAddress: firstStep.originToken.address,
},
@@ -337,7 +337,7 @@ export async function getBuyWithCryptoQuote(
Number(
Value.format(quote.originAmount, firstStep.originToken.decimals),
) *
(firstStep.originToken.prices["USD"] || 0) *
(firstStep.originToken.prices.USD || 0) *
100,
gasCostUSDCents: 0,
slippageBPS: 0,
@@ -348,7 +348,7 @@ export async function getBuyWithCryptoQuote(
firstStep.destinationToken.decimals,
),
) *
(firstStep.destinationToken.prices["USD"] || 0) *
(firstStep.destinationToken.prices.USD || 0) *
100,
toAmountUSDCents:
Number(
@@ -357,7 +357,7 @@ export async function getBuyWithCryptoQuote(
firstStep.destinationToken.decimals,
),
) *
(firstStep.destinationToken.prices["USD"] || 0) *
(firstStep.destinationToken.prices.USD || 0) *
100,
},
fromAddress: quote.intent.sender,
@@ -372,7 +372,7 @@ export async function getBuyWithCryptoQuote(
chainId: firstStep.originToken.chainId,
decimals: firstStep.originToken.decimals,
name: firstStep.originToken.name,
priceUSDCents: (firstStep.originToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.originToken.prices.USD || 0) * 100,
symbol: firstStep.originToken.symbol,
tokenAddress: firstStep.originToken.address,
},
@@ -395,7 +395,7 @@ export async function getBuyWithCryptoQuote(
chainId: firstStep.destinationToken.chainId,
decimals: firstStep.destinationToken.decimals,
name: firstStep.destinationToken.name,
priceUSDCents: (firstStep.destinationToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.destinationToken.prices.USD || 0) * 100,
symbol: firstStep.destinationToken.symbol,
tokenAddress: firstStep.destinationToken.address,
},
@@ -198,14 +198,14 @@ export async function getBuyWithCryptoTransfer(
Number(
Value.format(quote.originAmount, firstStep.originToken.decimals),
) *
(firstStep.originToken.prices["USD"] || 0) *
(firstStep.originToken.prices.USD || 0) *
100,
amountWei: quote.originAmount.toString(),
token: {
chainId: firstStep.originToken.chainId,
decimals: firstStep.originToken.decimals,
name: firstStep.originToken.name,
priceUSDCents: (firstStep.originToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.originToken.prices.USD || 0) * 100,
symbol: firstStep.originToken.symbol,
tokenAddress: firstStep.originToken.address,
},
@@ -226,7 +226,7 @@ export async function getBuyWithCryptoTransfer(
firstStep.originToken.decimals,
),
) *
(firstStep.originToken.prices["USD"] || 0) *
(firstStep.originToken.prices.USD || 0) *
100
: 0,
amountWei:
@@ -237,7 +237,7 @@ export async function getBuyWithCryptoTransfer(
chainId: firstStep.originToken.chainId,
decimals: firstStep.originToken.decimals,
name: firstStep.originToken.name,
priceUSDCents: (firstStep.originToken.prices["USD"] || 0) * 100,
priceUSDCents: (firstStep.originToken.prices.USD || 0) * 100,
symbol: firstStep.originToken.symbol,
tokenAddress: firstStep.originToken.address,
},
@@ -372,7 +372,7 @@ export async function getBuyWithFiatQuote(
chainId: token.chainId,
decimals: token.decimals,
name: token.name,
priceUSDCents: Math.round((token.prices["USD"] || 0) * 100),
priceUSDCents: Math.round((token.prices.USD || 0) * 100),
symbol: token.symbol,
tokenAddress: token.address,
});
@@ -408,7 +408,7 @@ export async function getBuyWithFiatQuote(
const onRampTokenObject = {
amount: onRampTokenAmount,
amountUSDCents: Math.round(
Number(onRampTokenAmount) * (onRampTokenRaw.prices["USD"] || 0) * 100,
Number(onRampTokenAmount) * (onRampTokenRaw.prices.USD || 0) * 100,
),
amountWei: onRampTokenAmountWei.toString(),
token: tokenToPayTokenInfo(onRampTokenRaw),
@@ -434,7 +434,7 @@ export async function getBuyWithFiatQuote(
routingTokenObject = {
amount: routingAmount,
amountUSDCents: Math.round(
Number(routingAmount) * (routingTokenRaw.prices["USD"] || 0) * 100,
Number(routingAmount) * (routingTokenRaw.prices.USD || 0) * 100,
),
amountWei: routingAmountWei.toString(),
token: tokenToPayTokenInfo(routingTokenRaw),
@@ -240,10 +240,10 @@ export function usePaymentMethods(options: {
validOwnedTokens.sort((a, b) => {
const aDollarBalance =
Number.parseFloat(toTokens(a.balance, a.originToken.decimals)) *
(a.originToken.prices["USD"] || 0);
(a.originToken.prices.USD || 0);
const bDollarBalance =
Number.parseFloat(toTokens(b.balance, b.originToken.decimals)) *
(b.originToken.prices["USD"] || 0);
(b.originToken.prices.USD || 0);
return bDollarBalance - aDollarBalance;
});
@@ -1,20 +1,12 @@
import type * as ox__TypedData from "ox/TypedData";
import { serializeErc6492Signature } from "../../../auth/serialize-erc6492-signature.js";
import {
verifyEip1271Signature,
verifyHash,
} from "../../../auth/verify-hash.js";
import { verifyEip1271Signature } from "../../../auth/verify-hash.js";
import type { Chain } from "../../../chains/types.js";
import type { ThirdwebClient } from "../../../client/client.js";
import {
getContract,
type ThirdwebContract,
} from "../../../contract/contract.js";
import type { ThirdwebContract } from "../../../contract/contract.js";
import { encode } from "../../../transaction/actions/encode.js";
import { readContract } from "../../../transaction/read-contract.js";
import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
import type { Hex } from "../../../utils/encoding/hex.js";
import { hashMessage } from "../../../utils/hashing/hashMessage.js";
import { hashTypedData } from "../../../utils/hashing/hashTypedData.js";
import type { SignableMessage } from "../../../utils/types.js";
@@ -40,33 +32,24 @@ export async function smartAccountSignMessage({
message: SignableMessage;
}) {
const originalMsgHash = hashMessage(message);
const is712Factory = await checkFor712Factory({
accountContract,
factoryContract,
originalMsgHash,
});
let sig: `0x${string}`;
if (is712Factory) {
const wrappedMessageHash = encodeAbiParameters(
[{ type: "bytes32" }],
[originalMsgHash],
);
const wrappedMessageHash = encodeAbiParameters(
[{ type: "bytes32" }],
[originalMsgHash],
);
sig = await options.personalAccount.signTypedData({
domain: {
chainId: options.chain.id,
name: "Account",
verifyingContract: accountContract.address,
version: "1",
},
message: { message: wrappedMessageHash },
primaryType: "AccountMessage",
types: { AccountMessage: [{ name: "message", type: "bytes" }] },
});
} else {
sig = await options.personalAccount.signMessage({ message });
}
sig = await options.personalAccount.signTypedData({
domain: {
chainId: options.chain.id,
name: "Account",
verifyingContract: accountContract.address,
version: "1",
},
message: { message: wrappedMessageHash },
primaryType: "AccountMessage",
types: { AccountMessage: [{ name: "message", type: "bytes" }] },
});
const isDeployed = await isContractDeployed(accountContract);
if (isDeployed) {
@@ -96,19 +79,7 @@ export async function smartAccountSignMessage({
signature: sig,
});
// check if the signature is valid
const isValid = await verifyHash({
address: accountContract.address,
chain: accountContract.chain,
client: accountContract.client,
hash: originalMsgHash,
signature: erc6492Sig,
});
if (isValid) {
return erc6492Sig;
}
throw new Error("Unable to verify ERC-6492 signature after signing.");
return erc6492Sig;
}
}
@@ -138,33 +109,23 @@ export async function smartAccountSignTypedData<
}
const originalMsgHash = hashTypedData(typedData);
// check if the account contract supports EIP721 domain separator based signing
const is712Factory = await checkFor712Factory({
accountContract,
factoryContract,
originalMsgHash,
});
let sig: `0x${string}`;
if (is712Factory) {
const wrappedMessageHash = encodeAbiParameters(
[{ type: "bytes32" }],
[originalMsgHash],
);
sig = await options.personalAccount.signTypedData({
domain: {
chainId: options.chain.id,
name: "Account",
verifyingContract: accountContract.address,
version: "1",
},
message: { message: wrappedMessageHash },
primaryType: "AccountMessage",
types: { AccountMessage: [{ name: "message", type: "bytes" }] },
});
} else {
sig = await options.personalAccount.signTypedData(typedData);
}
const wrappedMessageHash = encodeAbiParameters(
[{ type: "bytes32" }],
[originalMsgHash],
);
sig = await options.personalAccount.signTypedData({
domain: {
chainId: options.chain.id,
name: "Account",
verifyingContract: accountContract.address,
version: "1",
},
message: { message: wrappedMessageHash },
primaryType: "AccountMessage",
types: { AccountMessage: [{ name: "message", type: "bytes" }] },
});
const isDeployed = await isContractDeployed(accountContract);
if (isDeployed) {
@@ -194,21 +155,7 @@ export async function smartAccountSignTypedData<
signature: sig,
});
// check if the signature is valid
const isValid = await verifyHash({
address: accountContract.address,
chain: accountContract.chain,
client: accountContract.client,
hash: originalMsgHash,
signature: erc6492Sig,
});
if (isValid) {
return erc6492Sig;
}
throw new Error(
"Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid.",
);
return erc6492Sig;
}
}
@@ -233,40 +180,6 @@ export async function confirmContractDeployment(args: {
}
}
async function checkFor712Factory({
factoryContract,
accountContract,
originalMsgHash,
}: {
factoryContract: ThirdwebContract;
accountContract: ThirdwebContract;
originalMsgHash: Hex;
}) {
try {
const implementationAccount = await readContract({
contract: factoryContract,
method: "function accountImplementation() public view returns (address)",
});
// check if the account contract supports EIP721 domain separator or modular based signing
const is712Factory = await readContract({
contract: getContract({
address: implementationAccount,
chain: accountContract.chain,
client: accountContract.client,
}),
method:
"function getMessageHash(bytes32 _hash) public view returns (bytes32)",
params: [originalMsgHash],
})
.then((res) => res !== "0x")
.catch(() => false);
return is712Factory;
} catch {
return false;
}
}
/**
* Deployes a smart account via a dummy transaction. If the account is already deployed, this will do nothing.
*
@@ -47,7 +47,6 @@ const contract = getContract({
chain,
client,
});
const factoryAddress = "0x564cf6453a1b0FF8DB603E92EA4BbD410dea45F3"; // pre 712
describe.runIf(process.env.TW_SECRET_KEY).sequential(
"SmartWallet core tests",
@@ -301,79 +300,6 @@ describe.runIf(process.env.TW_SECRET_KEY).sequential(
expect(logs.some((l) => l.args.isAdmin)).toBe(true);
});
it("can use a different factory without replay protection", async () => {
const wallet = smartWallet({
chain,
factoryAddress: factoryAddress,
gasless: true,
});
// should not be able to switch chains before connecting
await expect(
wallet.switchChain(baseSepolia),
).rejects.toMatchInlineSnapshot(
"[Error: Cannot switch chain without a previous connection]",
);
const newAccount = await wallet.connect({ client, personalAccount });
const message = "hello world";
const signature = await newAccount.signMessage({ message });
const isValidV1 = await verifySignature({
address: newAccount.address,
chain,
client,
message,
signature,
});
expect(isValidV1).toEqual(true);
// sign typed data
const signatureTyped = await newAccount.signTypedData({
...typedData.basic,
primaryType: "Mail",
});
const isValidV2 = await verifyTypedData({
address: newAccount.address,
chain,
client,
signature: signatureTyped,
...typedData.basic,
});
expect(isValidV2).toEqual(true);
// add admin pre-deployment
const newAdmin = await generateAccount({ client });
const receipt = await sendAndConfirmTransaction({
account: newAccount,
transaction: addAdmin({
account: newAccount,
adminAddress: newAdmin.address,
contract: getContract({
address: newAccount.address,
chain,
client,
}),
}),
});
const logs = parseEventLogs({
events: [adminUpdatedEvent()],
logs: receipt.logs,
});
expect(logs.map((l) => l.args.signer)).toContain(newAdmin.address);
expect(logs.map((l) => l.args.isAdmin)).toContain(true);
// should not be able to switch chains since factory not deployed elsewhere
await expect(
wallet.switchChain(baseSepolia),
).rejects.toMatchInlineSnapshot(
"[Error: Factory contract not deployed on chain: 84532]",
);
// check can disconnnect
await wallet.disconnect();
expect(wallet.getAccount()).toBeUndefined();
});
it("can switch chains", async () => {
await wallet.switchChain(baseSepolia);
expect(wallet.getChain()?.id).toEqual(baseSepolia.id);
@@ -424,7 +350,7 @@ describe.runIf(process.env.TW_SECRET_KEY).sequential(
tokenId: 0n,
}),
}),
sleep(1000).then(() =>
sleep(1500).then(() =>
sendAndConfirmTransaction({
account: newSmartAccount,
transaction: claimTo({
@@ -453,7 +379,6 @@ describe.runIf(process.env.TW_SECRET_KEY).sequential(
it("can use a different paymaster", async () => {
const wallet = smartWallet({
chain,
factoryAddress: factoryAddress,
gasless: true,
overrides: {
paymaster: async () => {
+2
View File
@@ -1,5 +1,7 @@
# @thirdweb-dev/wagmi-adapter
## 0.2.117
## 0.2.116
## 0.2.115
+1 -1
View File
@@ -55,5 +55,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "0.2.116"
"version": "0.2.117"
}