signet.js 0.0.5 → 0.0.7

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 (183) hide show
  1. package/.eslintrc.json +67 -0
  2. package/.prettierrc +1 -0
  3. package/README.md +3 -3
  4. package/babel.config.js +6 -0
  5. package/docs/dist/.vocs/icons/arrow-diagonal.svg +3 -0
  6. package/docs/dist/.vocs/icons/chevron-down.svg +13 -0
  7. package/docs/dist/.vocs/icons/chevron-up.svg +13 -0
  8. package/docs/dist/.vocs/icons/link.svg +3 -0
  9. package/docs/dist/.vocs/search-index-7b499e25.json +1 -0
  10. package/docs/dist/assets/arbitrary-hash-Cd6eo8ZD.js +309 -0
  11. package/docs/dist/assets/broadcast-tx-CeTEE9yX.js +8 -0
  12. package/docs/dist/assets/btc-rpc-adapter-C-qSHpFV.js +226 -0
  13. package/docs/dist/assets/chain-adapter-interface-B9TpOgQv.js +1280 -0
  14. package/docs/dist/assets/chain-contract-interface-DEku3k45.js +392 -0
  15. package/docs/dist/assets/constructor-73n7bp3b.js +161 -0
  16. package/docs/dist/assets/constructor-Bg7nvLe0.js +14 -0
  17. package/docs/dist/assets/contract-addresses-BYlrAOs3.js +200 -0
  18. package/docs/dist/assets/derive-address-and-public-key-DExrKiGV.js +14 -0
  19. package/docs/dist/assets/finalize-message-signing-W435d71R.js +20 -0
  20. package/docs/dist/assets/finalize-transaction-signing-BIgJ2dnc.js +36 -0
  21. package/docs/dist/assets/finalize-transaction-signing-C--HJs8D.js +24 -0
  22. package/docs/dist/assets/finalize-transaction-signing-CjGmN7d9.js +24 -0
  23. package/docs/dist/assets/finalize-typed-data-signing-CEOp_GWt.js +21 -0
  24. package/docs/dist/assets/get-balance-DBC-i6KG.js +13 -0
  25. package/docs/dist/assets/get-current-signature-deposit-BXm9AzYy.js +6 -0
  26. package/docs/dist/assets/get-current-signature-deposit-nOw4j1MN.js +6 -0
  27. package/docs/dist/assets/get-derived-public-key-BXvfo2m2.js +14 -0
  28. package/docs/dist/assets/get-derived-public-key-DQ1pyiFS.js +14 -0
  29. package/docs/dist/assets/get-latest-key-version-DWlkMCre.js +6 -0
  30. package/docs/dist/assets/get-public-key-B4PFoVqu.js +6 -0
  31. package/docs/dist/assets/get-public-key-B9xkYkD_.js +6 -0
  32. package/docs/dist/assets/index-BFuwoY4w.js +601 -0
  33. package/docs/dist/assets/index-C62Mf-vy.js +426 -0
  34. package/docs/dist/assets/index-D8xhaiVb.js +121 -0
  35. package/docs/dist/assets/index-DTr0DlO0.js +36 -0
  36. package/docs/dist/assets/index-V9dXf-ik.js +457 -0
  37. package/docs/dist/assets/prepare-message-for-signing-DESTq-Hg.js +16 -0
  38. package/docs/dist/assets/prepare-transaction-for-signing-DIKTU0zj.js +33 -0
  39. package/docs/dist/assets/prepare-transaction-for-signing-DV_wkZ5g.js +21 -0
  40. package/docs/dist/assets/prepare-transaction-for-signing-LVDP0COu.js +33 -0
  41. package/docs/dist/assets/prepare-typed-data-for-signing-CWcmJvw0.js +192 -0
  42. package/docs/dist/assets/sign-CwtS5LnB.js +13 -0
  43. package/docs/dist/assets/sign-OQxf9yn7.js +15 -0
  44. package/docs/dist/assets/signet-quick-start-CQK52nVG.js +350 -0
  45. package/docs/dist/assets/sponsor-foreign-chain-gas-C9iWXM8Q.js +1 -0
  46. package/docs/dist/assets/style-CKGXuRqx.css +1 -0
  47. package/docs/dist/examples/arbitrary-hash/index.html +88 -0
  48. package/docs/dist/examples/sponsor-foreign-chain-gas/index.html +21 -0
  49. package/docs/dist/index.html +56 -0
  50. package/docs/dist/initializeTheme.iife.js +1 -0
  51. package/docs/dist/introduction/signet-quick-start/index.html +109 -0
  52. package/docs/dist/primitives/chain-adapter-interface/index.html +515 -0
  53. package/docs/dist/primitives/chain-contract-interface/index.html +306 -0
  54. package/docs/dist/primitives/contract-addresses/index.html +97 -0
  55. package/docs/dist/signet-logo.png +0 -0
  56. package/docs/dist/signetjs/chain-adapters/bitcoin/btc-rpc-adapter/index.html +148 -0
  57. package/docs/dist/signetjs/chain-adapters/bitcoin/finalize-transaction-signing/index.html +41 -0
  58. package/docs/dist/signetjs/chain-adapters/bitcoin/index.html +187 -0
  59. package/docs/dist/signetjs/chain-adapters/bitcoin/prepare-transaction-for-signing/index.html +34 -0
  60. package/docs/dist/signetjs/chain-adapters/broadcast-tx/index.html +28 -0
  61. package/docs/dist/signetjs/chain-adapters/cosmos/finalize-transaction-signing/index.html +41 -0
  62. package/docs/dist/signetjs/chain-adapters/cosmos/index.html +166 -0
  63. package/docs/dist/signetjs/chain-adapters/cosmos/prepare-transaction-for-signing/index.html +43 -0
  64. package/docs/dist/signetjs/chain-adapters/derive-address-and-public-key/index.html +31 -0
  65. package/docs/dist/signetjs/chain-adapters/evm/finalize-message-signing/index.html +38 -0
  66. package/docs/dist/signetjs/chain-adapters/evm/finalize-transaction-signing/index.html +41 -0
  67. package/docs/dist/signetjs/chain-adapters/evm/finalize-typed-data-signing/index.html +39 -0
  68. package/docs/dist/signetjs/chain-adapters/evm/index.html +129 -0
  69. package/docs/dist/signetjs/chain-adapters/evm/prepare-message-for-signing/index.html +31 -0
  70. package/docs/dist/signetjs/chain-adapters/evm/prepare-transaction-for-signing/index.html +34 -0
  71. package/docs/dist/signetjs/chain-adapters/evm/prepare-typed-data-for-signing/index.html +49 -0
  72. package/docs/dist/signetjs/chain-adapters/get-balance/index.html +30 -0
  73. package/docs/dist/signetjs/contracts/evm/constructor/index.html +45 -0
  74. package/docs/dist/signetjs/contracts/evm/get-current-signature-deposit/index.html +26 -0
  75. package/docs/dist/signetjs/contracts/evm/get-derived-public-key/index.html +31 -0
  76. package/docs/dist/signetjs/contracts/evm/get-latest-key-version/index.html +26 -0
  77. package/docs/dist/signetjs/contracts/evm/get-public-key/index.html +26 -0
  78. package/docs/dist/signetjs/contracts/evm/sign/index.html +32 -0
  79. package/docs/dist/signetjs/contracts/near/constructor/index.html +34 -0
  80. package/docs/dist/signetjs/contracts/near/get-current-signature-deposit/index.html +26 -0
  81. package/docs/dist/signetjs/contracts/near/get-derived-public-key/index.html +31 -0
  82. package/docs/dist/signetjs/contracts/near/get-public-key/index.html +26 -0
  83. package/docs/dist/signetjs/contracts/near/sign/index.html +32 -0
  84. package/docs/pages/examples/arbitrary-hash.mdx +73 -0
  85. package/docs/pages/examples/sponsor-foreign-chain-gas.mdx +1 -0
  86. package/docs/pages/index.mdx +36 -0
  87. package/docs/pages/introduction/signet-quick-start.mdx +88 -0
  88. package/docs/pages/primitives/chain-adapter-interface.mdx +45 -0
  89. package/docs/pages/primitives/chain-contract-interface.mdx +52 -0
  90. package/docs/pages/primitives/contract-addresses.mdx +27 -0
  91. package/docs/pages/signetjs/chain-adapters/bitcoin/btc-rpc-adapter.mdx +26 -0
  92. package/docs/pages/signetjs/chain-adapters/bitcoin/finalize-transaction-signing.mdx +47 -0
  93. package/docs/pages/signetjs/chain-adapters/bitcoin/index.mdx +119 -0
  94. package/docs/pages/signetjs/chain-adapters/bitcoin/prepare-transaction-for-signing.mdx +30 -0
  95. package/docs/pages/signetjs/chain-adapters/broadcast-tx.mdx +23 -0
  96. package/docs/pages/signetjs/chain-adapters/cosmos/finalize-transaction-signing.mdx +53 -0
  97. package/docs/pages/signetjs/chain-adapters/cosmos/index.mdx +108 -0
  98. package/docs/pages/signetjs/chain-adapters/cosmos/prepare-transaction-for-signing.mdx +39 -0
  99. package/docs/pages/signetjs/chain-adapters/derive-address-and-public-key.mdx +28 -0
  100. package/docs/pages/signetjs/chain-adapters/evm/finalize-message-signing.mdx +33 -0
  101. package/docs/pages/signetjs/chain-adapters/evm/finalize-transaction-signing.mdx +44 -0
  102. package/docs/pages/signetjs/chain-adapters/evm/finalize-typed-data-signing.mdx +34 -0
  103. package/docs/pages/signetjs/chain-adapters/evm/index.mdx +84 -0
  104. package/docs/pages/signetjs/chain-adapters/evm/prepare-message-for-signing.mdx +26 -0
  105. package/docs/pages/signetjs/chain-adapters/evm/prepare-transaction-for-signing.mdx +30 -0
  106. package/docs/pages/signetjs/chain-adapters/evm/prepare-typed-data-for-signing.mdx +44 -0
  107. package/docs/pages/signetjs/chain-adapters/get-balance.mdx +26 -0
  108. package/docs/pages/signetjs/contracts/evm/constructor.mdx +38 -0
  109. package/docs/pages/signetjs/contracts/evm/get-current-signature-deposit.mdx +17 -0
  110. package/docs/pages/signetjs/contracts/evm/get-derived-public-key.mdx +28 -0
  111. package/docs/pages/signetjs/contracts/evm/get-latest-key-version.mdx +17 -0
  112. package/docs/pages/signetjs/contracts/evm/get-public-key.mdx +17 -0
  113. package/docs/pages/signetjs/contracts/evm/sign.mdx +36 -0
  114. package/docs/pages/signetjs/contracts/near/constructor.mdx +29 -0
  115. package/docs/pages/signetjs/contracts/near/get-current-signature-deposit.mdx +17 -0
  116. package/docs/pages/signetjs/contracts/near/get-derived-public-key.mdx +28 -0
  117. package/docs/pages/signetjs/contracts/near/get-public-key.mdx +17 -0
  118. package/docs/pages/signetjs/contracts/near/sign.mdx +32 -0
  119. package/docs/public/signet-logo.png +0 -0
  120. package/docs/snippets/code/chains.ts +42 -0
  121. package/docs/snippets/code/contract.ts +44 -0
  122. package/docs/snippets/code/evm/contract.ts +24 -0
  123. package/docs/snippets/code/evm/env.ts +16 -0
  124. package/docs/snippets/code/near/env.ts +13 -0
  125. package/hardhat.config.mts +19 -0
  126. package/package.json +38 -22
  127. package/src/chain-adapters/Bitcoin/BTCRpcAdapter/BTCRpcAdapter.ts +15 -0
  128. package/src/chain-adapters/Bitcoin/BTCRpcAdapter/Mempool/Mempool.ts +101 -0
  129. package/src/chain-adapters/Bitcoin/BTCRpcAdapter/Mempool/index.ts +1 -0
  130. package/src/chain-adapters/Bitcoin/BTCRpcAdapter/Mempool/types.ts +72 -0
  131. package/src/chain-adapters/Bitcoin/BTCRpcAdapter/index.ts +6 -0
  132. package/src/chain-adapters/Bitcoin/Bitcoin.ts +287 -0
  133. package/src/chain-adapters/Bitcoin/index.ts +13 -0
  134. package/src/chain-adapters/Bitcoin/types.ts +48 -0
  135. package/src/chain-adapters/Bitcoin/utils.ts +14 -0
  136. package/src/chain-adapters/ChainAdapter.ts +92 -0
  137. package/src/chain-adapters/Cosmos/Cosmos.ts +258 -0
  138. package/src/chain-adapters/Cosmos/index.ts +8 -0
  139. package/src/chain-adapters/Cosmos/types.ts +35 -0
  140. package/src/chain-adapters/Cosmos/utils.ts +45 -0
  141. package/src/chain-adapters/EVM/EVM.test.ts +238 -0
  142. package/src/chain-adapters/EVM/EVM.ts +337 -0
  143. package/src/chain-adapters/EVM/index.ts +11 -0
  144. package/src/chain-adapters/EVM/types.ts +53 -0
  145. package/src/chain-adapters/EVM/utils.ts +27 -0
  146. package/src/chain-adapters/index.ts +5 -0
  147. package/src/constants.ts +62 -0
  148. package/src/contracts/ChainSignatureContract.ts +65 -0
  149. package/src/contracts/evm/ChainSignaturesContract.ts +323 -0
  150. package/src/contracts/evm/ChainSignaturesContractABI.ts +359 -0
  151. package/src/contracts/evm/errors.ts +52 -0
  152. package/src/contracts/evm/index.ts +10 -0
  153. package/src/contracts/evm/types.ts +39 -0
  154. package/src/contracts/evm/utils.ts +41 -0
  155. package/src/contracts/index.ts +4 -0
  156. package/src/contracts/near/ChainSignatureContract.ts +196 -0
  157. package/src/contracts/near/account.ts +42 -0
  158. package/src/contracts/near/constants.ts +4 -0
  159. package/src/contracts/near/index.ts +10 -0
  160. package/src/contracts/near/signAndSend/index.ts +1 -0
  161. package/src/contracts/near/signAndSend/keypair.ts +178 -0
  162. package/src/contracts/near/transaction.ts +202 -0
  163. package/src/contracts/near/types.ts +71 -0
  164. package/src/index.ts +5 -0
  165. package/src/types.ts +46 -0
  166. package/src/utils/cryptography.ts +141 -0
  167. package/src/utils/index.ts +1 -0
  168. package/src/utils/publicKey.ts +17 -0
  169. package/tsconfig.eslint.json +8 -0
  170. package/tsconfig.json +126 -0
  171. package/tsup.config.ts +58 -0
  172. package/vitest.config.ts +19 -0
  173. package/vocs.config.ts +213 -0
  174. package/browser/index.browser.cjs +0 -3
  175. package/browser/index.browser.cjs.map +0 -1
  176. package/browser/index.browser.js +0 -3
  177. package/browser/index.browser.js.map +0 -1
  178. package/node/index.node.cjs +0 -3
  179. package/node/index.node.cjs.map +0 -1
  180. package/node/index.node.js +0 -3
  181. package/node/index.node.js.map +0 -1
  182. package/types/index.d.cts +0 -919
  183. package/types/index.d.ts +0 -919
