signet.js 0.0.2-beta.6 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.eslintrc.json +55 -0
  2. package/.prettierrc +1 -0
  3. package/babel.config.js +6 -0
  4. package/docs/pages/index.mdx +36 -0
  5. package/docs/pages/signetjs/advanced/chain-signatures-contract.mdx +52 -0
  6. package/docs/pages/signetjs/advanced/chain.mdx +45 -0
  7. package/docs/pages/signetjs/chains/bitcoin/bitcoin.mdx +171 -0
  8. package/docs/pages/signetjs/chains/bitcoin/btc-rpc-adapter.mdx +26 -0
  9. package/docs/pages/signetjs/chains/cosmos.mdx +171 -0
  10. package/docs/pages/signetjs/chains/evm.mdx +319 -0
  11. package/docs/pages/signetjs/contract-addresses.mdx +27 -0
  12. package/docs/pages/signetjs/index.mdx +88 -0
  13. package/docs/snippets/code/contract.ts +21 -0
  14. package/docs/snippets/code/evm/env.ts +16 -0
  15. package/docs/snippets/code/near/env.ts +13 -0
  16. package/hardhat.config.mts +19 -0
  17. package/package.json +1 -1
  18. package/src/chains/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.ts +11 -0
  19. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/Mempool.ts +96 -0
  20. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/index.ts +1 -0
  21. package/src/chains/Bitcoin/BTCRpcAdapter/Mempool/types.ts +72 -0
  22. package/src/chains/Bitcoin/BTCRpcAdapter/index.ts +6 -0
  23. package/src/chains/Bitcoin/Bitcoin.ts +287 -0
  24. package/src/chains/Bitcoin/types.ts +48 -0
  25. package/src/chains/Bitcoin/utils.ts +14 -0
  26. package/src/chains/Chain.ts +92 -0
  27. package/src/chains/ChainSignatureContract.ts +65 -0
  28. package/src/chains/Cosmos/Cosmos.ts +258 -0
  29. package/src/chains/Cosmos/types.ts +35 -0
  30. package/src/chains/Cosmos/utils.ts +45 -0
  31. package/src/chains/EVM/EVM.test.ts +238 -0
  32. package/src/chains/EVM/EVM.ts +334 -0
  33. package/src/chains/EVM/types.ts +53 -0
  34. package/src/chains/EVM/utils.ts +27 -0
  35. package/src/chains/index.ts +38 -0
  36. package/src/chains/types.ts +46 -0
  37. package/src/index.ts +2 -0
  38. package/src/utils/chains/evm/ChainSignaturesContract.ts +286 -0
  39. package/src/utils/chains/evm/ChainSignaturesContractABI.ts +359 -0
  40. package/src/utils/chains/evm/errors.ts +52 -0
  41. package/src/utils/chains/evm/index.ts +3 -0
  42. package/src/utils/chains/evm/types.ts +28 -0
  43. package/src/utils/chains/evm/utils.ts +11 -0
  44. package/src/utils/chains/index.ts +2 -0
  45. package/src/utils/chains/near/ChainSignatureContract.ts +155 -0
  46. package/src/utils/chains/near/account.ts +42 -0
  47. package/src/utils/chains/near/constants.ts +4 -0
  48. package/src/utils/chains/near/index.ts +3 -0
  49. package/src/utils/chains/near/signAndSend/index.ts +1 -0
  50. package/src/utils/chains/near/signAndSend/keypair.ts +178 -0
  51. package/src/utils/chains/near/transactionBuilder.ts +73 -0
  52. package/src/utils/chains/near/types.ts +77 -0
  53. package/src/utils/constants.ts +62 -0
  54. package/src/utils/cryptography.ts +131 -0
  55. package/src/utils/index.ts +3 -0
  56. package/src/utils/publicKey.ts +23 -0
  57. package/tsconfig.eslint.json +8 -0
  58. package/tsconfig.json +122 -0
  59. package/tsup.config.ts +55 -0
  60. package/vitest.config.ts +16 -0
  61. package/vocs.config.ts +73 -0
  62. package/browser/index.browser.cjs +0 -3
  63. package/browser/index.browser.cjs.map +0 -1
  64. package/browser/index.browser.js +0 -3
  65. package/browser/index.browser.js.map +0 -1
  66. package/node/index.node.cjs +0 -3
  67. package/node/index.node.cjs.map +0 -1
  68. package/node/index.node.js +0 -3
  69. package/node/index.node.js.map +0 -1
  70. package/types/index.d.cts +0 -919
  71. package/types/index.d.ts +0 -919
