@tapforce/pod-bridge-sdk 1.1.17 → 1.2.2

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
@@ -1,387 +1,223 @@
1
1
  # Pod Bridge SDK
2
2
 
3
- TypeScript SDK for interacting with Bridges between POD and other chains - enabling cross-chain token transfers between POD and other EVM chains (e.g., Sepolia).
3
+ TypeScript SDK for bridging ERC20 tokens between POD and EVM chains (e.g., Sepolia/ETH).
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ETH → Pod: Deposit on ETH, AUTO-CLAIM on Pod (no claim TX needed)
9
+ Pod → ETH: Deposit on Pod, claim on ETH with aggregated validator signatures
10
+ ```
11
+
12
+ **Key points:**
13
+ - Only ERC20 tokens supported (wrap ETH to WETH first)
14
+ - `Deposit.id` on source chain = `Claim.id` on destination chain (for tracking)
15
+ - Token addresses differ between chains
4
16
 
5
17
  ## Features
6
18
 
7
- - 🌉 **Bridge Token Deposits**: Deposit ERC20 tokens and native tokens to the bridge
8
- - 🔍 **Track Bridge Requests**: Query deposits sent by or received by any address
9
- - **Claim Status Tracking**: Monitor claim status and finality of bridge requests
10
- - 📝 **Transaction Building**: Generate unsigned transactions ready for signing
11
- - 🔐 **Type-Safe**: Full TypeScript support with comprehensive type definitions
12
- - 🔗 **Dual Architecture Support**: Handles both certificate-based (Source Chain) and precompile-based (POD) claim verification
19
+ - **Bridge ERC20 Deposits**: Deposit tokens to the bridge
20
+ - **Track Bridge Requests**: Query deposits sent/received by any address
21
+ - **Claim Status Tracking**: Monitor claim status and finality
22
+ - **Transaction Building**: Generate unsigned transactions ready for signing
23
+ - **Type-Safe**: Full TypeScript support
13
24
 
14
25
  ## Installation
15
26
 
16
27
  ```bash
17
28
  npm install @tapforce/pod-bridge-sdk
18
- ```
19
-
20
- or
21
-
22
- ```bash
29
+ # or
23
30
  yarn add @tapforce/pod-bridge-sdk
24
- ```
25
-
26
- or
27
-
28
- ```bash
31
+ # or
29
32
  pnpm add @tapforce/pod-bridge-sdk
30
33
  ```
31
34
 
32
35
  ## Quick Start
33
36
 
34
- ### Import the SDK
35
-
36
37
  ```typescript
37
38
  import {
38
39
  SourceChainToPodActionClient,
39
40
  PodToSourceChainActionClient,
40
41
  PodBridgeTrackerClient,
41
42
  PodBridgeConfig,
42
- POD_BRIDGE_ABI,
43
- SOURCE_CHAIN_BRIDGE_ABI
43
+ BRIDGE_ABI
44
44
  } from '@tapforce/pod-bridge-sdk';
45
45
  ```
46
46
 
47
- ### Configuration
47
+ ## Configuration
48
48
 
49
- Create a configuration object with source and destination chain details:
49
+ ### For Action Clients (creating transactions)
50
50
 
51
51
  ```typescript
