@tapforce/pod-bridge-sdk 1.2.3 → 1.2.5

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.
package/README.md CHANGED
@@ -110,6 +110,11 @@ const receipt = await tx.wait();
110
110
  Deposits on Pod require manual claim on ETH with aggregated validator signatures.
111
111
 
112
112
  ```typescript
113
+ import {
114
+ PodToSourceChainActionClient,
115
+ extractAggregatedSignaturesWithValidators
116
+ } from '@tapforce/pod-bridge-sdk';
117
+
113
118
  const client = new PodToSourceChainActionClient(actionConfig);
114
119
 
115
120
  // Step 1: Deposit on Pod
@@ -123,17 +128,26 @@ const depositTx = client.deposit({
123
128
  const tx = await podSigner.sendTransaction(depositTx);
124
129
  const receipt = await tx.wait();
125
130
 
126
- // Step 2: Get aggregated signatures from your indexer/API
127
- // (Implementation depends on your backend)
131
+ // Step 2: Get the receipt with validator signatures
132
+ const podReceipt = await podProvider.send('eth_getTransactionReceipt', [tx.hash]);
133
+
134
+ // Step 3: Get committee for v-value recovery
135
+ const committee = await podProvider.send('pod_getCommittee', {});
128
136
 
129
- // Step 3: Claim on ETH
137
+ // Step 4: Extract aggregated signatures (65-byte format with correct v-values)
138
+ const aggregatedSignatures = extractAggregatedSignaturesWithValidators(
139
+ podReceipt,
140
+ committee.validators // [[0, "02..."], [1, "03..."], ...] compressed pubkeys
141
+ );
142
+
143
+ // Step 5: Claim on ETH
130
144
  const claimTx = client.claim({
131
145
  claimData: {
132
146
  ethTokenAddress: '0xETH_TOKEN_ADDRESS', // Token on ETH (different from Pod)
133
147
  amount: ethers.parseEther('1'),
134
148
  to: '0x...', // Recipient
135
149
  committeeEpoch: 0, // Hardcoded for now
136
- aggregatedSignatures: '0x...', // 65-byte signatures (r,s,v) concatenated
150
+ aggregatedSignatures, // 65-byte signatures (r,s,v) concatenated
137
151
  depositTxHash: tx.hash // Deposit TX hash from Pod
138
152
  },
139
153
  from: '0x...'
@@ -269,30 +283,68 @@ interface ClaimProofData {
269
283
 
270
284
  ### Signature Recovery Helpers
271
285
 
272
- Pod uses 64-byte signatures without the parity bit (v), but the ETH contract requires 65-byte (r,s,v) format.
273
- The helpers exactly match the Rust implementation - they try both recovery IDs and verify against the public key.
286
+ Pod returns DER-encoded 64-byte signatures without the parity bit (v), but the ETH contract requires 65-byte (r,s,v) format.
287
+ The SDK handles this by trying both v values (27/28) and verifying against the committee's validator public keys.
274
288
 
275
289
  ```typescript
276
290
  import {
291
+ extractAggregatedSignaturesWithValidators,
277
292
  extractAggregatedSignatures,
278
- recoverSignature65B,
279
- addressFromPublicKey
293
+ parseDerSignature,
294
+ addressFromPublicKey,
295
+ recoverSignatureWithoutPubkey
280
296
  } from '@tapforce/pod-bridge-sdk';
281
297
 
282
- // Recommended: Extract signatures directly from Pod receipt
298
+ // Recommended: Extract with validator verification (correct v-values)
283
299
  const podReceipt = await podProvider.send('eth_getTransactionReceipt', [depositTxHash]);