@@ -0,0 +1,258 @@
1
+ import { encodeSecp256k1Pubkey } from '@cosmjs/amino'
2
+ import { ripemd160, sha256 } from '@cosmjs/crypto'
3
+ import { toBase64, fromBase64, fromHex } from '@cosmjs/encoding'
4
+ import {
5
+ Registry,
6
+ makeSignBytes,
7
+ encodePubkey,
8
+ makeAuthInfoBytes,
9
+ makeSignDoc,
10
+ type TxBodyEncodeObject,
11
+ } from '@cosmjs/proto-signing'
12
+ import { GasPrice, StargateClient, calculateFee } from '@cosmjs/stargate'
13
+ import { bech32 } from 'bech32'
14
+ import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing'
15
+ import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
16
+
17
+ import { Chain } from '@chains/Chain'
18
+ import type { BaseChainSignatureContract } from '@chains/ChainSignatureContract'
19
+ import type {
20
+ CosmosNetworkIds,
21
+ CosmosTransactionRequest,
22
+ CosmosUnsignedTransaction,
23
+ ChainInfo,
24
+ BalanceResponse,
25
+ } from '@chains/Cosmos/types'
26
+ import { fetchChainInfo } from '@chains/Cosmos/utils'
27
+ import type { HashToSign, RSVSignature, KeyDerivationPath } from '@chains/types'
28
+ import { cryptography } from '@utils'
29
+
30
+ /**
31
+ * Implementation of the Chain interface for Cosmos-based networks.
32
+ * Handles interactions with Cosmos SDK chains like Cosmos Hub, Osmosis, etc.
33
+ */
34
+ export class Cosmos extends Chain<
35
+ CosmosTransactionRequest,
36
+ CosmosUnsignedTransaction
37
+ > {
38
+ private readonly registry: Registry
39
+ private readonly chainId: CosmosNetworkIds
40
+ private readonly contract: BaseChainSignatureContract
41
+ private readonly endpoints?: {
42
+ rpcUrl?: string
43
+ restUrl?: string
44
+ }
45
+
46
+ /**
47
+ * Creates a new Cosmos chain instance
48
+ * @param params - Configuration parameters
49
+ * @param params.chainId - Chain id for the Cosmos network
50
+ * @param params.contract - Instance of the chain signature contract for MPC operations
51
+ * @param params.endpoints - Optional RPC and REST endpoints
52
+ * @param params.endpoints.rpcUrl - Optional RPC endpoint URL
53
+ * @param params.endpoints.restUrl - Optional REST endpoint URL
54
+ */
55
+ constructor({
56
+ chainId,
57
+ contract,
58
+ endpoints,
59
+ }: {
60
+ contract: BaseChainSignatureContract
61
+ chainId: CosmosNetworkIds
62
+ endpoints?: {
63
+ rpcUrl?: string
64
+ restUrl?: string
65
+ }
66
+ }) {
67
+ super()
68
+
69
+ this.contract = contract
70
+ this.registry = new Registry()
71
+ this.chainId = chainId
72
+ this.endpoints = endpoints
73
+ }
74
+
75
+ private transformRSVSignature(rsvSignature: RSVSignature): Uint8Array {
76
+ return new Uint8Array([
77
+ ...fromHex(rsvSignature.r),
78
+ ...fromHex(rsvSignature.s),
79
+ ])
80
+ }
81
+
82
+ private async getChainInfo(): Promise<ChainInfo> {
83
+ return {
84
+ ...(await fetchChainInfo(this.chainId)),
85
+ ...this.endpoints,
86
+ }
87
+ }
88
+
89
+ async getBalance(
90
+ address: string
91
+ ): Promise<{ balance: bigint; decimals: number }> {
92
+ try {
93
+ const { restUrl, denom, decimals } = await this.getChainInfo()
94
+
95
+ const response = await fetch(
96
+ `${restUrl}/cosmos/bank/v1beta1/balances/${address}`
97
+ )
98
+
99
+ if (!response.ok) {
100
+ throw new Error(`HTTP error! status: ${response.status}`)
101
+ }
102
+
103
+ const data = (await response.json()) as BalanceResponse
104
+ const balance = data.balances.find((b) => b.denom === denom)
105
+ const amount = balance?.amount ?? '0'
106
+
107
+ return {
108
+ balance: BigInt(amount),
109
+ decimals,
110
+ }
111
+ } catch (error) {
112
+ console.error('Failed to fetch Cosmos balance:', error)
113
+ throw new Error('Failed to fetch Cosmos balance')
114
+ }
115
+ }
116
+
117
+ async deriveAddressAndPublicKey(
118
+ predecessor: string,
119
+ path: KeyDerivationPath
120
+ ): Promise<{
121
+ address: string
122
+ publicKey: string
123
+ }> {
124
+ const { prefix } = await this.getChainInfo()
125
+ const uncompressedPubKey = await this.contract.getDerivedPublicKey({
126
+ path,
127
+ predecessor,
128
+ })
129
+
130
+ if (!uncompressedPubKey) {
131
+ throw new Error('Failed to get derived public key')
132
+ }
133
+
134
+ const derivedKey = cryptography.compressPubKey(uncompressedPubKey)
135
+ const pubKeySha256 = sha256(fromHex(derivedKey))
136
+ const ripemd160Hash = ripemd160(pubKeySha256)
137
+ const address = bech32.encode(prefix, bech32.toWords(ripemd160Hash))
138
+
139
+ return { address, publicKey: derivedKey }
140
+ }
141
+
142
+ serializeTransaction(transaction: CosmosUnsignedTransaction): string {
143
+ const serialized = TxRaw.encode(transaction).finish()
144
+ return toBase64(serialized)
145
+ }
146
+
147
+ deserializeTransaction(serialized: string): CosmosUnsignedTransaction {
148
+ return TxRaw.decode(fromBase64(serialized))
149
+ }
150
+
151
+ async prepareTransactionForSigning(
152
+ transactionRequest: CosmosTransactionRequest
153
+ ): Promise<{
154
+ transaction: CosmosUnsignedTransaction
155
+ hashesToSign: HashToSign[]
156
+ }> {
157
+ const { denom, rpcUrl, gasPrice } = await this.getChainInfo()
158
+ const publicKeyBytes = fromHex(transactionRequest.publicKey)
159
+
160
+ const gasLimit = transactionRequest.gas || 200_000
161
+
162
+ const fee = calculateFee(
163
+ gasLimit,
164
+ GasPrice.fromString(`${gasPrice}${denom}`)
165
+ )
166
+
167
+ const client = await StargateClient.connect(rpcUrl)
168
+ const accountOnChain = await client.getAccount(transactionRequest.address)
169
+ if (!accountOnChain) {
170
+ throw new Error(
171
+ `Account ${transactionRequest.address} does not exist on chain`
172
+ )
173
+ }
174
+
175
+ const { accountNumber, sequence } = accountOnChain
176
+
177
+ const txBodyEncodeObject: TxBodyEncodeObject = {
178
+ typeUrl: '/cosmos.tx.v1beta1.TxBody',
179
+ value: {
180
+ messages: transactionRequest.messages,
181
+ memo: transactionRequest.memo || '',
182
+ },
183
+ }
184
+
185
+ const txBodyBytes = this.registry.encode(txBodyEncodeObject)
186
+
187
+ const pubkey = encodePubkey(encodeSecp256k1Pubkey(publicKeyBytes))
188
+
189
+ // TODO: Allow caller to provide: multiple signers, fee payer, fee granter
190
+ const authInfoBytes = makeAuthInfoBytes(
191
+ [
192
+ {
193
+ pubkey,
194
+ sequence,
195
+ },
196
+ ],
197
+ fee.amount,
198
+ Number(fee.gas),
199
+ undefined,
200
+ undefined,
201
+ SignMode.SIGN_MODE_DIRECT
202
+ )
203
+
204
+ const signDoc = makeSignDoc(
205
+ txBodyBytes,
206
+ authInfoBytes,
207
+ this.chainId,
208
+ accountNumber
209
+ )
210
+
211
+ const signBytes = makeSignBytes(signDoc)
212
+ const payload = Array.from(sha256(signBytes))
213
+
214
+ return {
215
+ transaction: TxRaw.fromPartial({
216
+ bodyBytes: txBodyBytes,
217
+ authInfoBytes,
218
+ signatures: [],
219
+ }),
220
+ hashesToSign: [payload],
221
+ }
222
+ }
223
+
224
+ attachTransactionSignature({
225
+ transaction,
226
+ rsvSignatures,
227
+ }: {
228
+ transaction: CosmosUnsignedTransaction
229
+ rsvSignatures: RSVSignature[]
230
+ }): string {
231
+ // Allow support for multi-sig but the package only supports single-sig
232
+ transaction.signatures = rsvSignatures.map((sig) =>
233
+ this.transformRSVSignature(sig)
234
+ )
235
+
236
+ const txBytes = TxRaw.encode(transaction).finish()
237
+ return Buffer.from(txBytes).toString('hex')
238
+ }
239
+
240
+ async broadcastTx(txSerialized: string): Promise<string> {
241
+ try {
242
+ const { rpcUrl } = await this.getChainInfo()
243
+ const client = await StargateClient.connect(rpcUrl)
244
+
245
+ const txBytes = fromHex(txSerialized)
246
+ const broadcastResponse = await client.broadcastTx(txBytes)
247
+
248
+ if (broadcastResponse.code !== 0) {
249
+ throw new Error(`Broadcast error: ${broadcastResponse.rawLog}`)
250
+ }
251
+
252
+ return broadcastResponse.transactionHash
253
+ } catch (error) {
254
+ console.error('Transaction broadcast failed:', error)
255
+ throw new Error('Failed to broadcast transaction.')
256
+ }
257
+ }
258
+ }
@@ -0,0 +1,35 @@
1
+ import { type EncodeObject } from '@cosmjs/proto-signing'
2
+ import { type TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
3
+
4
+ export type CosmosNetworkIds = string
5
+
6
+ export type CosmosUnsignedTransaction = TxRaw
7
+
8
+ export interface CosmosTransactionRequest {
9
+ address: string
10
+ publicKey: string
11
+ messages: EncodeObject[]
12
+ memo?: string
13
+ gas?: number
14
+ }
15
+
16
+ export interface BalanceResponse {
17
+ balances: Array<{
18
+ denom: string
19
+ amount: string
20
+ }>
21
+ pagination: {
22
+ next_key: string | null
23
+ total: string
24
+ }
25
+ }
26
+
27
+ export interface ChainInfo {
28
+ prefix: string
29
+ denom: string
30
+ rpcUrl: string
31
+ restUrl: string
32
+ expectedChainId: string
33
+ gasPrice: number
34
+ decimals: number
35
+ }
@@ -0,0 +1,45 @@
1
+ import { chains, assets } from 'chain-registry'
2
+
3
+ import { type ChainInfo } from '@chains/Cosmos/types'
4
+
5
+ export const fetchChainInfo = async (chainId: string): Promise<ChainInfo> => {
6
+ const chainInfo = chains.find((chain) => chain.chain_id === chainId)
7
+ if (!chainInfo) {
8
+ throw new Error(`Chain info not found for chainId: ${chainId}`)
9
+ }
10
+
11
+ const { bech32_prefix: prefix, chain_id: expectedChainId } = chainInfo
12
+ const denom = chainInfo.staking?.staking_tokens?.[0]?.denom
13
+ const rpcUrl = chainInfo.apis?.rpc?.[0]?.address
14
+ const restUrl = chainInfo.apis?.rest?.[0]?.address
15
+ const gasPrice = chainInfo.fees?.fee_tokens?.[0]?.average_gas_price
16
+
17
+ if (
18
+ !prefix ||
19
+ !denom ||
20
+ !rpcUrl ||
21
+ !restUrl ||
22
+ !expectedChainId ||
23
+ gasPrice === undefined
24
+ ) {
25
+ throw new Error(
26
+ `Missing required chain information for ${chainInfo.chain_name}`
27
+ )
28
+ }
29
+
30
+ const assetList = assets.find(
31
+ (asset) => asset.chain_name === chainInfo.chain_name
32
+ )
33
+ const asset = assetList?.assets.find((asset) => asset.base === denom)
34
+ const decimals = asset?.denom_units.find(
35
+ (unit) => unit.denom === asset.display
36
+ )?.exponent
37
+
38
+ if (decimals === undefined) {
39
+ throw new Error(
40
+ `Could not find decimals for ${denom} on chain ${chainInfo.chain_name}`
41
+ )
42
+ }
43
+
44
+ return { prefix, denom, rpcUrl, restUrl, expectedChainId, gasPrice, decimals }
45
+ }
@@ -0,0 +1,238 @@
1
+ import { LocalAccountSigner } from '@aa-sdk/core'
2
+ import { alchemy, sepolia as alchemySepolia } from '@account-kit/infra'
3
+ import { createLightAccountAlchemyClient } from '@account-kit/smart-contracts'
4
+ import { secp256k1 } from '@noble/curves/secp256k1'
5
+ import BN from 'bn.js'
6
+ import {
7
+ createPublicClient,
8
+ createWalletClient,
9
+ http,
10
+ parseEther,
11
+ recoverMessageAddress,
12
+ recoverTypedDataAddress,
13
+ } from 'viem'
14
+ import { privateKeyToAccount } from 'viem/accounts'
15
+ import { hardhat } from 'viem/chains'
16
+ import { describe, expect, it } from 'vitest'
17
+
18
+ import type { ChainSignatureContract } from '../ChainSignatureContract'
19
+ import type { UncompressedPubKeySEC1 } from '../types'
20
+
21
+ import { EVM } from './EVM'
22
+
23
+ describe('EVM', async () => {
24
+ const privateKey =
25
+ '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
26
+ const testAccount = privateKeyToAccount(privateKey)
27
+ const rpcUrl = 'http://127.0.0.1:8545'
28
+
29
+ const publicClient = createPublicClient({
30
+ chain: hardhat,
31
+ transport: http(rpcUrl),
32
+ })
33
+
34
+ const walletClient = createWalletClient({
35
+ account: testAccount,
36
+ chain: hardhat,
37
+ transport: http(rpcUrl),
38
+ })
39
+
40
+ const contract: ChainSignatureContract = {
41
+ sign: async ({ payload }) => {
42
+ const messageBytes = new Uint8Array(payload)
43
+ const privKeyBytes = new Uint8Array(
44
+ Buffer.from(privateKey.slice(2), 'hex')
45
+ )
46
+ const { r, s, recovery } = secp256k1.sign(messageBytes, privKeyBytes)
47
+ return {
48
+ r: r.toString(16).padStart(64, '0'),
49
+ s: s.toString(16).padStart(64, '0'),
50
+ v: recovery + 27,
51
+ }
52
+ },
53
+ getDerivedPublicKey: async () => {
54
+ return '04' as UncompressedPubKeySEC1
55
+ },
56
+ getPublicKey: async () => {
57
+ const pubKey = secp256k1.getPublicKey(
58
+ Buffer.from(privateKey.slice(2), 'hex')
59
+ )
60
+ return ('04' +
61
+ Buffer.from(pubKey.slice(1)).toString('hex')) as UncompressedPubKeySEC1
62
+ },
63
+ getCurrentSignatureDeposit: async () => new BN(0),
64
+ }
65
+
66
+ const evm = new EVM({
67
+ contract,
68
+ rpcUrl,
69
+ })
70
+
71
+ it('should sign a message', async () => {
72
+ const message = 'Hello, World!'
73
+ const { hashesToSign } = await evm.prepareMessageForSigning(message)
74
+
75
+ const mpcSignature = await contract.sign({
76
+ payload: hashesToSign[0],
77
+ path: '',
78
+ key_version: 0,
79
+ })
80
+
81
+ const signature = evm.assembleMessageSignature({
82
+ rsvSignatures: [mpcSignature],
83
+ })
84
+
85
+ const walletSignature = await walletClient.signMessage({
86
+ message,
87
+ })
88
+
89
+ const recoveredAddress = await recoverMessageAddress({
90
+ message,
91
+ signature: walletSignature,
92
+ })
93
+
94
+ expect(recoveredAddress).toBe(testAccount.address)
95
+ expect(signature).toBe(walletSignature)
96
+ })
97
+
98
+ it('should sign typed data', async () => {
99
+ const typedData = {
100
+ domain: {
101
+ name: 'Test',
102
+ version: '1',
103
+ chainId: hardhat.id,
104
+ verifyingContract:
105
+ '0x1234567890123456789012345678901234567890' as `0x${string}`,
106
+ },
107
+ types: {
108
+ Person: [
109
+ { name: 'name', type: 'string' },
110
+ { name: 'wallet', type: 'address' },
111
+ ],
112
+ },
113
+ primaryType: 'Person' as const,
114
+ message: {
115
+ name: 'Bob',
116
+ wallet: '0x1234567890123456789012345678901234567890' as `0x${string}`,
117
+ },
118
+ }
119
+
120
+ const { hashesToSign } = await evm.prepareTypedDataForSigning(typedData)
121
+
122
+ const mpcSignature = await contract.sign({
123
+ payload: hashesToSign[0],
124
+ path: '',
125
+ key_version: 0,
126
+ })
127
+
128
+ const signature = evm.assembleTypedDataSignature({
129
+ rsvSignatures: [mpcSignature],
130
+ })
131
+
132
+ const walletSignature = await walletClient.signTypedData(typedData)
133
+
134
+ const recoveredAddress = await recoverTypedDataAddress({
135
+ ...typedData,
136
+ signature: walletSignature,
137
+ })
138
+
139
+ expect(recoveredAddress).toBe(testAccount.address)
140
+ expect(signature).toBe(walletSignature)
141
+ })
142
+
143
+ it('should sign a transaction', async () => {
144
+ await publicClient.request({
145
+ // @ts-expect-error: hardhat_setBalance is valid as we are using a hardhat client
146
+ method: 'hardhat_setBalance',
147
+ params: [testAccount.address, '0x4563918244f400000000'], // 5 ETH
148
+ })
149
+
150
+ const transactionInput = {
151
+ from: testAccount.address,
152
+ to: '0x1234567890123456789012345678901234567890' as `0x${string}`,
153
+ value: parseEther('1'),
154
+ maxFeePerGas: parseEther('0.001'),
155
+ maxPriorityFeePerGas: parseEther('0.0001'),
156
+ gas: 21000n,
157
+ nonce: await publicClient.getTransactionCount({
158
+ address: testAccount.address,
159
+ }),
160
+ type: 'eip1559' as const,
161
+ chainId: hardhat.id,
162
+ accessList: [],
163
+ }
164
+
165
+ const { hashesToSign, transaction } =
166
+ await evm.prepareTransactionForSigning(transactionInput)
167
+
168
+ const mpcSignature = await contract.sign({
169
+ payload: hashesToSign[0],
170
+ path: '',
171
+ key_version: 0,
172
+ })
173
+
174
+ const tx = evm.attachTransactionSignature({
175
+ transaction,
176
+ rsvSignatures: [mpcSignature],
177
+ })
178
+
179
+ const walletSignature = await walletClient.signTransaction(transactionInput)
180
+
181
+ expect(tx).toBe(walletSignature)
182
+
183
+ const txHash = await evm.broadcastTx(tx)
184
+
185
+ const txReceipt = await publicClient.getTransactionReceipt({
186
+ hash: txHash,
187
+ })
188
+
189
+ expect(txReceipt.status).toBe('success')
190
+ })
191
+
192
+ it('should sign a user operation', async () => {
193
+ const lightAccountClient = await createLightAccountAlchemyClient({
194
+ transport: alchemy({ apiKey: 'er9VowLvLw2YQbgTaRLudG81JPxs77rT' }),
195
+ chain: alchemySepolia,
196
+ signer: LocalAccountSigner.privateKeyToAccountSigner(privateKey),
197
+ })
198
+
199
+ const userOp = {
200
+ sender: testAccount.address,
201
+ nonce: '0x0' as `0x${string}`,
202
+ initCode: '0x' as `0x${string}`,
203
+ callData: '0x' as `0x${string}`,
204
+ callGasLimit: '0x5208' as `0x${string}`,
205
+ verificationGasLimit: '0x5208' as `0x${string}`,
206
+ preVerificationGas: '0x5208' as `0x${string}`,
207
+ maxFeePerGas: '0x38d7ea4c68000' as `0x${string}`,
208
+ maxPriorityFeePerGas: '0x5af3107a4000' as `0x${string}`,
209
+ paymasterAndData: '0x' as `0x${string}`,
210
+ signature: '0x' as `0x${string}`,
211
+ }
212
+
213
+ const { hashesToSign } = await evm.prepareUserOpForSigning(
214
+ userOp,
215
+ '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
216
+ 11155111
217
+ )
218
+
219
+ const mpcSignature = await contract.sign({
220
+ payload: hashesToSign[0],
221
+ path: '',
222
+ key_version: 0,
223
+ })
224
+
225
+ const signedUserOp = evm.attachUserOpSignature({
226
+ userOp,
227
+ rsvSignatures: [mpcSignature],
228
+ })
229
+
230
+ const walletSignature = await lightAccountClient.signUserOperation({
231
+ uoStruct: userOp,
232
+ })
233
+
234
+ expect(signedUserOp.signature).toBe(walletSignature.signature)
235
+ })
236
+
237
+ // TODO: Include test for v7 user operations.
238
+ })