perps-sdk-ts 1.0.1

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 (117) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CONTRACT_METHOD_FIXES.md +189 -0
  3. package/INTEGRATION_SUMMARY.md +219 -0
  4. package/OPTIMIZATION_GUIDE.md +238 -0
  5. package/README.md +384 -0
  6. package/SNAPSHOT_FIX_SUMMARY.md +161 -0
  7. package/SNAPSHOT_OPTIMIZATION_SUMMARY.md +199 -0
  8. package/dist/abis/Referral.d.ts +36 -0
  9. package/dist/abis/Referral.js +4 -0
  10. package/dist/abis/Trading.d.ts +57 -0
  11. package/dist/abis/Trading.js +742 -0
  12. package/dist/abis/erc20.d.ts +51 -0
  13. package/dist/abis/erc20.js +4 -0
  14. package/dist/abis/index.d.ts +8 -0
  15. package/dist/abis/index.js +24 -0
  16. package/dist/abis/multicall.d.ts +85 -0
  17. package/dist/abis/multicall.js +4 -0
  18. package/dist/abis/pairInfos.d.ts +77 -0
  19. package/dist/abis/pairInfos.js +4 -0
  20. package/dist/abis/pairStorage.d.ts +124 -0
  21. package/dist/abis/pairStorage.js +4 -0
  22. package/dist/abis/priceAggregator.d.ts +77 -0
  23. package/dist/abis/priceAggregator.js +4 -0
  24. package/dist/abis/tardingStorage.d.ts +97 -0
  25. package/dist/abis/tardingStorage.js +1295 -0
  26. package/dist/abis.d.ts +623 -0
  27. package/dist/abis.js +49 -0
  28. package/dist/client.d.ts +118 -0
  29. package/dist/client.js +224 -0
  30. package/dist/config.d.ts +43 -0
  31. package/dist/config.js +42 -0
  32. package/dist/crypto/spki.d.ts +55 -0
  33. package/dist/crypto/spki.js +160 -0
  34. package/dist/feed/feed_client.d.ts +68 -0
  35. package/dist/feed/feed_client.js +239 -0
  36. package/dist/index.d.ts +28 -0
  37. package/dist/index.js +87 -0
  38. package/dist/rpc/asset_parameters.d.ts +62 -0
  39. package/dist/rpc/asset_parameters.js +169 -0
  40. package/dist/rpc/blended.d.ts +23 -0
  41. package/dist/rpc/blended.js +55 -0
  42. package/dist/rpc/category_parameters.d.ts +34 -0
  43. package/dist/rpc/category_parameters.js +105 -0
  44. package/dist/rpc/delegation.d.ts +81 -0
  45. package/dist/rpc/delegation.js +180 -0
  46. package/dist/rpc/fee_parameters.d.ts +46 -0
  47. package/dist/rpc/fee_parameters.js +113 -0
  48. package/dist/rpc/multicall.d.ts +83 -0
  49. package/dist/rpc/multicall.js +117 -0
  50. package/dist/rpc/pair_info_queries.d.ts +101 -0
  51. package/dist/rpc/pair_info_queries.js +161 -0
  52. package/dist/rpc/pairs_cache.d.ts +62 -0
  53. package/dist/rpc/pairs_cache.js +240 -0
  54. package/dist/rpc/referral_operations.d.ts +67 -0
  55. package/dist/rpc/referral_operations.js +143 -0
  56. package/dist/rpc/snapshot.d.ts +49 -0
  57. package/dist/rpc/snapshot.js +162 -0
  58. package/dist/rpc/trade.d.ts +84 -0
  59. package/dist/rpc/trade.js +249 -0
  60. package/dist/rpc/trading_operations.d.ts +103 -0
  61. package/dist/rpc/trading_operations.js +295 -0
  62. package/dist/rpc/trading_parameters.d.ts +49 -0
  63. package/dist/rpc/trading_parameters.js +94 -0
  64. package/dist/signers/base.d.ts +24 -0
  65. package/dist/signers/base.js +10 -0
  66. package/dist/signers/kms.d.ts +47 -0
  67. package/dist/signers/kms.js +172 -0
  68. package/dist/signers/local.d.ts +43 -0
  69. package/dist/signers/local.js +64 -0
  70. package/dist/types.d.ts +1419 -0
  71. package/dist/types.js +245 -0
  72. package/dist/utils.d.ts +52 -0
  73. package/dist/utils.js +134 -0
  74. package/examples/advanced-queries.ts +181 -0
  75. package/examples/basic-usage.ts +78 -0
  76. package/examples/delegation-and-referrals.ts +130 -0
  77. package/examples/get-pyth-ids.ts +61 -0
  78. package/examples/kms-signer.ts +31 -0
  79. package/examples/optimized-snapshot.ts +153 -0
  80. package/examples/price-feed-with-sdk-ids.ts +97 -0
  81. package/examples/price-feed.ts +36 -0
  82. package/examples/trading-operations.ts +149 -0
  83. package/package.json +41 -0
  84. package/src/abis/Referral.ts +3 -0
  85. package/src/abis/Trading.ts +741 -0
  86. package/src/abis/erc20.ts +3 -0
  87. package/src/abis/index.ts +8 -0
  88. package/src/abis/multicall.ts +3 -0
  89. package/src/abis/pairInfos.ts +3 -0
  90. package/src/abis/pairStorage.ts +3 -0
  91. package/src/abis/priceAggregator.ts +3 -0
  92. package/src/abis/tardingStorage.ts +1294 -0
  93. package/src/abis.ts +56 -0
  94. package/src/client.ts +373 -0
  95. package/src/config.ts +62 -0
  96. package/src/crypto/spki.ts +197 -0
  97. package/src/feed/feed_client.ts +288 -0
  98. package/src/index.ts +114 -0
  99. package/src/rpc/asset_parameters.ts +217 -0
  100. package/src/rpc/blended.ts +77 -0
  101. package/src/rpc/category_parameters.ts +128 -0
  102. package/src/rpc/delegation.ts +225 -0
  103. package/src/rpc/fee_parameters.ts +150 -0
  104. package/src/rpc/multicall.ts +164 -0
  105. package/src/rpc/pair_info_queries.ts +208 -0
  106. package/src/rpc/pairs_cache.ts +268 -0
  107. package/src/rpc/referral_operations.ts +164 -0
  108. package/src/rpc/snapshot.ts +210 -0
  109. package/src/rpc/trade.ts +306 -0
  110. package/src/rpc/trading_operations.ts +378 -0
  111. package/src/rpc/trading_parameters.ts +127 -0
  112. package/src/signers/base.ts +27 -0
  113. package/src/signers/kms.ts +212 -0
  114. package/src/signers/local.ts +70 -0
  115. package/src/types.ts +410 -0
  116. package/src/utils.ts +155 -0
  117. package/tsconfig.json +18 -0