284
- const aggregatedSignatures = extractAggregatedSignatures(podReceipt, msgHash);
285
-
286
- // Low-level: Recover single signature (matches Rust recover_65b_signature)
287
- const sig65 = recoverSignature65B(
288
- r, // r component (32 bytes hex)
289
- s, // s component (32 bytes hex)
290
- msgHash, // message hash that was signed
291
- publicKey // signer's public key (to verify recovery)
300
+ const committee = await podProvider.send('pod_getCommittee', {});
301
+ const aggregatedSignatures = extractAggregatedSignaturesWithValidators(
302
+ podReceipt,
303
+ committee.validators // [[index, compressedPubKey], ...]
292
304
  );
293
305
 
294
- // Utility: Derive address from public key
295
- const address = addressFromPublicKey(publicKey);
306
+ // Alternative: Extract without validator verification (may have wrong v-values)
307
+ const sigs = extractAggregatedSignatures(podReceipt);
308
+
309
+ // Low-level: Parse DER signature to r,s components
310
+ const { r, s } = parseDerSignature(derEncodedSig);
311
+
312
+ // Low-level: Recover 65-byte signature without pubkey (tries v=27 first)
313
+ const sig65 = recoverSignatureWithoutPubkey(r, s, msgHash);
314
+
315
+ // Utility: Derive address from uncompressed public key
316
+ const address = addressFromPublicKey(uncompressedPubKey);
317
+ ```
318
+
319
+ #### Pod Receipt Format
320
+
321
+ ```typescript
322
+ interface PodTransactionReceipt {
323
+ // ... standard fields ...
324
+ attested_tx: {
325
+ hash: string; // Hash signed by validators
326
+ committee_epoch: number;
327
+ };
328
+ signatures: {
329
+ "0": string; // DER-encoded signature from validator 0
330
+ "1": string; // DER-encoded signature from validator 1
331
+ // ...
332
+ };
333
+ }
334
+ ```
335
+
336
+ #### Committee Format
337
+
338
+ ```typescript
339
+ // pod_getCommittee response
340
+ {
341
+ validators: [
342
+ [0, "024ee7..."], // [index, compressed_secp256k1_pubkey]
343
+ [1, "025c59..."],
344
+ // ...
345
+ ],
346
+ quorum_size: 3
347
+ }
296
348
  ```
297
349
 
298
350
  ## Events
@@ -6,7 +6,7 @@ import { PodBridgeConfig, BridgeRequest, BridgeRequestWithType } from "../../lib
6
6
  * to provide a unified view of bridge activity across both chains.
7
7
  *
8
8
  * Bridge Architecture:
9
- * - ETH -> Pod: deposits on ETH, AUTO-CLAIM on Pod (no claim needed)
9
+ * - ETH -> Pod: deposits on ETH, claimable on Pod once Sepolia block is finalized
10
10
  * - Pod -> ETH: deposits on Pod, claims on ETH with aggregated validator signatures
11
11
  * - Only ERC20 tokens supported (wrap ETH to WETH)
12
12
  *
@@ -15,7 +15,6 @@ import { PodBridgeConfig, BridgeRequest, BridgeRequestWithType } from "../../lib
15
15
  * - Claims release ERC20 tokens with validator signatures (Pod -> ETH direction)
16
16
  *
17
17
  * POD Chain:
18
- * - Auto-claim for ETH -> Pod deposits (instant finality)
19
18
  * - Deposits for Pod -> ETH direction
20
19
  */
21
20
  export declare class PodBridgeTrackerClient {
@@ -46,19 +45,17 @@ export declare class PodBridgeTrackerClient {
46
45
  /**
47
46
  * Check if deposits can be claimed
48
47
  *
49
- * New architecture:
50
- * - ETH -> Pod: AUTO-CLAIM on Pod, no manual claim needed (always returns false)
48
+ * - ETH -> Pod: Claimable once the Sepolia deposit block is finalized
51
49
  * - Pod -> ETH: Manual claim needed on ETH with aggregated signatures
52
50
  *
53
51
  * @param deposit The bridge request to check
54
- * @returns True if the deposit can be manually claimed (only Pod -> ETH direction)
52
+ * @returns True if the deposit can be claimed
55
53
  */
56
54
  canBeClaimed(deposit: BridgeRequest): Promise<boolean>;
57
55
  /**
58
56
  * Batch check claim status for multiple deposits
59
57
  *
60
- * New architecture:
61
- * - ETH -> Pod: AUTO-CLAIM on Pod (mark as claimed after finalization)
58
+ * - ETH -> Pod: isClaimed=false, isClaimable=true once Sepolia block is finalized
62
59
  * - Pod -> ETH: Check claim events on Source Chain
63
60
  *
64
61
  * @private
@@ -67,8 +64,7 @@ export declare class PodBridgeTrackerClient {
67
64
  /**
68
65
  * Batch check if multiple requests have been processed on-chain
69
66
  *
70
- * New architecture:
71
- * - ETH -> Pod: Auto-claimed after finalization
67
+ * - ETH -> Pod: Not auto-claimed; isClaimed reflects actual claim status
72
68
  * - Pod -> ETH: Check claim events on Source Chain
73
69
  *
74
70
  * @param deposits Array of deposits to check
@@ -11,7 +11,7 @@ const pod_tracker_service_1 = require("./pod-tracker.service");
11
11
  * to provide a unified view of bridge activity across both chains.
12
12
  *
13
13
  * Bridge Architecture:
14
- * - ETH -> Pod: deposits on ETH, AUTO-CLAIM on Pod (no claim needed)
14
+ * - ETH -> Pod: deposits on ETH, claimable on Pod once Sepolia block is finalized
15
15
  * - Pod -> ETH: deposits on Pod, claims on ETH with aggregated validator signatures
16
16
  * - Only ERC20 tokens supported (wrap ETH to WETH)
17
17
  *
@@ -20,7 +20,6 @@ const pod_tracker_service_1 = require("./pod-tracker.service");
20
20
  * - Claims release ERC20 tokens with validator signatures (Pod -> ETH direction)
21
21
  *
22
22
  * POD Chain:
23
- * - Auto-claim for ETH -> Pod deposits (instant finality)
24
23
  * - Deposits for Pod -> ETH direction
25
24
  */
26
25
  class PodBridgeTrackerClient {
@@ -107,18 +106,17 @@ class PodBridgeTrackerClient {
107
106
  /**
108
107
  * Check if deposits can be claimed
109
108
  *
110
- * New architecture:
111
- * - ETH -> Pod: AUTO-CLAIM on Pod, no manual claim needed (always returns false)
109
+ * - ETH -> Pod: Claimable once the Sepolia deposit block is finalized
112
110
  * - Pod -> ETH: Manual claim needed on ETH with aggregated signatures
113
111
  *
114
112
  * @param deposit The bridge request to check
115
- * @returns True if the deposit can be manually claimed (only Pod -> ETH direction)
113
+ * @returns True if the deposit can be claimed
116
114
  */
117
115
  async canBeClaimed(deposit) {
118
- // ETH -> Pod deposits have AUTO-CLAIM on Pod, no manual claim needed
119
116
  if (deposit.deposit.chain === pod_bridge_types_1.BridgeChain.SOURCE_CHAIN) {
120
- // Auto-claim on Pod - nothing to claim manually
121
- return false;
117
+ // ETH -> Pod: claimable once the deposit block is finalized on Sepolia
118
+ const isFinalized = await this.sourceChainTracker.isBlockFinalized(deposit.deposit.blockNumber);
119
+ return isFinalized && !deposit.isClaimed;
122
120
  }
123
121
  else {
124
122
  // POD -> ETH deposits need manual claim on ETH
@@ -129,8 +127,7 @@ class PodBridgeTrackerClient {
129
127
  /**
130
128
  * Batch check claim status for multiple deposits
131
129
  *
132
- * New architecture:
133
- * - ETH -> Pod: AUTO-CLAIM on Pod (mark as claimed after finalization)
130
+ * - ETH -> Pod: isClaimed=false, isClaimable=true once Sepolia block is finalized
134
131
  * - Pod -> ETH: Check claim events on Source Chain
135
132
  *
136
133
  * @private
@@ -200,8 +197,7 @@ class PodBridgeTrackerClient {
200
197
  /**
201
198
  * Batch check if multiple requests have been processed on-chain
202
199
  *
203
- * New architecture:
204
- * - ETH -> Pod: Auto-claimed after finalization
200
+ * - ETH -> Pod: Not auto-claimed; isClaimed reflects actual claim status
205
201
  * - Pod -> ETH: Check claim events on Source Chain
206
202
  *
207
203
  * @param deposits Array of deposits to check
@@ -210,8 +206,6 @@ class PodBridgeTrackerClient {
210
206
  async areRequestsProcessed(deposits) {
211
207
  if (deposits.length === 0)
212
208
  return [];
213
- // Get finalized block for ETH -> Pod auto-claim check
214
- const finalizedBlock = await this.sourceChainTracker.getFinalizedBlockNumber();
215
209
  // Group deposits by origin chain
216
210
  const podDeposits = deposits.filter(d => d.deposit.chain === pod_bridge_types_1.BridgeChain.POD);
217
211
  // Check claims on Source Chain for Pod -> ETH deposits
@@ -220,8 +214,8 @@ class PodBridgeTrackerClient {
220
214
  : new Map();
221
215
  return deposits.map(d => {
222
216
  if (d.deposit.chain === pod_bridge_types_1.BridgeChain.SOURCE_CHAIN) {
223
- // ETH -> Pod: Auto-claimed once finalized
224
- return d.deposit.blockNumber <= finalizedBlock;
217
+ // ETH -> Pod: isClaimed is set during updateClaimStatus
218
+ return d.isClaimed;
225
219
  }
226
220
  else {
227
221
  // Pod -> ETH: Check claim event on Source Chain
@@ -110,11 +110,20 @@ class SourceChainTrackerService {
110
110
  async getClaimEvents(deposits) {
111
111
  const uniqueRecipients = [...new Set(deposits.map(d => d.deposit.destination))];
112
112
  const allClaimLogs = [];
113
+ const startBlock = this.config.deploymentBlock ?? 0;
114
+ const currentBlock = await this.provider.getBlockNumber();
115
+ const BLOCK_BATCH_SIZE = 10000;
113
116
  for (const recipient of uniqueRecipients) {
114
117
  // Event: Claim(bytes32 indexed id, address indexed to, address token, uint256 amount)
115
118
  const claimFilter = this.bridge.filters.Claim(null, recipient);
116
- const claimLogs = await this.bridge.queryFilter(claimFilter);
117
- allClaimLogs.push(...claimLogs);
119
+ for (let start = startBlock; start <= currentBlock; start += BLOCK_BATCH_SIZE) {
120
+ const end = Math.min(start + BLOCK_BATCH_SIZE - 1, currentBlock);
121
+ const claimLogs = await this.bridge.queryFilter(claimFilter, start, end);
122
+ allClaimLogs.push(...claimLogs);
123
+ if (start + BLOCK_BATCH_SIZE <= currentBlock) {
124
+ await new Promise(resolve => setTimeout(resolve, 100));
125
+ }
126
+ }
118
127
  }
119
128
  console.log(`[SourceChain] Found ${allClaimLogs.length} claims`);
120
129
  const claimedMap = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapforce/pod-bridge-sdk",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "SDK for interacting with Bridges between pod and other chains",
5
5
  "keywords": [
6
6
  "pod",