@@ -0,0 +1,8 @@
1
+ // Cosmos
2
+ export { Cosmos } from './Cosmos'
3
+
4
+ export type {
5
+ CosmosNetworkIds,
6
+ CosmosTransactionRequest,
7
+ CosmosUnsignedTransaction,
8
+ } from './types'
@@ -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 '@chain-adapters/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 '../../contracts/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 { hashToSign } = await evm.prepareMessageForSigning(message)
74
+
75
+ const mpcSignature = await contract.sign({
76
+ payload: hashToSign,
77
+ path: '',
78
+ key_version: 0,
79
+ })
80
+
81
+ const signature = evm.finalizeMessageSigning({
82
+ rsvSignature: 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 { hashToSign } = await evm.prepareTypedDataForSigning(typedData)
121
+
122
+ const mpcSignature = await contract.sign({
123
+ payload: hashToSign,
124
+ path: '',
125
+ key_version: 0,
126
+ })
127
+
128
+ const signature = evm.finalizeTypedDataSigning({
129
+ rsvSignature: 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.finalizeTransactionSigning({
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 { hashToSign } = await evm.prepareUserOpForSigning(
214
+ userOp,
215
+ '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
216
+ 11155111
217
+ )
218
+
219
+ const mpcSignature = await contract.sign({
220
+ payload: hashToSign,
221
+ path: '',
222
+ key_version: 0,
223
+ })
224
+
225
+ const signedUserOp = evm.finalizeUserOpSigning({
226
+ userOp,
227
+ rsvSignature: 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
+ })
@@ -0,0 +1,337 @@
1
+ import {
2
+ createPublicClient,
3
+ http,
4
+ parseTransaction,
5
+ type PublicClient,
6
+ hashMessage,
7
+ hashTypedData,
8
+ keccak256,
9
+ toBytes,
10
+ type Hex,
11
+ serializeTransaction,
12
+ type Signature,
13
+ numberToHex,
14
+ getAddress,
15
+ type Address,
16
+ type Hash,
17
+ concatHex,
18
+ encodeAbiParameters,
19
+ hexToBigInt,
20
+ concat,
21
+ pad,
22
+ isAddress,
23
+ } from 'viem'
24
+
25
+ import { ChainAdapter } from '@chain-adapters/ChainAdapter'
26
+ import type {
27
+ EVMTransactionRequest,
28
+ EVMUnsignedTransaction,
29
+ EVMMessage,
30
+ EVMTypedData,
31
+ UserOperationV6,
32
+ UserOperationV7,
33
+ } from '@chain-adapters/EVM/types'
34
+ import { fetchEVMFeeProperties } from '@chain-adapters/EVM/utils'
35
+ import type { BaseChainSignatureContract } from '@contracts/ChainSignatureContract'
36
+ import type { HashToSign, RSVSignature, KeyDerivationPath } from '@types'
37
+
38
+ /**
39
+ * Implementation of the ChainAdapter interface for EVM-compatible networks.
40
+ * Handles interactions with Ethereum Virtual Machine based blockchains like Ethereum, BSC, Polygon, etc.
41
+ */
42
+ export class EVM extends ChainAdapter<
43
+ EVMTransactionRequest,
44
+ EVMUnsignedTransaction
45
+ > {
46
+ private readonly client: PublicClient
47
+ private readonly contract: BaseChainSignatureContract
48
+
49
+ /**
50
+ * Creates a new EVM chain instance
51
+ * @param params - Configuration parameters
52
+ * @param params.rpcUrl - URL of the EVM JSON-RPC provider (e.g., Infura endpoint)
53
+ * @param params.contract - Instance of the chain signature contract for MPC operations
54
+ */
55
+ constructor({
56
+ rpcUrl,
57
+ contract,
58
+ }: {
59
+ rpcUrl: string
60
+ contract: BaseChainSignatureContract
61
+ }) {
62
+ super()
63
+
64
+ this.contract = contract
65
+ this.client = createPublicClient({
66
+ transport: http(rpcUrl),
67
+ })
68
+ }
69
+
70
+ private async attachGasAndNonce(
71
+ transaction: EVMTransactionRequest
72
+ ): Promise<EVMUnsignedTransaction> {
73
+ const fees = await fetchEVMFeeProperties(this.client, transaction)
74
+ const nonce = await this.client.getTransactionCount({
75
+ address: transaction.from,
76
+ })
77
+
78
+ const { from, ...rest } = transaction
79
+
80
+ return {
81
+ ...fees,
82
+ nonce,
83
+ chainId: Number(await this.client.getChainId()),
84
+ type: 'eip1559',
85
+ ...rest,
86
+ }
87
+ }
88
+
89
+ private transformRSVSignature(signature: RSVSignature): Signature {
90
+ return {
91
+ r: `0x${signature.r}`,
92
+ s: `0x${signature.s}`,
93
+ yParity: signature.v - 27,
94
+ }
95
+ }
96
+
97
+ private assembleSignature(signature: RSVSignature): Hex {
98
+ const { r, s, yParity } = this.transformRSVSignature(signature)
99
+
100
+ if (yParity === undefined) {
101
+ throw new Error('Missing yParity')
102
+ }
103
+
104
+ return concatHex([r, s, numberToHex(yParity + 27, { size: 1 })])
105
+ }
106
+
107
+ async deriveAddressAndPublicKey(
108
+ predecessor: string,
109
+ path: KeyDerivationPath
110
+ ): Promise<{
111
+ address: string
112
+ publicKey: string
113
+ }> {
114
+ const uncompressedPubKey = await this.contract.getDerivedPublicKey({
115
+ path,
116
+ predecessor,
117
+ })
118
+
119
+ if (!uncompressedPubKey) {
120
+ throw new Error('Failed to get derived public key')
121
+ }
122
+
123
+ const publicKeyNoPrefix = uncompressedPubKey.startsWith('04')
124
+ ? uncompressedPubKey.slice(2)
125
+ : uncompressedPubKey
126
+
127
+ const hash = keccak256(Buffer.from(publicKeyNoPrefix, 'hex'))
128
+ const address = getAddress(`0x${hash.slice(-40)}`)
129
+
130
+ return {
131
+ address,
132
+ publicKey: uncompressedPubKey,
133
+ }
134
+ }
135
+
136
+ async getBalance(
137
+ address: string
138
+ ): Promise<{ balance: bigint; decimals: number }> {
139
+ const balance = await this.client.getBalance({
140
+ address: address as Address,
141
+ })
142
+ return {
143
+ balance,
144
+ decimals: 18,
145
+ }
146
+ }
147
+
148
+ serializeTransaction(transaction: EVMUnsignedTransaction): `0x${string}` {
149
+ return serializeTransaction(transaction)
150
+ }
151
+
152
+ deserializeTransaction(serialized: `0x${string}`): EVMUnsignedTransaction {
153
+ return parseTransaction(serialized) as EVMUnsignedTransaction
154
+ }
155
+
156
+ async prepareTransactionForSigning(
157
+ transactionRequest: EVMTransactionRequest
158
+ ): Promise<{
159
+ transaction: EVMUnsignedTransaction
160
+ hashesToSign: HashToSign[]
161
+ }> {
162
+ const transaction = await this.attachGasAndNonce(transactionRequest)
163
+
164
+ const serializedTx = serializeTransaction(transaction)
165
+ const txHash = toBytes(keccak256(serializedTx))
166
+
167
+ return {
168
+ transaction,
169
+ hashesToSign: [Array.from(txHash)],
170
+ }
171
+ }
172
+
173
+ async prepareMessageForSigning(message: EVMMessage): Promise<{
174
+ hashToSign: HashToSign
175
+ }> {
176
+ return {
177
+ hashToSign: Array.from(toBytes(hashMessage(message))),
178
+ }
179
+ }
180
+
181
+ async prepareTypedDataForSigning(typedDataRequest: EVMTypedData): Promise<{
182
+ hashToSign: HashToSign
183
+ }> {
184
+ return {
185
+ hashToSign: Array.from(toBytes(hashTypedData(typedDataRequest))),
186
+ }
187
+ }
188
+
189
+ /**
190
+ * This implementation is a common step for Biconomy and Alchemy.
191
+ * Key differences between implementations:
192
+ * - Signature format: Biconomy omits 0x00 prefix when concatenating, Alchemy includes it
193
+ * - Version support: Biconomy only supports v6, Alchemy supports both v6 and v7
194
+ * - Validation: Biconomy uses modules for signature validation, Alchemy uses built-in validation
195
+ */
196
+ async prepareUserOpForSigning(
197
+ userOp: UserOperationV7 | UserOperationV6,
198
+ entryPointAddress?: Address,
199
+ chainIdArgs?: number
200
+ ): Promise<{
201
+ userOp: UserOperationV7 | UserOperationV6
202
+ hashToSign: HashToSign
203
+ }> {
204
+ const chainId = chainIdArgs ?? (await this.client.getChainId())
205
+ const entryPoint =
206
+ entryPointAddress || '0x0000000071727De22E5E9d8BAf0edAc6f37da032'
207
+
208
+ const encoded = encodeAbiParameters(
209
+ [{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }],
210
+ [
211
+ keccak256(
212
+ encodeAbiParameters(
213
+ [
214
+ { type: 'address' },
215
+ { type: 'uint256' },
216
+ { type: 'bytes32' },
217
+ { type: 'bytes32' },
218
+ { type: 'bytes32' },
219
+ { type: 'uint256' },
220
+ { type: 'bytes32' },
221
+ { type: 'bytes32' },
222
+ ],
223
+ [
224
+ userOp.sender,
225
+ hexToBigInt(userOp.nonce),
226
+ keccak256(
227
+ 'factory' in userOp &&
228
+ 'factoryData' in userOp &&
229
+ userOp.factory &&
230
+ userOp.factoryData
231
+ ? concat([userOp.factory, userOp.factoryData])
232
+ : 'initCode' in userOp
233
+ ? userOp.initCode
234
+ : '0x'
235
+ ),
236
+ keccak256(userOp.callData),
237
+ concat([
238
+ pad(userOp.verificationGasLimit, { size: 16 }),
239
+ pad(userOp.callGasLimit, { size: 16 }),
240
+ ]),
241
+ hexToBigInt(userOp.preVerificationGas),
242
+ concat([
243
+ pad(userOp.maxPriorityFeePerGas, { size: 16 }),
244
+ pad(userOp.maxFeePerGas, { size: 16 }),
245
+ ]),
246
+ keccak256(
247
+ 'paymaster' in userOp &&
248
+ userOp.paymaster &&
249
+ isAddress(userOp.paymaster)
250
+ ? concat([
251
+ userOp.paymaster,
252
+ pad(userOp.paymasterVerificationGasLimit, { size: 16 }),
253
+ pad(userOp.paymasterPostOpGasLimit, { size: 16 }),
254
+ userOp.paymasterData,
255
+ ])
256
+ : 'paymasterAndData' in userOp
257
+ ? userOp.paymasterAndData
258
+ : '0x'
259
+ ),
260
+ ]
261
+ )
262
+ ),
263
+ entryPoint,
264
+ BigInt(chainId),
265
+ ]
266
+ )
267
+
268
+ const userOpHash = keccak256(encoded)
269
+
270
+ return {
271
+ userOp,
272
+ hashToSign: Array.from(toBytes(hashMessage({ raw: userOpHash }))),
273
+ }
274
+ }
275
+
276
+ finalizeTransactionSigning({
277
+ transaction,
278
+ rsvSignatures,
279
+ }: {
280
+ transaction: EVMUnsignedTransaction
281
+ rsvSignatures: RSVSignature[]
282
+ }): `0x02${string}` {
283
+ const signature = this.transformRSVSignature(rsvSignatures[0])
284
+
285
+ return serializeTransaction(transaction, signature)
286
+ }
287
+
288
+ finalizeMessageSigning({
289
+ rsvSignature,
290
+ }: {
291
+ rsvSignature: RSVSignature
292
+ }): Hex {
293
+ return this.assembleSignature(rsvSignature)
294
+ }
295
+
296
+ finalizeTypedDataSigning({
297
+ rsvSignature,
298
+ }: {
299
+ rsvSignature: RSVSignature
300
+ }): Hex {
301
+ return this.assembleSignature(rsvSignature)
302
+ }
303
+
304
+ finalizeUserOpSigning({
305
+ userOp,
306
+ rsvSignature,
307
+ }: {
308
+ userOp: UserOperationV7 | UserOperationV6
309
+ rsvSignature: RSVSignature
310
+ }): UserOperationV7 | UserOperationV6 {
311
+ const { r, s, yParity } = this.transformRSVSignature(rsvSignature)
312
+ if (yParity === undefined) {
313
+ throw new Error('Missing yParity')
314
+ }
315
+
316
+ return {
317
+ ...userOp,
318
+ signature: concatHex([
319
+ '0x00', // Alchemy specific implementation. Biconomy doesn't include the 0x00 prefix.
320
+ r,
321
+ s,
322
+ numberToHex(Number(yParity + 27), { size: 1 }),
323
+ ]),
324
+ }
325
+ }
326
+
327
+ async broadcastTx(txSerialized: `0x${string}`): Promise<Hash> {
328
+ try {
329
+ return await this.client.sendRawTransaction({
330
+ serializedTransaction: txSerialized,
331
+ })
332
+ } catch (error) {
333
+ console.error('Transaction broadcast failed:', error)
334
+ throw new Error('Failed to broadcast transaction.')
335
+ }
336
+ }
337
+ }
@@ -0,0 +1,11 @@
1
+ // EVM
2
+ export { EVM } from './EVM'
3
+
4
+ export { fetchEVMFeeProperties } from './utils'
5
+
6
+ export type {
7
+ EVMTransactionRequest,
8
+ EVMUnsignedTransaction,
9
+ EVMMessage,
10
+ EVMTypedData,
11
+ } from './types'