52
- const config: PodBridgeConfig = {
52
+ import { PodBridgeActionsClientConfig } from '@tapforce/pod-bridge-sdk';
53
+
54
+ const actionConfig: PodBridgeActionsClientConfig = {
53
55
  sourceChain: {
54
- rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
55
- contractAddress: '0x...', // Source chain bridge contract
56
- deploymentBlock: 9502541 // Optional: Start indexing from deployment block (avoids querying empty blocks)
56
+ contractAddress: '0x...' // Bridge contract on ETH/Sepolia
57
57
  },
58
58
  pod: {
59
- rpcUrl: 'https://rpc.v1.dev.pod.network',
60
- contractAddress: '0x...', // Destination chain bridge contract
61
- deploymentBlock: 0 // Optional: POD deployment block
59
+ contractAddress: '0x...' // Bridge contract on Pod
62
60
  }
63
61
  };
64
62
  ```
65
63
 
66
- ## Usage
67
-
68
- ### 1. Action Clients - Creating Transactions
69
-
70
- The SDK provides two action clients for different bridge directions:
71
-
72
- - **`SourceChainToPodActionClient`**: For Source Chain → POD transfers (e.g., Sepolia → POD)
73
- - **`PodToSourceChainActionClient`**: For POD → Source Chain transfers (e.g., POD → Sepolia)
74
-
75
- #### Configuration for Action Clients
64
+ ### For Tracker Client (querying events)
76
65
 
77
66
  ```typescript
78
- const actionConfig = {
67
+ import { PodBridgeConfig } from '@tapforce/pod-bridge-sdk';
68
+ import { ethers } from 'ethers';
69
+
70
+ const trackerConfig: PodBridgeConfig = {
79
71
  sourceChain: {
80
- contractAddress: '0x...' // Source chain bridge contract (e.g., Sepolia)
72
+ provider: new ethers.JsonRpcProvider('https://sepolia.infura.io/v3/YOUR_KEY'),
73
+ contractAddress: '0x...',
74
+ deploymentBlock: 9502541 // Optional: avoids indexing empty blocks
81
75
  },
82
76
  pod: {
83
- contractAddress: '0x...' // POD chain bridge contract
77
+ provider: new ethers.JsonRpcProvider('https://rpc.pod.network'),
78
+ contractAddress: '0x...',
79
+ deploymentBlock: 0
84
80
  }
85
81
  };
86
-
87
- const sourceChainToPodClient = new SourceChainToPodActionClient(actionConfig);
88
- const podToSourceChainClient = new PodToSourceChainActionClient(actionConfig);
89
82
  ```
90
83
 
91
- #### Deposit from Source Chain (e.g., Sepolia → POD)
92
-
93
- ```typescript
94
- // Create unsigned transaction for ERC20 deposit on Source chain
95
- const unsignedTx = sourceChainToPodClient.depositToken({
96
- token: '0x...', // ERC20 token address
97
- amount: '1000000000000000000', // Amount in wei (1 token with 18 decimals)
98
- destinationWalletAddress: '0x...', // Recipient address on POD
99
- from: '0x...' // Optional: sender address
100
- });
101
-
102
- // Deposit native tokens
103
- const unsignedNativeTx = sourceChainToPodClient.depositNative({
104
- amount: '1000000000000000000', // Amount in wei (1 ETH)
105
- destinationWalletAddress: '0x...', // Recipient address on POD
106
- from: '0x...' // Optional: sender address
107
- });
108
-
109
- // Sign and send transaction using your wallet/provider
110
- // const tx = await signer.sendTransaction(unsignedTx);
111
- ```
84
+ ## Usage
112
85
 
113
- #### Claim on POD from Source Chain Deposits
86
+ ### ETH Pod (Auto-claim)
114
87
 
115
- For claiming on POD from Source chain deposits, use block number-based claims:
88
+ Deposits on ETH are automatically claimed on Pod after block finalization (~15 min on Sepolia).
116
89
 
117
90
  ```typescript
118
- // Create unsigned claim transaction for ERC20 on POD
119
- const unsignedTx = sourceChainToPodClient.claimWithBlockNumber({
120
- id: '123', // Request ID from deposit event
121
- token: '0x...', // Token address on source chain
122
- blockNumber: 12345678, // Block number of deposit on source chain
123
- from: '0x...' // Optional: claimer address
124
- });
125
-
126
- // For native tokens
127
- const unsignedNativeTx = sourceChainToPodClient.claimNativeWithBlockNumber({
128
- id: '123',
129
- blockNumber: 12345678,
130
- from: '0x...'
91
+ const client = new SourceChainToPodActionClient(actionConfig);
92
+
93
+ // Create deposit transaction
94
+ const depositTx = client.deposit({
95
+ token: '0xWETH_ADDRESS', // ERC20 token on ETH
96
+ amount: ethers.parseEther('1'),
97
+ destinationWalletAddress: '0x...', // Recipient on Pod
98
+ from: '0x...' // Optional
131
99
  });
132
- ```
133
-
134
- #### Deposit from POD (POD → Source Chain)
135
100
 
136
- ```typescript
137
- // Create unsigned transaction for ERC20 deposit on POD
138
- const unsignedTx = podToSourceChainClient.depositToken({
139
- token: '0x...', // ERC20 token address
140
- amount: '1000000000000000000', // Amount in wei
141
- destinationWalletAddress: '0x...', // Recipient address on Source chain
142
- from: '0x...' // Optional: sender address
143
- });
101
+ // Send transaction
102
+ const tx = await signer.sendTransaction(depositTx);
103
+ const receipt = await tx.wait();
144
104
 
145
- // Deposit native tokens
146
- const unsignedNativeTx = podToSourceChainClient.depositNative({
147
- amount: '1000000000000000000',
148
- destinationWalletAddress: '0x...',
149
- from: '0x...'
150
- });
105
+ // No claim needed - balance auto-credited on Pod after finalization
151
106
  ```
152
107
 
153
- #### Claim on Source Chain from POD Deposits
108
+ ### Pod ETH (Manual claim with signatures)
154
109
 
155
- For claiming on Source chain from POD deposits, use certificate-based claims. You'll need to get the certified log from an external indexer service:
110
+ Deposits on Pod require manual claim on ETH with aggregated validator signatures.
156
111
 
157
112
  ```typescript
158
- import { PodIndexerHttpClient } from '@tapforce/pod-indexer-sdk';
159
-
160
- // Initialize the POD indexer client
161
- const indexerClient = new PodIndexerHttpClient({
162
- apiUrl: 'YOUR_INDEXER_API_URL',
163
- apiKey: 'YOUR_INDEXER_API_KEY'
164
- });
165
-
166
- // Get certified log from POD deposit transaction
167
- const certifiedLogResponse = await indexerClient.getBridgeCertifiedLog({
168
- transactionHash: '0x...', // Transaction hash of deposit on POD
169
- bridgeContractAddress: config.pod.contractAddress,
170
- rpcUrl: config.pod.rpcUrl
171
- });
113
+ const client = new PodToSourceChainActionClient(actionConfig);
172
114
 
173
- const certifiedLog = certifiedLogResponse.result;
174
-
175
- // Create unsigned claim transaction for ERC20 on Source chain
176
- const unsignedTx = podToSourceChainClient.claimWithCertificate({
177
- certifiedLog,
178
- from: '0x...' // Optional: claimer address
179
- });
180
-
181
- // For native tokens
182
- const unsignedNativeTx = podToSourceChainClient.claimNativeWithCertificate({
183
- certifiedLog,
115
+ // Step 1: Deposit on Pod
116
+ const depositTx = client.deposit({
117
+ token: '0xPOD_TOKEN_ADDRESS', // ERC20 token on Pod
118
+ amount: ethers.parseEther('1'),
119
+ destinationWalletAddress: '0x...', // Recipient on ETH
184
120
  from: '0x...'
185
121
  });
186
- ```
187
-
188
- ### 2. Tracker Client - Querying Bridge Activity
189
-
190
- The `PodBridgeTrackerClient` queries the blockchain for deposit and claim events.
191
122
 
192
- ```typescript
193
- const trackerClient = new PodBridgeTrackerClient(config);
194
- ```
195
-
196
- #### Get Deposits Sent by Address
197
-
198
- ```typescript
199
- // Get all deposits sent by an address
200
- const deposits = await trackerClient.getDepositsSentBy(
201
- '0x...', // sender address
202
- 0 // starting block (optional, default: 0)
203
- );
123
+ const tx = await podSigner.sendTransaction(depositTx);
124
+ const receipt = await tx.wait();
204
125
 
205
- deposits.forEach(deposit => {
206
- console.log(`Request ID: ${deposit.requestId}`);
207
- console.log(`Deposit Chain: ${deposit.deposit.chain}`);
208
- console.log(`From: ${deposit.deposit.from} -> To: ${deposit.deposit.to}`);
209
- console.log(`Token: ${deposit.deposit.token}`);
210
- console.log(`Amount: ${deposit.deposit.amount}`);
211
- console.log(`Block: ${deposit.deposit.blockNumber}, Time: ${deposit.deposit.timestamp}`);
212
- console.log(`Claimed: ${deposit.isClaimed}`);
213
- if (deposit.isClaimed && deposit.claim) {
214
- console.log(`Claim Chain: ${deposit.claim.chain}`);
215
- console.log(`Claimed by: ${deposit.claim.claimer}`);
216
- console.log(`Claimed tx: ${deposit.claim.txHash}`);
217
- }
126
+ // Step 2: Get aggregated signatures from your indexer/API
127
+ // (Implementation depends on your backend)
128
+
129
+ // Step 3: Claim on ETH
130
+ const claimTx = client.claim({
131
+ claimData: {
132
+ ethTokenAddress: '0xETH_TOKEN_ADDRESS', // Token on ETH (different from Pod)
133
+ amount: ethers.parseEther('1'),
134
+ to: '0x...', // Recipient
135
+ committeeEpoch: 0, // Hardcoded for now
136
+ aggregatedSignatures: '0x...', // 65-byte signatures (r,s,v) concatenated
137
+ depositTxHash: tx.hash // Deposit TX hash from Pod
138
+ },
139
+ from: '0x...'
218
140
  });
219
- ```
220
-
221
- #### Get Deposits Received by Address
222
-
223
- ```typescript
224
- // Get all deposits where the address is the recipient
225
- const deposits = await trackerClient.getDepositsReceivedBy(
226
- '0x...', // recipient address
227
- 0 // starting block (optional)
228
- );
229
- ```
230
-
231
- #### Get All Deposits (Sent + Received)
232
-
233
- ```typescript
234
- // Get all deposits related to an address (both sent and received)
235
- const allDeposits = await trackerClient.getAllDepositsFor(
236
- '0x...', // address
237
- 0 // starting block (optional)
238
- );
239
141
 
240
- // Deposits include a 'type' field indicating SENT or RECEIVED
241
- allDeposits.forEach(deposit => {
242
- console.log(`Type: ${deposit.type}`); // 'sent' or 'received'
243
- console.log(`Request ID: ${deposit.requestId}`);
244
- });
142
+ const claim = await ethSigner.sendTransaction(claimTx);
245
143
  ```
246
144
 
247
- #### Check if Deposit Can Be Claimed
145
+ ### Tracking Deposits
248
146
 
249
147
  ```typescript
250
- // Check if a deposit has been finalized and can be claimed
251
- const canClaim = await trackerClient.canBeClaimed(deposit);
252
-
253
- if (canClaim) {
254
- console.log('Deposit is ready to be claimed!');
148
+ const tracker = new PodBridgeTrackerClient(trackerConfig);
149
+
150
+ // Get all deposits for an address
151
+ const deposits = await tracker.getAllDepositsFor('0x...');
152
+
153
+ for (const deposit of deposits) {
154
+ console.log('Request ID:', deposit.requestId);
155
+ console.log('Direction:', deposit.deposit.chain === 'sourceChain' ? 'ETH → Pod' : 'Pod → ETH');
156
+ console.log('From:', deposit.deposit.depositor);
157
+ console.log('To:', deposit.deposit.destination);
158
+ console.log('Token:', deposit.deposit.token);
159
+ console.log('Amount:', deposit.deposit.amount);
160
+ console.log('Claimed:', deposit.isClaimed);
161
+ console.log('Claimable:', deposit.isClaimable);
255
162
  }
256
- ```
257
163
 
258
- #### Batch Check Processed Requests
164
+ // Get deposits sent by address
165
+ const sent = await tracker.getDepositsSentBy('0x...');
259
166
 
260
- ```typescript
261
- // Check multiple deposits at once
262
- const deposits = await trackerClient.getDepositsSentBy('0x...');
263
- const processedStatuses = await trackerClient.areRequestsProcessed(deposits);
167
+ // Get deposits received by address
168
+ const received = await tracker.getDepositsReceivedBy('0x...');
264
169
 
265
- processedStatuses.forEach((isProcessed, index) => {
266
- console.log(`Deposit ${deposits[index].requestId} processed: ${isProcessed}`);
267
- });
170
+ // Check if deposit can be claimed (Pod ETH only)
171
+ const canClaim = await tracker.canBeClaimed(deposit);
172
+
173
+ // Batch check processed status
174
+ const statuses = await tracker.areRequestsProcessed(deposits);
268
175
  ```
269
176
 
270
177
  ## API Reference
271
178
 
272
179
  ### SourceChainToPodActionClient
273
180
 
274
- Handles transactions for Source Chain POD direction.
275
-
276
- #### Constructor
181
+ For ETH Pod deposits (auto-claim on Pod).
277
182
 
278
183
  ```typescript
279
- new SourceChainToPodActionClient(config: PodBridgeActionsClientConfig)
184
+ // Deposit ERC20 tokens
185
+ deposit(args: {
186
+ token: string;
187
+ amount: string | bigint;
188
+ destinationWalletAddress: string;
189
+ from?: string;
190
+ }): UnsignedTransaction
280
191
  ```
281
192
 
282
- #### Methods
283
-
284
- - **`depositToken(args)`**: Create unsigned transaction for ERC20 deposit on Source chain
285
- - `token`: Token address
286
- - `amount`: Amount in wei (string | bigint)
287
- - `destinationWalletAddress`: Recipient address on POD
288
- - `from?`: Optional sender address
289
-
290
- - **`depositNative(args)`**: Create unsigned transaction for native token deposit on Source chain
291
- - `amount`: Amount in wei (string | bigint)
292
- - `destinationWalletAddress`: Recipient address on POD
293
- - `from?`: Optional sender address
294
-
295
- - **`claimWithBlockNumber(args)`**: Create unsigned transaction for claiming on POD with block number
296
- - `id`: Request ID
297
- - `token`: Token address
298
- - `blockNumber`: Block number of deposit on Source chain
299
- - `from?`: Optional claimer address
300
-
301
- - **`claimNativeWithBlockNumber(args)`**: Create unsigned transaction for claiming native tokens on POD with block number
302
- - `id`: Request ID
303
- - `blockNumber`: Block number of deposit on Source chain
304
- - `from?`: Optional claimer address
305
-
306
193
  ### PodToSourceChainActionClient
307
194
 
308
- Handles transactions for POD Source Chain direction.
309
-
310
- #### Constructor
195
+ For Pod ETH deposits and claims.
311
196
 
312
197
  ```typescript
313
- new PodToSourceChainActionClient(config: PodBridgeActionsClientConfig)
314
- ```
315
-
316
- #### Methods
317
-
318
- - **`depositToken(args)`**: Create unsigned transaction for ERC20 deposit on POD
319
- - `token`: Token address
320
- - `amount`: Amount in wei (string | bigint)
321
- - `destinationWalletAddress`: Recipient address on Source chain
322
- - `from?`: Optional sender address
323
-
324
- - **`depositNative(args)`**: Create unsigned transaction for native token deposit on POD
325
- - `amount`: Amount in wei (string | bigint)
326
- - `destinationWalletAddress`: Recipient address on Source chain
327
- - `from?`: Optional sender address
328
-
329
- - **`claimWithCertificate(args)`**: Create unsigned transaction for claiming on Source chain with certificate
330
- - `certifiedLog`: Certified log from POD certificate system
331
- - `from?`: Optional claimer address
198
+ // Deposit ERC20 tokens on Pod
199
+ deposit(args: {
200
+ token: string;
201
+ amount: string | bigint;
202
+ destinationWalletAddress: string;
203
+ from?: string;
204
+ }): UnsignedTransaction
332
205
 
333
- - **`claimNativeWithCertificate(args)`**: Create unsigned transaction for claiming native tokens on Source chain with certificate
334
- - `certifiedLog`: Certified log from POD certificate system
335
- - `from?`: Optional claimer address
206
+ // Claim on ETH with aggregated signatures
207
+ claim(args: {
208
+ claimData: ClaimProofData;
209
+ from?: string;
210
+ }): UnsignedTransaction
211
+ ```
336
212
 
337
213
  ### PodBridgeTrackerClient
338
214
 
339
- #### Methods
340
-
341
- - **`getDepositsSentBy(address, fromBlock?)`**: Get deposits sent by address
342
- - Returns: `Promise<BridgeRequest[]>`
343
-
344
- - **`getDepositsReceivedBy(address, fromBlock?)`**: Get deposits received by address
345
- - Returns: `Promise<BridgeRequest[]>`
346
-
347
- - **`getAllDepositsFor(address, fromBlock?)`**: Get all deposits for address (sent + received)
348
- - Returns: `Promise<BridgeRequestWithType[]>`
349
-
350
- - **`canBeClaimed(deposit)`**: Check if deposit can be claimed
351
- - Returns: `Promise<boolean>`
352
-
353
- - **`areRequestsProcessed(deposits)`**: Batch check if requests are processed
354
- - Returns: `Promise<boolean[]>`
355
-
356
- ## ABIs
357
-
358
- The SDK exports two separate ABIs for the different bridge contract types:
359
-
360
- ### POD_BRIDGE_ABI
361
-
362
- Used for the **BridgeMintBurn** contract on POD Chain:
363
- - Deposit functions: `deposit()`, `depositNative()`
364
- - Claim functions: `claim(uint256 id, address token, uint256 blockNumber)`, `claimNative(uint256 id, uint256 blockNumber)`
365
- - Claims use block number verification via POD precompiles
366
- - Events: `Deposit`, `DepositNative`, `Claim`, `ClaimNative`
367
-
368
- ### SOURCE_CHAIN_BRIDGE_ABI
369
-
370
- Used for the **BridgeDepositWithdraw** contract on Source Chain (e.g., Sepolia):
371
- - Deposit functions: `deposit()`, `depositNative()`
372
- - Claim functions: `claim(CertifiedLog)`, `claimNative(CertifiedLog)`
373
- - Claims use POD certificate system with attestations and merkle proofs
374
- - Events: `Deposit`, `DepositNative`, `Claim`, `ClaimNative`
375
-
376
- Both are exported from the package and can be used with ethers.js:
377
-
378
215
  ```typescript
379
- import { POD_BRIDGE_ABI, SOURCE_CHAIN_BRIDGE_ABI } from '@tapforce/pod-bridge-sdk';
380
- import { ethers } from 'ethers';
381
-
382
- // Create contract interface
383
- const podBridge = new ethers.Contract(podAddress, POD_BRIDGE_ABI, provider);
384
- const sourceChainBridge = new ethers.Contract(sepoliaAddress, SOURCE_CHAIN_BRIDGE_ABI, provider);
216
+ getDepositsSentBy(address: string, fromBlock?: number): Promise<BridgeRequest[]>
217
+ getDepositsReceivedBy(address: string, fromBlock?: number): Promise<BridgeRequest[]>
218
+ getAllDepositsFor(address: string, fromBlock?: number): Promise<BridgeRequestWithType[]>
219
+ canBeClaimed(deposit: BridgeRequest): Promise<boolean>
220
+ areRequestsProcessed(deposits: BridgeRequest[]): Promise<boolean[]>
385
221
  ```
386
222
 
387
223
  ## Types
@@ -389,264 +225,95 @@ const sourceChainBridge = new ethers.Contract(sepoliaAddress, SOURCE_CHAIN_BRIDG
389
225
  ### BridgeRequest
390
226
 
391
227
  ```typescript
392
- enum BridgeChain {
393
- SOURCE_CHAIN = 'sourceChain',
394
- POD = 'pod'
395
- }
396
-
397
228
  interface BridgeRequest {
398
229
  requestId: string;
399
230
 
400
- // Deposit information
401
231
  deposit: {
402
- chain: BridgeChain; // Which chain the deposit occurred on
232
+ chain: 'sourceChain' | 'pod';
403
233
  txHash: string;
404
- from: string; // Sender address
405
- to: string; // Recipient address on destination chain
406
- token: string; // Token address (or 0x0 for native)
407
- amount: string; // Amount in wei
234
+ depositor: string;
235
+ destination: string;
236
+ token: string;
237
+ amount: string;
408
238
  chainId: number;
409
- blockNumber: number; // Block number (for POD, this is essentially a timestamp)
410
- timestamp: number; // Unix timestamp
239
+ blockNumber: number;
240
+ timestamp: number;
411
241
  };
412
242
 
413
- // Claim information (optional, only if claimed)
414
243
  claim?: {
415
- chain: BridgeChain; // Which chain the claim occurred on
244
+ chain: 'sourceChain' | 'pod';
416
245
  txHash: string;
417
- claimer: string; // Address that claimed
246
+ claimer: string;
418
247
  chainId: number;
419
- blockNumber: number; // Block number (for POD, this is essentially a timestamp)
420
- timestamp: number; // Unix timestamp
248
+ blockNumber: number;
249
+ timestamp: number;
421
250
  };
422
251
 
423
- // Status
424
252
  isClaimed: boolean;
425
- isClaimable: boolean; // Can be claimed (finalized and not yet claimed)
253
+ isClaimable: boolean;
426
254
  }
427
255
  ```
428
256
 
429
- ### UnsignedTransaction
257
+ ### ClaimProofData
430
258
 
431
259
  ```typescript
432
- interface UnsignedTransaction {
433
- to: string;
434
- data: string;
435
- value: string;
436
- from?: string;
437
- chainId?: number;
438
- gasLimit?: bigint;
260
+ interface ClaimProofData {
261
+ ethTokenAddress: string; // Token address on ETH (different from Pod)
262
+ amount: string | bigint; // Same amount as deposited
263
+ to: string; // Recipient address
264
+ committeeEpoch: number; // Hardcoded to 0 for now
265
+ aggregatedSignatures: string; // Concatenated 65-byte ECDSA signatures (r,s,v)
266
+ depositTxHash: string; // Deposit TX hash from Pod
439
267
  }
440
268
  ```
441
269
 
442
- ### CertifiedLog
443
-
444
- ```typescript
445
- interface CertifiedLog {
446
- log: {
447
- addr: string;
448
- topics: string[];
449
- data: string;
450
- };
451
- log_index: bigint | string;
452
- certificate: {
453
- leaf: string;
454
- certified_receipt: {
455
- receipt_root: string;
456
- aggregate_signature: string;
457
- sorted_attestation_timestamps: bigint[] | string[];
458
- };
459
- proof: {
460
- path: string[];
461
- };
462
- };
463
- }
464
- ```
465
-
466
- ## Bridge Types
467
-
468
- The SDK supports two bridge implementations:
469
-
470
- 1. **BridgeDepositWithdraw** (Certificate-based) - Source Chain
471
- - Used for claiming on Source chain from POD deposits
472
- - Requires certified logs from POD's certificate system via indexer
473
- - Uses `SOURCE_CHAIN_BRIDGE_ABI`
474
- - Methods: `claimWithCertificate`, `claimNativeWithCertificate`
475
- - Certified logs include attestations, receipt root, aggregate signatures, and merkle proofs
476
-
477
- 2. **BridgeMintBurn** (Precompile-based) - POD Chain
478
- - Used for claiming on POD from Source chain deposits
479
- - Uses block number verification via POD precompiles
480
- - Uses `POD_BRIDGE_ABI`
481
- - Methods: `claimWithBlockNumber`, `claimNativeWithBlockNumber`
482
- - POD has instant finality (single-block architecture)
270
+ ### Signature Recovery Helpers
483
271
 
484
- ## Examples
485
-
486
- ### Complete Deposit and Claim Flow (Sepolia → POD)
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.
487
274
 
488
275
  ```typescript
489
- import { ethers } from 'ethers';
490
- import { SourceChainToPodActionClient, PodBridgeTrackerClient } from '@tapforce/pod-bridge-sdk';
491
-
492
- // Setup
493
- const trackerConfig = {
494
- sourceChain: {
495
- rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
496
- contractAddress: '0xSepoliaBridgeContract'
497
- },
498
- pod: {
499
- rpcUrl: 'https://rpc.v1.dev.pod.network',
500
- contractAddress: '0xPodBridgeContract'
501
- }
502
- };
503
-
504
- const actionConfig = {
505
- sourceChain: { contractAddress: '0xSepoliaBridgeContract' },
506
- pod: { contractAddress: '0xPodBridgeContract' }
507
- };
276
+ import {
277
+ extractAggregatedSignatures,
278
+ recoverSignature65B,
279
+ addressFromPublicKey
280
+ } from '@tapforce/pod-bridge-sdk';
508
281
 
509
- const actionClient = new SourceChainToPodActionClient(actionConfig);
510
- const trackerClient = new PodBridgeTrackerClient(trackerConfig);
511
- const sepoliaProvider = new ethers.JsonRpcProvider(trackerConfig.sourceChain.rpcUrl);
512
- const sepoliaSigner = new ethers.Wallet('PRIVATE_KEY', sepoliaProvider);
282
+ // Recommended: Extract signatures directly from Pod receipt
283
+ const podReceipt = await podProvider.send('eth_getTransactionReceipt', [depositTxHash]);
284
+ const aggregatedSignatures = extractAggregatedSignatures(podReceipt, msgHash);
513
285
 
514
- // Step 1: Deposit tokens on Sepolia
515
- const depositTx = actionClient.depositToken({
516
- token: '0xTokenAddress',
517
- amount: ethers.parseEther('10').toString(),
518
- destinationWalletAddress: await sepoliaSigner.getAddress()
519
- });
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)
292
+ );
520
293
 
521
- const tx = await sepoliaSigner.sendTransaction(depositTx);
522
- await tx.wait();
523
- console.log(`Deposit tx: ${tx.hash}`);
524
-
525
- // Step 2: Track deposit
526
- const deposits = await trackerClient.getDepositsSentBy(await sepoliaSigner.getAddress());
527
- const latestDeposit = deposits[0];
528
-
529
- console.log(`Request ID: ${latestDeposit.requestId}`);
530
- console.log(`Amount: ${ethers.formatEther(latestDeposit.deposit.amount)}`);
531
-
532
- // Step 3: Wait for finality and claim on POD
533
- const canClaim = await trackerClient.canBeClaimed(latestDeposit);
534
- if (canClaim) {
535
- const claimTx = actionClient.claimWithBlockNumber({
536
- id: latestDeposit.requestId,
537
- token: latestDeposit.deposit.token,
538
- blockNumber: latestDeposit.deposit.blockNumber
539
- });
540
-
541
- // Submit claim on POD
542
- const podProvider = new ethers.JsonRpcProvider(trackerConfig.pod.rpcUrl);
543
- const podSigner = new ethers.Wallet('PRIVATE_KEY', podProvider);
544
- const claim = await podSigner.sendTransaction(claimTx);
545
- await claim.wait();
546
- console.log(`Claimed on POD: ${claim.hash}`);
547
- }
294
+ // Utility: Derive address from public key
295
+ const address = addressFromPublicKey(publicKey);
548
296
  ```
549
297
 
550
- ### Complete Deposit and Claim Flow (POD → Sepolia)
551
-
552
- ```typescript
553
- import { ethers } from 'ethers';
554
- import { PodToSourceChainActionClient, PodBridgeTrackerClient } from '@tapforce/pod-bridge-sdk';
555
- import { PodIndexerHttpClient } from '@tapforce/pod-indexer-sdk';
556
-
557
- // Setup
558
- const trackerConfig = {
559
- sourceChain: {
560
- rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
561
- contractAddress: '0xSepoliaBridgeContract'
562
- },
563
- pod: {
564
- rpcUrl: 'https://rpc.v1.dev.pod.network',
565
- contractAddress: '0xPodBridgeContract'
566
- }
567
- };
568
-
569
- const actionConfig = {
570
- sourceChain: { contractAddress: '0xSepoliaBridgeContract' },
571
- pod: { contractAddress: '0xPodBridgeContract' }
572
- };
573
-
574
- const actionClient = new PodToSourceChainActionClient(actionConfig);
575
- const trackerClient = new PodBridgeTrackerClient(trackerConfig);
576
- const podProvider = new ethers.JsonRpcProvider(trackerConfig.pod.rpcUrl);
577
- const podSigner = new ethers.Wallet('PRIVATE_KEY', podProvider);
578
-
579
- // Initialize indexer client for getting certified logs
580
- const indexerClient = new PodIndexerHttpClient({
581
- apiUrl: 'YOUR_INDEXER_API_URL',
582
- apiKey: 'YOUR_INDEXER_API_KEY'
583
- });
584
-
585
- // Step 1: Deposit tokens on POD
586
- const depositTx = actionClient.depositToken({
587
- token: '0xTokenAddress',
588
- amount: ethers.parseEther('10').toString(),
589
- destinationWalletAddress: await podSigner.getAddress()
590
- });
591
-
592
- const tx = await podSigner.sendTransaction(depositTx);
593
- const receipt = await tx.wait();
594
- console.log(`Deposit tx on POD: ${tx.hash}`);
595
-
596
- // Step 2: Get certified log from POD transaction using indexer
597
- const certifiedLogResponse = await indexerClient.getBridgeCertifiedLog({
598
- transactionHash: tx.hash,
599
- bridgeContractAddress: trackerConfig.pod.contractAddress,
600
- rpcUrl: trackerConfig.pod.rpcUrl
601
- });
298
+ ## Events
602
299
 
603
- const certifiedLog = certifiedLogResponse.result;
604
- if (!certifiedLog) {
605
- throw new Error('Certified log not found');
606
- }
607
- console.log('Got certified log with attestations');
300
+ The bridge contract emits:
608
301
 
609
- // Step 3: Claim on Sepolia using certified log
610
- const claimTx = actionClient.claimWithCertificate({
611
- certifiedLog,
612
- from: await podSigner.getAddress()
613
- });
614
-
615
- const sepoliaProvider = new ethers.JsonRpcProvider(trackerConfig.sourceChain.rpcUrl);
616
- const sepoliaSigner = new ethers.Wallet('PRIVATE_KEY', sepoliaProvider);
617
- const claim = await sepoliaSigner.sendTransaction(claimTx);
618
- await claim.wait();
619
- console.log(`Claimed on Sepolia: ${claim.hash}`);
302
+ ```solidity
303
+ event Deposit(bytes32 indexed id, address indexed from, address indexed to, address token, uint256 amount);
304
+ event Claim(bytes32 indexed id, address indexed to, address token, uint256 amount);
620
305
  ```
621
306
 
622
307
  ## Development
623
308
 
624
- ### Build
625
-
626
- ```bash
627
- npm run build
628
- ```
629
-
630
- ### Clean
631
-
632
309
  ```bash
633
- npm run clean
310
+ npm run build # Build
311
+ npm run clean # Clean
634
312
  ```
635
313
 
636
314
  ## Dependencies
637
315
 
638
- - **ethers**: ^6.15.0 - Ethereum library for interacting with contracts
639
- - **typescript**: ^5.8.3 - TypeScript support
640
-
641
- ### External Dependencies for Claims
642
-
643
- To claim tokens on Source Chain from POD deposits, you'll need:
644
-
645
- - **@tapforce/pod-indexer-sdk**: For retrieving certified logs from POD transactions
646
-
647
- ```bash
648
- npm install @tapforce/pod-indexer-sdk
649
- ```
316
+ - **ethers**: ^6.15.0
650
317
 
651
318
  ## License
652
319
 
@@ -654,13 +321,5 @@ ISC
654
321
 
655
322
  ## Repository
656
323
 
657
- - **Homepage**: [https://github.com/tapforce/pod-ts-bridge-sdk](https://github.com/tapforce/pod-ts-bridge-sdk)
658
- - **Issues**: [https://github.com/tapforce/pod-ts-bridge-sdk/issues](https://github.com/tapforce/pod-ts-bridge-sdk/issues)
659
-
660
- ## Contributing
661
-
662
- Contributions are welcome! Please open an issue or submit a pull request.
663
-
664
- ## Author
665
-
666
- Tapforce
324
+ - [GitHub](https://github.com/tapforce/pod-ts-bridge-sdk)
325
+ - [Issues](https://github.com/tapforce/pod-ts-bridge-sdk/issues)