package/src/abis.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Contract ABIs for Avantis Protocol
3
+ *
4
+ * These ABIs contain the function signatures needed to interact with
5
+ * the Avantis smart contracts.
6
+ */
7
+
8
+ /**
9
+ * ERC20 ABI (for USDC)
10
+ */
11
+
12
+ import refferal from "./abis/Referral";
13
+ import trading from "./abis/Trading"
14
+ import erc20 from "./abis/erc20"
15
+ import pairStorage from "./abis/pairStorage"
16
+ import tradingStorage from "./abis/tardingStorage"
17
+ import pairinfos from "./abis/pairInfos"
18
+ import priceAggregator from "./abis/priceAggregator"
19
+ import referral from "./abis/Referral"
20
+ import multicall from "./abis/multicall"
21
+
22
+ export const ERC20_ABI = erc20;
23
+
24
+ /**
25
+ * Trading Contract ABI
26
+ */
27
+ export const TRADING_ABI = trading;
28
+
29
+ /**
30
+ * TradingStorage Contract ABI
31
+ */
32
+ export const TRADING_STORAGE_ABI = tradingStorage;
33
+
34
+ /**
35
+ * PairStorage Contract ABI
36
+ */
37
+ export const PAIR_STORAGE_ABI = pairStorage;
38
+
39
+ /**
40
+ * PairInfos Contract ABI
41
+ */
42
+ export const PAIR_INFOS_ABI = pairinfos;
43
+
44
+ /**
45
+ * PriceAggregator Contract ABI
46
+ */
47
+ export const PRICE_AGGREGATOR_ABI = priceAggregator;
48
+ /**
49
+ * Referral Contract ABI
50
+ */
51
+ export const REFERRAL_ABI = referral;
52
+
53
+ /**
54
+ * Multicall Contract ABI
55
+ */
56
+ export const MULTICALL_ABI = multicall;
package/src/client.ts ADDED
@@ -0,0 +1,373 @@
1
+ import { ethers, JsonRpcProvider, Contract, TransactionReceipt, TransactionRequest } from 'ethers';
2
+ import { BaseSigner } from './signers/base';
3
+ import { LocalSigner } from './signers/local';
4
+ import { KMSSigner } from './signers/kms';
5
+ import { FeedClient } from './feed/feed_client';
6
+ import { CONTRACTS, getContractAddress } from './config';
7
+ import { PairsCache } from './rpc/pairs_cache';
8
+ import { AssetParametersRPC } from './rpc/asset_parameters';
9
+ import { CategoryParametersRPC } from './rpc/category_parameters';
10
+ import { FeeParametersRPC } from './rpc/fee_parameters';
11
+ import { TradingParametersRPC } from './rpc/trading_parameters';
12
+ import { BlendedRPC } from './rpc/blended';
13
+ import { TradeRPC } from './rpc/trade';
14
+ import { SnapshotRPC } from './rpc/snapshot';
15
+ import { TradingOperationsRPC } from './rpc/trading_operations';
16
+ import { DelegationRPC } from './rpc/delegation';
17
+ import { PairInfoQueriesRPC } from './rpc/pair_info_queries';
18
+ import { ReferralOperationsRPC } from './rpc/referral_operations';
19
+ import { MulticallRPC } from './rpc/multicall';
20
+ import { fromBlockchain6 } from './types';
21
+ import {
22
+ ERC20_ABI,
23
+ TRADING_ABI,
24
+ TRADING_STORAGE_ABI,
25
+ PAIR_STORAGE_ABI,
26
+ PAIR_INFOS_ABI,
27
+ PRICE_AGGREGATOR_ABI,
28
+ REFERRAL_ABI,
29
+ MULTICALL_ABI,
30
+ } from './abis';
31
+
32
+ /**
33
+ * Main client for interacting with Avantis trading platform
34
+ */
35
+ export class TraderClient {
36
+ public provider: JsonRpcProvider;
37
+ signer?: BaseSigner;
38
+ public feedClient?: FeedClient;
39
+
40
+
41
+ // Contracts
42
+ private contracts: Map<string, Contract> = new Map();
43
+
44
+ // RPC modules
45
+ public pairsCache: PairsCache;
46
+ public assetParams: AssetParametersRPC;
47
+ public categoryParams: CategoryParametersRPC;
48
+ public feeParams: FeeParametersRPC;
49
+ public tradingParams: TradingParametersRPC;
50
+ public blendedParams: BlendedRPC;
51
+ public tradeRPC: TradeRPC;
52
+ public snapshotRPC: SnapshotRPC;
53
+
54
+ // New trading modules
55
+ public tradingOps: TradingOperationsRPC;
56
+ public delegation: DelegationRPC;
57
+ public pairInfoQueries: PairInfoQueriesRPC;
58
+ public referral: ReferralOperationsRPC;
59
+ public multicall: MulticallRPC;
60
+
61
+ /**
62
+ * Create a new TraderClient
63
+ * @param providerUrl - Ethereum RPC endpoint
64
+ * @param signer - Transaction signer (optional)
65
+ * @param feedClient - Feed client for price updates (optional)
66
+ */
67
+ constructor(
68
+ providerUrl: string,
69
+ signer?: BaseSigner,
70
+ feedClient?: FeedClient
71
+ ) {
72
+ this.provider = new JsonRpcProvider(providerUrl);
73
+ this.signer = signer;
74
+ this.feedClient = feedClient;
75
+
76
+
77
+ // Initialize RPC modules
78
+ this.initializeContracts();
79
+ this.pairsCache = new PairsCache(
80
+ this.provider,
81
+ this.getContract('PairStorage')
82
+ );
83
+
84
+ const pairStorage = this.getContract('PairStorage');
85
+ const pairInfos = this.getContract('PairInfos');
86
+ const trading = this.getContract('Trading');
87
+ const tradingStorage = this.getContract('TradingStorage');
88
+ const referral = this.getContract('Referral');
89
+
90
+ this.assetParams = new AssetParametersRPC(
91
+ this.provider,
92
+ pairStorage,
93
+ pairInfos,
94
+ this.pairsCache,
95
+ tradingStorage
96
+ );
97
+
98
+ this.categoryParams = new CategoryParametersRPC(
99
+ this.provider,
100
+ pairStorage,
101
+ this.pairsCache
102
+ );
103
+
104
+ this.feeParams = new FeeParametersRPC(
105
+ this.provider,
106
+ pairInfos,
107
+ this.pairsCache,
108
+ referral
109
+ );
110
+
111
+ this.tradingParams = new TradingParametersRPC(
112
+ this.provider,
113
+ pairInfos,
114
+ this.pairsCache
115
+ );
116
+
117
+ this.blendedParams = new BlendedRPC(
118
+ this.assetParams,
119
+ this.categoryParams,
120
+ this.pairsCache
121
+ );
122
+
123
+ this.tradeRPC = new TradeRPC(
124
+ this.provider,
125
+ trading,
126
+ tradingStorage,
127
+ this.pairsCache
128
+ );
129
+
130
+ this.snapshotRPC = new SnapshotRPC(
131
+ this.pairsCache,
132
+ this.assetParams,
133
+ this.categoryParams,
134
+ this.feeParams,
135
+ this.blendedParams
136
+ );
137
+
138
+ // Initialize new trading modules
139
+ this.tradingOps = new TradingOperationsRPC(
140
+ trading,
141
+ tradingStorage,
142
+ this.signer
143
+ );
144
+
145
+ this.delegation = new DelegationRPC(
146
+ trading,
147
+ this.signer
148
+ );
149
+
150
+ this.pairInfoQueries = new PairInfoQueriesRPC(
151
+ pairInfos,
152
+ this.getContract('PriceAggregator')
153
+ );
154
+
155
+ this.referral = new ReferralOperationsRPC(
156
+ referral,
157
+ this.signer
158
+ );
159
+
160
+ this.multicall = new MulticallRPC(
161
+ this.getContract('Multicall')
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Initialize contract instances
167
+ */
168
+ private initializeContracts(): void {
169
+ this.contracts.set(
170
+ 'TradingStorage',
171
+ new Contract(CONTRACTS.TradingStorage, TRADING_STORAGE_ABI, this.provider)
172
+ );
173
+ this.contracts.set(
174
+ 'PairStorage',
175
+ new Contract(CONTRACTS.PairStorage, PAIR_STORAGE_ABI, this.provider)
176
+ );
177
+ this.contracts.set(
178
+ 'PairInfos',
179
+ new Contract(CONTRACTS.PairInfos, PAIR_INFOS_ABI, this.provider)
180
+ );
181
+ this.contracts.set(
182
+ 'PriceAggregator',
183
+ new Contract(CONTRACTS.PriceAggregator, PRICE_AGGREGATOR_ABI, this.provider)
184
+ );
185
+ this.contracts.set(
186
+ 'USDC',
187
+ new Contract(CONTRACTS.USDC, ERC20_ABI, this.provider)
188
+ );
189
+ this.contracts.set(
190
+ 'Trading',
191
+ new Contract(CONTRACTS.Trading, TRADING_ABI, this.provider)
192
+ );
193
+ this.contracts.set(
194
+ 'Referral',
195
+ new Contract(CONTRACTS.Referral, REFERRAL_ABI, this.provider)
196
+ );
197
+ this.contracts.set(
198
+ 'Multicall',
199
+ new Contract(CONTRACTS.Multicall, MULTICALL_ABI, this.provider)
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Get a contract instance
205
+ * @param name - Contract name
206
+ * @returns Contract instance
207
+ */
208
+ private getContract(name: string): Contract {
209
+ const contract = this.contracts.get(name);
210
+ if (!contract) {
211
+ throw new Error(`Contract ${name} not initialized`);
212
+ }
213
+ return contract;
214
+ }
215
+
216
+ /**
217
+ * Set signer for transaction signing
218
+ * @param signer - Signer instance
219
+ */
220
+ setSigner(signer: BaseSigner): void {
221
+ this.signer = signer;
222
+ this.tradingOps.setSigner(signer);
223
+ this.delegation.setSigner(signer);
224
+ this.referral.setSigner(signer);
225
+ }
226
+
227
+ /**
228
+ * Set local signer using private key
229
+ * @param privateKey - Private key
230
+ */
231
+ setLocalSigner(privateKey: string): void {
232
+ this.signer = new LocalSigner(privateKey, this.provider);
233
+ this.setSigner(this.signer);
234
+ }
235
+
236
+ /**
237
+ * Set AWS KMS signer
238
+ * @param kmsKeyId - KMS key ID
239
+ * @param region - AWS region
240
+ */
241
+ setAwsKmsSigner(kmsKeyId: string, region: string = 'us-east-1'): void {
242
+ this.signer = new KMSSigner(kmsKeyId, this.provider, region);
243
+ this.setSigner(this.signer);
244
+ }
245
+
246
+ /**
247
+ * Get native token balance
248
+ * @param address - Address to check
249
+ * @returns Balance in native token
250
+ */
251
+ async getBalance(address: string): Promise<bigint> {
252
+ return await this.provider.getBalance(address);
253
+ }
254
+
255
+ /**
256
+ * Get USDC balance
257
+ * @param address - Address to check
258
+ * @returns USDC balance
259
+ */
260
+ async getUsdcBalance(address: string): Promise<number> {
261
+ const balance = await this.getContract('USDC').balanceOf(address);
262
+ return fromBlockchain6(balance);
263
+ }
264
+
265
+ /**
266
+ * Get USDC allowance for trading
267
+ * @param address - Address to check
268
+ * @returns Allowance amount
269
+ */
270
+ async getUsdcAllowanceForTrading(address: string): Promise<number> {
271
+ const tradingStorageAddress = await this.getContract('TradingStorage').getAddress();
272
+ const allowance = await this.getContract('USDC').allowance(address, tradingStorageAddress);
273
+ return fromBlockchain6(allowance);
274
+ }
275
+
276
+ /**
277
+ * Approve USDC for trading
278
+ * @param amount - Amount to approve
279
+ * @returns Transaction receipt
280
+ */
281
+ async approveUsdcForTrading(amount: number): Promise<TransactionReceipt | null> {
282
+ if (!this.signer) {
283
+ throw new Error('Signer not set');
284
+ }
285
+
286
+ const tradingStorageAddress = await this.getContract('TradingStorage').getAddress();
287
+ const amountWei = BigInt(Math.floor(amount * 1e6));
288
+
289
+ const tx: TransactionRequest = {
290
+ to: CONTRACTS.USDC,
291
+ data: this.getContract('USDC').interface.encodeFunctionData('approve', [
292
+ tradingStorageAddress,
293
+ amountWei,
294
+ ]),
295
+ };
296
+
297
+ return await this.signAndGetReceipt(tx);
298
+ }
299
+
300
+ /**
301
+ * Sign transaction and wait for receipt
302
+ * @param tx - Transaction to sign
303
+ * @returns Transaction receipt
304
+ */
305
+ async signAndGetReceipt(tx: TransactionRequest): Promise<TransactionReceipt | null> {
306
+ if (!this.signer) {
307
+ throw new Error('Signer not set');
308
+ }
309
+
310
+ // Fill in missing transaction fields
311
+ const address = await this.signer.getAddress();
312
+ tx.from = address;
313
+
314
+ if (!tx.chainId) {
315
+ const network = await this.provider.getNetwork();
316
+ tx.chainId = network.chainId;
317
+ }
318
+
319
+ if (tx.nonce === undefined) {
320
+ tx.nonce = await this.provider.getTransactionCount(address);
321
+ }
322
+
323
+ if (!tx.gasLimit) {
324
+ tx.gasLimit = await this.provider.estimateGas(tx);
325
+ }
326
+
327
+ if (!tx.maxFeePerGas && !tx.gasPrice) {
328
+ const feeData = await this.provider.getFeeData();
329
+ if (feeData.maxFeePerGas) {
330
+ tx.maxFeePerGas = feeData.maxFeePerGas;
331
+ tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || feeData.maxFeePerGas;
332
+ } else {
333
+ tx.gasPrice = feeData.gasPrice || undefined;
334
+ }
335
+ }
336
+
337
+ // Sign the transaction
338
+ const signedTx = await this.signer.signTransaction(tx);
339
+
340
+ // Send the transaction
341
+ const txResponse = await this.provider.broadcastTransaction(signedTx);
342
+
343
+ // Wait for confirmation
344
+ return await txResponse.wait();
345
+ }
346
+
347
+ /**
348
+ * Estimate gas for a transaction
349
+ * @param tx - Transaction to estimate
350
+ * @returns Estimated gas
351
+ */
352
+ async estimateGas(tx: TransactionRequest): Promise<bigint> {
353
+ return await this.provider.estimateGas(tx);
354
+ }
355
+
356
+ /**
357
+ * Get transaction count (nonce)
358
+ * @param address - Address to check
359
+ * @returns Transaction count
360
+ */
361
+ async getTransactionCount(address: string): Promise<number> {
362
+ return await this.provider.getTransactionCount(address);
363
+ }
364
+
365
+ /**
366
+ * Get chain ID
367
+ * @returns Chain ID
368
+ */
369
+ async getChainId(): Promise<bigint> {
370
+ const network = await this.provider.getNetwork();
371
+ return network.chainId;
372
+ }
373
+ }
package/src/config.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Configuration file containing contract addresses and API endpoints
3
+ * for Avantis trading platform
4
+ */
5
+
6
+ export interface ContractAddresses {
7
+ TradingStorage: string;
8
+ PairStorage: string;
9
+ PairInfos: string;
10
+ PriceAggregator: string;
11
+ USDC: string;
12
+ Trading: string;
13
+ Multicall: string;
14
+ Referral: string;
15
+ }
16
+
17
+ /**
18
+ * Mainnet contract addresses for Avantis (Base Network)
19
+ */
20
+ export const CONTRACTS: ContractAddresses = {
21
+ TradingStorage: '0x8a311D7048c35985aa31C131B9A13e03a5f7422d',
22
+ PairStorage: '0x5db3772136e5557EFE028Db05EE95C84D76faEC4',
23
+ PairInfos: '0x81F22d0Cc22977c91bEfE648C9fddf1f2bd977e5',
24
+ PriceAggregator: '0x64e2625621970F8cfA17B294670d61CB883dA511',
25
+ USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
26
+ Trading: '0x44914408af82bC9983bbb330e3578E1105e11d4e',
27
+ Multicall: '0xb7125506Ff25211c4C51DFD8DdED00BE6Fa8Cbf7',
28
+ Referral: '0x1A110bBA13A1f16cCa4b79758BD39290f29De82D',
29
+ };
30
+
31
+ /**
32
+ * API endpoints for Avantis services
33
+ */
34
+ export const API_ENDPOINTS = {
35
+ SOCKET_API: 'https://socket-api-pub.avantisfi.com/socket-api/v1/data',
36
+ PYTH_WS: 'wss://hermes.pyth.network/ws',
37
+ PYTH_HTTP: 'https://hermes.pyth.network/v2/updates/price/latest',
38
+ };
39
+
40
+ /**
41
+ * Network configuration
42
+ */
43
+ export interface NetworkConfig {
44
+ chainId: number;
45
+ name: string;
46
+ rpcUrl: string;
47
+ contracts: ContractAddresses;
48
+ }
49
+
50
+ /**
51
+ * Get contract address by name
52
+ */
53
+ export function getContractAddress(contractName: keyof ContractAddresses): string {
54
+ return CONTRACTS[contractName];
55
+ }
56
+
57
+ /**
58
+ * Update contract addresses (useful for testing or different networks)
59
+ */
60
+ export function setContractAddresses(addresses: Partial<ContractAddresses>): void {
61
+ Object.assign(CONTRACTS, addresses);
62
+ }
@@ -0,0 +1,197 @@
1
+ import { keccak256, getBytes, concat, SigningKey } from 'ethers';
2
+
3
+ /**
4
+ * Cryptographic utilities for KMS signature handling
5
+ * Handles ECDSA signature conversion and Ethereum address derivation
6
+ */
7
+
8
+ /**
9
+ * Convert a public key (as big integer coordinates) to an Ethereum address
10
+ * @param publicKeyX - X coordinate of public key
11
+ * @param publicKeyY - Y coordinate of public key
12
+ * @returns Ethereum address
13
+ */
14
+ export function publicKeyIntToEthAddress(publicKeyX: bigint, publicKeyY: bigint): string {
15
+ // Concatenate 0x04 (uncompressed key prefix) + x + y
16
+ const xBytes = publicKeyX.toString(16).padStart(64, '0');
17
+ const yBytes = publicKeyY.toString(16).padStart(64, '0');
18
+ const publicKeyHex = '0x04' + xBytes + yBytes;
19
+
20
+ // Keccak256 hash
21
+ const hash = keccak256(publicKeyHex);
22
+
23
+ // Take last 20 bytes as address
24
+ return '0x' + hash.slice(-40);
25
+ }
26
+
27
+ /**
28
+ * Parse DER-encoded public key and convert to Ethereum address
29
+ * @param derPublicKey - DER-encoded public key bytes
30
+ * @returns Ethereum address
31
+ */
32
+ export function derEncodedPublicKeyToEthAddress(derPublicKey: Uint8Array): string {
33
+ // Parse the DER structure to extract the public key
34
+ // DER structure for ECDSA public key (simplified parsing)
35
+
36
+ // Find the public key bytes (starts with 0x04 for uncompressed key)
37
+ let publicKeyStart = -1;
38
+ for (let i = 0; i < derPublicKey.length - 64; i++) {
39
+ if (derPublicKey[i] === 0x04) {
40
+ publicKeyStart = i;
41
+ break;
42
+ }
43
+ }
44
+
45
+ if (publicKeyStart === -1) {
46
+ throw new Error('Could not find uncompressed public key in DER structure');
47
+ }
48
+
49
+ // Extract 65 bytes: 0x04 + 32 bytes X + 32 bytes Y
50
+ const publicKeyBytes = derPublicKey.slice(publicKeyStart, publicKeyStart + 65);
51
+
52
+ // Convert to hex
53
+ const publicKeyHex = '0x' + Buffer.from(publicKeyBytes).toString('hex');
54
+
55
+ // Keccak256 hash (skip the 0x04 prefix)
56
+ const hash = keccak256(publicKeyHex);
57
+
58
+ // Take last 20 bytes as address
59
+ return '0x' + hash.slice(-40);
60
+ }
61
+
62
+ /**
63
+ * Parse DER-encoded signature to extract r and s values
64
+ * @param derSignature - DER-encoded signature
65
+ * @returns Object with r and s as hex strings
66
+ */
67
+ export function getSigRS(derSignature: Uint8Array): { r: string; s: string } {
68
+ // DER signature structure:
69
+ // 0x30 [total-length] 0x02 [r-length] [r-bytes] 0x02 [s-length] [s-bytes]
70
+
71
+ let offset = 0;
72
+
73
+ // Check sequence tag
74
+ if (derSignature[offset++] !== 0x30) {
75
+ throw new Error('Invalid DER signature: missing sequence tag');
76
+ }
77
+
78
+ // Skip total length
79
+ offset++;
80
+
81
+ // Read r
82
+ if (derSignature[offset++] !== 0x02) {
83
+ throw new Error('Invalid DER signature: missing r integer tag');
84
+ }
85
+
86
+ const rLength = derSignature[offset++];
87
+ let rBytes = derSignature.slice(offset, offset + rLength);
88
+ offset += rLength;
89
+
90
+ // Remove leading zero if present (DER encoding adds it for positive numbers)
91
+ if (rBytes[0] === 0x00) {
92
+ rBytes = rBytes.slice(1);
93
+ }
94
+
95
+ const r = '0x' + Buffer.from(rBytes).toString('hex');
96
+
97
+ // Read s
98
+ if (derSignature[offset++] !== 0x02) {
99
+ throw new Error('Invalid DER signature: missing s integer tag');
100
+ }
101
+
102
+ const sLength = derSignature[offset++];
103
+ let sBytes = derSignature.slice(offset, offset + sLength);
104
+
105
+ // Remove leading zero if present
106
+ if (sBytes[0] === 0x00) {
107
+ sBytes = sBytes.slice(1);
108
+ }
109
+
110
+ const s = '0x' + Buffer.from(sBytes).toString('hex');
111
+
112
+ return { r, s };
113
+ }
114
+
115
+ /**
116
+ * Recover the v value for an ECDSA signature
117
+ * @param msgHash - Message hash that was signed
118
+ * @param r - r component of signature
119
+ * @param s - s component of signature
120
+ * @param expectedAddress - Expected Ethereum address
121
+ * @returns v value (27 or 28)
122
+ */
123
+ export function getSigV(
124
+ msgHash: string,
125
+ r: string,
126
+ s: string,
127
+ expectedAddress: string
128
+ ): number {
129
+ // Try both possible v values (27 and 28)
130
+ for (const v of [27, 28]) {
131
+ try {
132
+ const signature = {
133
+ r,
134
+ s,
135
+ v,
136
+ };
137
+
138
+ // Construct the signature string
139
+ const sigString = concat([
140
+ signature.r,
141
+ signature.s,
142
+ new Uint8Array([signature.v]),
143
+ ]);
144
+
145
+ // Try to recover the address
146
+ const recoveredAddress = SigningKey.recoverPublicKey(
147
+ getBytes(msgHash),
148
+ sigString
149
+ );
150
+
151
+ const recoveredAddr = keccak256('0x' + recoveredAddress.slice(4));
152
+ const addr = '0x' + recoveredAddr.slice(-40);
153
+
154
+ if (addr.toLowerCase() === expectedAddress.toLowerCase()) {
155
+ return v;
156
+ }
157
+ } catch (e) {
158
+ // Continue to next v value
159
+ continue;
160
+ }
161
+ }
162
+
163
+ throw new Error('Could not recover v value from signature');
164
+ }
165
+
166
+ /**
167
+ * Get complete signature (r, s, v) from DER-encoded KMS signature
168
+ * @param kmsSignature - DER-encoded signature from KMS
169
+ * @param msgHash - Message hash that was signed
170
+ * @param ethAddress - Expected Ethereum address
171
+ * @returns Complete signature object
172
+ */
173
+ export function getSigRSV(
174
+ kmsSignature: Uint8Array,
175
+ msgHash: string,
176
+ ethAddress: string
177
+ ): { r: string; s: string; v: number } {
178
+ const { r, s } = getSigRS(kmsSignature);
179
+ const v = getSigV(msgHash, r, s, ethAddress);
180
+
181
+ return { r, s, v };
182
+ }
183
+
184
+ /**
185
+ * Convert signature components to a single hex string
186
+ * @param r - r component
187
+ * @param s - s component
188
+ * @param v - v component
189
+ * @returns Signature as hex string
190
+ */
191
+ export function signatureToHex(r: string, s: string, v: number): string {
192
+ const rHex = r.startsWith('0x') ? r.slice(2) : r;
193
+ const sHex = s.startsWith('0x') ? s.slice(2) : s;
194
+ const vHex = v.toString(16).padStart(2, '0');
195
+
196
+ return '0x' + rHex.padStart(64, '0') + sHex.padStart(64, '0') + vHex;
197
+ }