@tapforce/pod-bridge-sdk 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,11 +46,11 @@ Create a configuration object with source and destination chain details:
46
46
 
47
47
  ```typescript
48
48
  const config: PodBridgeConfig = {
49
- source: {
49
+ bridged: {
50
50
  rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
51
51
  contractAddress: '0x...' // Source chain bridge contract
52
52
  },
53
- destination: {
53
+ pod: {
54
54
  rpcUrl: 'https://pod-rpc.example.com',
55
55
  contractAddress: '0x...' // Destination chain bridge contract
56
56
  }
@@ -98,32 +98,24 @@ const unsignedTx = actionClient.depositNative({
98
98
 
99
99
  #### Claim Tokens (with Certificate) - Sepolia from POD
100
100
 
101
- For claiming on Sepolia from POD deposits, use certificate-based claims:
101
+ For claiming on Sepolia from POD deposits, use certificate-based claims. First, get the certified log from the POD deposit transaction:
102
102
 
103
103
  ```typescript
104
- // Obtain certified log from POD certificate system
105
- const certifiedLog = {
106
- log: {
107
- addr: '0x...',
108
- topics: ['0x...'],
109
- data: '0x...'
110
- },
111
- sigs: [
112
- { r: '0x...', s: '0x...', v: 27 }
113
- ]
114
- };
104
+ // Get certified log from POD deposit transaction
105
+ const depositTxHash = '0x...'; // Transaction hash of deposit on POD
106
+ const certifiedLog = await trackerClient.getDepositCertifiedLog(depositTxHash);
115
107
 
116
108
  // Create unsigned claim transaction for ERC20
117
109
  const unsignedTx = actionClient.claimWithCertificate({
118
110
  certifiedLog,
119
- contractAddress: config.destination.contractAddress,
111
+ contractAddress: config.bridged.contractAddress, // Sepolia bridge contract
120
112
  from: '0x...' // Optional: claimer address
121
113
  });
122
114
 
123
115
  // For native tokens
124
116
  const unsignedNativeTx = actionClient.claimNativeWithCertificate({
125
117
  certifiedLog,
126
- contractAddress: config.destination.contractAddress,
118
+ contractAddress: config.bridged.contractAddress,
127
119
  from: '0x...'
128
120
  });
129
121
  ```
@@ -291,6 +283,10 @@ processedStatuses.forEach((isProcessed, index) => {
291
283
  - **`areRequestsProcessed(deposits)`**: Batch check if requests are processed
292
284
  - Returns: `Promise<boolean[]>`
293
285
 
286
+ - **`getDepositCertifiedLog(txHash)`**: Get certified log for claiming on Sepolia from POD deposit
287
+ - `txHash`: Transaction hash of deposit on POD
288
+ - Returns: `Promise<CertifiedLog>` with attestations and merkle proof
289
+
294
290
  ## Types
295
291
 
296
292
  ### BridgeRequest
@@ -334,11 +330,18 @@ interface CertifiedLog {
334
330
  topics: string[];
335
331
  data: string;
336
332
  };
337
- sigs: Array<{
338
- r: string;
339
- s: string;
340
- v: number;
341
- }>;
333
+ logIndex: bigint | string;
334
+ certificate: {
335
+ leaf: string;
336
+ certifiedReceipt: {
337
+ receiptRoot: string;
338
+ aggregateSignature: string;
339
+ sortedAttestationTimestamps: bigint[] | string[];
340
+ };
341
+ proof: {
342
+ path: string[];
343
+ };
344
+ };
342
345
  }
343
346
  ```
344
347
 
@@ -358,7 +361,7 @@ The SDK supports two bridge implementations:
358
361
 
359
362
  ## Examples
360
363
 
361
- ### Complete Deposit and Claim Flow
364
+ ### Complete Deposit and Claim Flow (Sepolia → POD)
362
365
 
363
366
  ```typescript
364
367
  import { ethers } from 'ethers';
@@ -366,57 +369,111 @@ import { PodBridgeActionClient, PodBridgeTrackerClient } from '@tapforce/pod-bri
366
369
 
367
370
  // Setup
368
371
  const config = {
369
- source: {
372
+ bridged: {
370
373
  rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
371
- contractAddress: '0xSourceBridgeContract'
374
+ contractAddress: '0xSepoliaBridgeContract'
372
375
  },
373
- destination: {
374
- rpcUrl: 'https://pod-rpc.example.com',
375
- contractAddress: '0xDestBridgeContract'
376
+ pod: {
377
+ rpcUrl: 'https://rpc.v1.dev.pod.network',
378
+ contractAddress: '0xPodBridgeContract'
376
379
  }
377
380
  };
378
381
 
379
382
  const actionClient = new PodBridgeActionClient();
380
383
  const trackerClient = new PodBridgeTrackerClient(config);
381
- const provider = new ethers.JsonRpcProvider(config.source.rpcUrl);
382
- const signer = new ethers.Wallet('PRIVATE_KEY', provider);
384
+ const sepoliaProvider = new ethers.JsonRpcProvider(config.bridged.rpcUrl);
385
+ const sepoliaSigner = new ethers.Wallet('PRIVATE_KEY', sepoliaProvider);
383
386
 
384
- // Step 1: Deposit tokens
387
+ // Step 1: Deposit tokens on Sepolia
385
388
  const depositTx = actionClient.depositToken({
386
389
  token: '0xTokenAddress',
387
390
  amount: ethers.parseEther('10').toString(),
388
- recipient: await signer.getAddress(),
389
- contractAddress: config.source.contractAddress
391
+ destinationWalletAddress: await sepoliaSigner.getAddress(),
392
+ contractAddress: config.bridged.contractAddress
390
393
  });
391
394
 
392
- const tx = await signer.sendTransaction(depositTx);
395
+ const tx = await sepoliaSigner.sendTransaction(depositTx);
393
396
  await tx.wait();
394
397
  console.log(`Deposit tx: ${tx.hash}`);
395
398
 
396
399
  // Step 2: Track deposit
397
- const deposits = await trackerClient.getDepositsSentBy(await signer.getAddress());
400
+ const deposits = await trackerClient.getDepositsSentBy(await sepoliaSigner.getAddress());
398
401
  const latestDeposit = deposits[0];
399
402
 
400
403
  console.log(`Request ID: ${latestDeposit.requestId}`);
401
404
  console.log(`Amount: ${ethers.formatEther(latestDeposit.amount)}`);
402
405
 
403
- // Step 3: Wait for finality and claim
406
+ // Step 3: Wait for finality and claim on POD
404
407
  const canClaim = await trackerClient.canBeClaimed(latestDeposit);
405
408
  if (canClaim) {
406
409
  const claimTx = actionClient.claimWithBlockNumber({
407
410
  id: latestDeposit.requestId,
408
411
  token: latestDeposit.token,
409
412
  blockNumber: latestDeposit.blockNumber,
410
- contractAddress: config.destination.contractAddress
413
+ contractAddress: config.pod.contractAddress
411
414
  });
412
415
 
413
- // Submit claim on destination chain
414
- // const destSigner = new ethers.Wallet('PRIVATE_KEY', destProvider);
415
- // const claim = await destSigner.sendTransaction(claimTx);
416
- // await claim.wait();
416
+ // Submit claim on POD
417
+ const podProvider = new ethers.JsonRpcProvider(config.pod.rpcUrl);
418
+ const podSigner = new ethers.Wallet('PRIVATE_KEY', podProvider);
419
+ const claim = await podSigner.sendTransaction(claimTx);
420
+ await claim.wait();
421
+ console.log(`Claimed on POD: ${claim.hash}`);
417
422
  }
418
423
  ```
419
424
 
425
+ ### Complete Deposit and Claim Flow (POD → Sepolia)
426
+
427
+ ```typescript
428
+ import { ethers } from 'ethers';
429
+ import { PodBridgeActionClient, PodBridgeTrackerClient } from '@tapforce/pod-bridge-sdk';
430
+
431
+ // Setup
432
+ const config = {
433
+ bridged: {
434
+ rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
435
+ contractAddress: '0xSepoliaBridgeContract'
436
+ },
437
+ pod: {
438
+ rpcUrl: 'https://rpc.v1.dev.pod.network',
439
+ contractAddress: '0xPodBridgeContract'
440
+ }
441
+ };
442
+
443
+ const actionClient = new PodBridgeActionClient();
444
+ const trackerClient = new PodBridgeTrackerClient(config);
445
+ const podProvider = new ethers.JsonRpcProvider(config.pod.rpcUrl);
446
+ const podSigner = new ethers.Wallet('PRIVATE_KEY', podProvider);
447
+
448
+ // Step 1: Deposit tokens on POD
449
+ const depositTx = actionClient.depositToken({
450
+ token: '0xTokenAddress',
451
+ amount: ethers.parseEther('10').toString(),
452
+ destinationWalletAddress: await podSigner.getAddress(),
453
+ contractAddress: config.pod.contractAddress
454
+ });
455
+
456
+ const tx = await podSigner.sendTransaction(depositTx);
457
+ const receipt = await tx.wait();
458
+ console.log(`Deposit tx on POD: ${tx.hash}`);
459
+
460
+ // Step 2: Get certified log from POD transaction
461
+ const certifiedLog = await trackerClient.getDepositCertifiedLog(tx.hash);
462
+ console.log('Got certified log with attestations');
463
+
464
+ // Step 3: Claim on Sepolia using certified log
465
+ const claimTx = actionClient.claimWithCertificate({
466
+ certifiedLog,
467
+ contractAddress: config.bridged.contractAddress
468
+ });
469
+
470
+ const sepoliaProvider = new ethers.JsonRpcProvider(config.bridged.rpcUrl);
471
+ const sepoliaSigner = new ethers.Wallet('PRIVATE_KEY', sepoliaProvider);
472
+ const claim = await sepoliaSigner.sendTransaction(claimTx);
473
+ await claim.wait();
474
+ console.log(`Claimed on Sepolia: ${claim.hash}`);
475
+ ```
476
+
420
477
  ## Development
421
478
 
422
479
  ### Build
@@ -6,7 +6,7 @@ export declare class PodBridgeActionClient {
6
6
  * Create unsigned transaction for depositing ERC20 tokens
7
7
  * @param args.token Token address to deposit
8
8
  * @param args.amount Amount to deposit (in wei)
9
- * @param args.recipient Recipient address on destination chain
9
+ * @param args.destinationWalletAddress Recipient address on destination chain
10
10
  * @param args.contractAddress Bridge contract address (source chain)
11
11
  * @param args.from Optional sender address
12
12
  * @returns Unsigned transaction template with encoded deposit call
@@ -14,21 +14,21 @@ export declare class PodBridgeActionClient {
14
14
  depositToken(args: {
15
15
  token: string;
16
16
  amount: string | bigint;
17
- recipient: string;
17
+ destinationWalletAddress: string;
18
18
  contractAddress: string;
19
19
  from?: string;
20
20
  }): UnsignedTransaction;
21
21
  /**
22
22
  * Create unsigned transaction for depositing native tokens (ETH)
23
23
  * @param args.amount Amount to deposit (in wei) - also set as transaction value
24
- * @param args.recipient Recipient address on destination chain
24
+ * @param args.destinationWalletAddress Recipient address on destination chain
25
25
  * @param args.contractAddress Bridge contract address (source chain)
26
26
  * @param args.from Optional sender address
27
27
  * @returns Unsigned transaction template with encoded depositNative call
28
28
  */
29
29
  depositNative(args: {
30
30
  amount: string | bigint;
31
- recipient: string;
31
+ destinationWalletAddress: string;
32
32
  contractAddress: string;
33
33
  from?: string;
34
34
  }): UnsignedTransaction;
@@ -11,7 +11,7 @@ class PodBridgeActionClient {
11
11
  * Create unsigned transaction for depositing ERC20 tokens
12
12
  * @param args.token Token address to deposit
13
13
  * @param args.amount Amount to deposit (in wei)
14
- * @param args.recipient Recipient address on destination chain
14
+ * @param args.destinationWalletAddress Recipient address on destination chain
15
15
  * @param args.contractAddress Bridge contract address (source chain)
16
16
  * @param args.from Optional sender address
17
17
  * @returns Unsigned transaction template with encoded deposit call
@@ -20,7 +20,7 @@ class PodBridgeActionClient {
20
20
  const data = this.iface.encodeFunctionData('deposit', [
21
21
  args.token,
22
22
  args.amount,
23
- args.recipient
23
+ args.destinationWalletAddress
24
24
  ]);
25
25
  return {
26
26
  to: args.contractAddress,
@@ -32,13 +32,13 @@ class PodBridgeActionClient {
32
32
  /**
33
33
  * Create unsigned transaction for depositing native tokens (ETH)
34
34
  * @param args.amount Amount to deposit (in wei) - also set as transaction value
35
- * @param args.recipient Recipient address on destination chain
35
+ * @param args.destinationWalletAddress Recipient address on destination chain
36
36
  * @param args.contractAddress Bridge contract address (source chain)
37
37
  * @param args.from Optional sender address
38
38
  * @returns Unsigned transaction template with encoded depositNative call
39
39
  */
40
40
  depositNative(args) {
41
- const data = this.iface.encodeFunctionData('depositNative', [args.recipient]);
41
+ const data = this.iface.encodeFunctionData('depositNative', [args.destinationWalletAddress]);
42
42
  return {
43
43
  to: args.contractAddress,
44
44
  data,
@@ -1,10 +1,10 @@
1
- import { PodBridgeConfig, BridgeRequest, BridgeRequestWithType } from "../../libs/types/pod-bridge.types";
1
+ import { PodBridgeConfig, BridgeRequest, BridgeRequestWithType, CertifiedLog } from "../../libs/types/pod-bridge.types";
2
2
  export declare class PodBridgeTrackerClient {
3
3
  private readonly config;
4
- private readonly sourceProvider;
5
- private readonly destProvider;
6
- private readonly sourceBridge;
7
- private readonly destBridge;
4
+ private readonly bridgedProvider;
5
+ private readonly podProvider;
6
+ private readonly bridgedBridge;
7
+ private readonly podBridge;
8
8
  private readonly iface;
9
9
  constructor(config: PodBridgeConfig);
10
10
  /**
@@ -54,4 +54,36 @@ export declare class PodBridgeTrackerClient {
54
54
  * @returns Request hash
55
55
  */
56
56
  private computeRequestHash;
57
+ /**
58
+ * Get certified log for a deposit transaction from POD
59
+ * This is required to claim tokens on Sepolia from POD deposits
60
+ * @param txHash Transaction hash of the deposit on POD
61
+ * @returns Certified log with attestations and merkle proof
62
+ */
63
+ getDepositCertifiedLog(txHash: string): Promise<CertifiedLog>;
64
+ /**
65
+ * Compute receipt root hash
66
+ * @private
67
+ */
68
+ private computeReceiptRoot;
69
+ /**
70
+ * Aggregate signatures from attestations
71
+ * @private
72
+ */
73
+ private aggregateSignatures;
74
+ /**
75
+ * Hash a log entry
76
+ * @private
77
+ */
78
+ private hashLog;
79
+ /**
80
+ * Hash a leaf for merkle tree
81
+ * @private
82
+ */
83
+ private hashLeaf;
84
+ /**
85
+ * Generate merkle proof for a log at given index
86
+ * @private
87
+ */
88
+ private generateMerkleProof;
57
89
  }
@@ -8,11 +8,11 @@ class PodBridgeTrackerClient {
8
8
  constructor(config) {
9
9
  this.config = config;
10
10
  // Initialize providers
11
- this.sourceProvider = new ethers_1.ethers.JsonRpcProvider(config.source.rpcUrl);
12
- this.destProvider = new ethers_1.ethers.JsonRpcProvider(config.destination.rpcUrl);
11
+ this.bridgedProvider = new ethers_1.ethers.JsonRpcProvider(config.bridged.rpcUrl);
12
+ this.podProvider = new ethers_1.ethers.JsonRpcProvider(config.pod.rpcUrl);
13
13
  // Initialize contract instances
14
- this.sourceBridge = new ethers_1.Contract(config.source.contractAddress, bridge_abi_1.POD_BRIDGE_ABI, this.sourceProvider);
15
- this.destBridge = new ethers_1.Contract(config.destination.contractAddress, bridge_abi_1.POD_BRIDGE_ABI, this.destProvider);
14
+ this.bridgedBridge = new ethers_1.Contract(config.bridged.contractAddress, bridge_abi_1.POD_BRIDGE_ABI, this.bridgedProvider);
15
+ this.podBridge = new ethers_1.Contract(config.pod.contractAddress, bridge_abi_1.POD_BRIDGE_ABI, this.podProvider);
16
16
  // Interface for parsing logs
17
17
  this.iface = new ethers_1.Interface(bridge_abi_1.POD_BRIDGE_ABI);
18
18
  }
@@ -23,24 +23,23 @@ class PodBridgeTrackerClient {
23
23
  * @returns Array of bridge requests sent by this address
24
24
  */
25
25
  async getDepositsSentBy(address, fromBlock = 0) {
26
- console.log(`Fetching deposits sent by ${address}...`);
27
26
  const deposits = [];
28
- const currentBlock = await this.sourceProvider.getBlockNumber();
27
+ const currentBlock = await this.bridgedProvider.getBlockNumber();
29
28
  const BLOCK_BATCH_SIZE = 10000;
30
29
  for (let start = fromBlock; start <= currentBlock; start += BLOCK_BATCH_SIZE) {
31
30
  const end = Math.min(start + BLOCK_BATCH_SIZE - 1, currentBlock);
32
31
  // With improved events, we can filter by 'from' directly!
33
- const depositFilter = this.sourceBridge.filters.Deposit(null, // id
32
+ const depositFilter = this.bridgedBridge.filters.Deposit(null, // id
34
33
  address, // from (indexed) - Filter by sender!
35
34
  null // to
36
35
  );
37
- const depositNativeFilter = this.sourceBridge.filters.DepositNative(null, // id
36
+ const depositNativeFilter = this.bridgedBridge.filters.DepositNative(null, // id
38
37
  address, // from (indexed) - Filter by sender!
39
38
  null // to
40
39
  );
41
40
  const [depositLogs, depositNativeLogs] = await Promise.all([
42
- this.sourceBridge.queryFilter(depositFilter, start, end),
43
- this.sourceBridge.queryFilter(depositNativeFilter, start, end)
41
+ this.bridgedBridge.queryFilter(depositFilter, start, end),
42
+ this.bridgedBridge.queryFilter(depositNativeFilter, start, end)
44
43
  ]);
45
44
  // Process Deposit events
46
45
  for (const log of depositLogs) {
@@ -94,24 +93,23 @@ class PodBridgeTrackerClient {
94
93
  * @returns Array of bridge requests sent to this address
95
94
  */
96
95
  async getDepositsReceivedBy(address, fromBlock = 0) {
97
- console.log(`Fetching deposits received by ${address}...`);
98
96
  const deposits = [];
99
- const currentBlock = await this.sourceProvider.getBlockNumber();
97
+ const currentBlock = await this.bridgedProvider.getBlockNumber();
100
98
  const BLOCK_BATCH_SIZE = 10000;
101
99
  for (let start = fromBlock; start <= currentBlock; start += BLOCK_BATCH_SIZE) {
102
100
  const end = Math.min(start + BLOCK_BATCH_SIZE - 1, currentBlock);
103
101
  // With improved events, we can filter by 'to' directly!
104
- const depositFilter = this.sourceBridge.filters.Deposit(null, // id
102
+ const depositFilter = this.bridgedBridge.filters.Deposit(null, // id
105
103
  null, // from
106
104
  address // to (indexed) - Filter by recipient!
107
105
  );
108
- const depositNativeFilter = this.sourceBridge.filters.DepositNative(null, // id
106
+ const depositNativeFilter = this.bridgedBridge.filters.DepositNative(null, // id
109
107
  null, // from
110
108
  address // to (indexed) - Filter by recipient!
111
109
  );
112
110
  const [depositLogs, depositNativeLogs] = await Promise.all([
113
- this.sourceBridge.queryFilter(depositFilter, start, end),
114
- this.sourceBridge.queryFilter(depositNativeFilter, start, end)
111
+ this.bridgedBridge.queryFilter(depositFilter, start, end),
112
+ this.bridgedBridge.queryFilter(depositNativeFilter, start, end)
115
113
  ]);
116
114
  // Process Deposit events
117
115
  for (const log of depositLogs) {
@@ -165,7 +163,6 @@ class PodBridgeTrackerClient {
165
163
  * @returns Array of bridge requests with type indicator
166
164
  */
167
165
  async getAllDepositsFor(address, fromBlock = 0) {
168
- console.log(`Fetching all deposits for ${address}...`);
169
166
  // Fetch both sent and received in parallel
170
167
  const [sent, received] = await Promise.all([
171
168
  this.getDepositsSentBy(address, fromBlock),
@@ -205,7 +202,7 @@ class PodBridgeTrackerClient {
205
202
  * @returns True if the deposit can be claimed
206
203
  */
207
204
  async canBeClaimed(deposit) {
208
- const currentBlock = await this.sourceProvider.getBlockNumber();
205
+ const currentBlock = await this.bridgedProvider.getBlockNumber();
209
206
  const finalizedBlock = currentBlock - 15; // ~15 minutes on Sepolia
210
207
  return deposit.blockNumber <= finalizedBlock && !deposit.isClaimed;
211
208
  }
@@ -214,16 +211,16 @@ class PodBridgeTrackerClient {
214
211
  * @param deposits Array of deposits to check
215
212
  */
216
213
  async updateClaimStatus(deposits) {
217
- if (deposits.length === 0)
214
+ if (deposits.length === 0) {
218
215
  return;
219
- console.log(`Checking claim status for ${deposits.length} deposits...`);
216
+ }
220
217
  try {
221
218
  // Get claim events from destination chain
222
- const claimFilter = this.destBridge.filters.Claim();
223
- const claimNativeFilter = this.destBridge.filters.ClaimNative();
219
+ const claimFilter = this.podBridge.filters.Claim();
220
+ const claimNativeFilter = this.podBridge.filters.ClaimNative();
224
221
  const [claimLogs, claimNativeLogs] = await Promise.all([
225
- this.destBridge.queryFilter(claimFilter),
226
- this.destBridge.queryFilter(claimNativeFilter)
222
+ this.podBridge.queryFilter(claimFilter),
223
+ this.podBridge.queryFilter(claimNativeFilter)
227
224
  ]);
228
225
  // Create a map of claimed request IDs
229
226
  const claimedMap = new Map();
@@ -268,8 +265,7 @@ class PodBridgeTrackerClient {
268
265
  const tokens = deposits.map(d => d.token);
269
266
  const amounts = deposits.map(d => d.amount);
270
267
  const tos = deposits.map(d => d.to);
271
- const results = await this.destBridge.areRequestsProcessed(ids, tokens, amounts, tos);
272
- return results;
268
+ return await this.podBridge.areRequestsProcessed(ids, tokens, amounts, tos);
273
269
  }
274
270
  catch (error) {
275
271
  console.error('Batch check failed, falling back to individual checks:', error);
@@ -278,7 +274,7 @@ class PodBridgeTrackerClient {
278
274
  for (const deposit of deposits) {
279
275
  try {
280
276
  const hash = this.computeRequestHash(deposit.requestId, deposit.token, deposit.amount, deposit.to);
281
- const isProcessed = await this.destBridge.processedRequests(hash);
277
+ const isProcessed = await this.podBridge.processedRequests(hash);
282
278
  results.push(isProcessed);
283
279
  }
284
280
  catch (_a) {
@@ -299,5 +295,157 @@ class PodBridgeTrackerClient {
299
295
  computeRequestHash(id, token, amount, to) {
300
296
  return ethers_1.ethers.solidityPackedKeccak256(['uint256', 'address', 'uint256', 'address'], [id, token, amount, to]);
301
297
  }
298
+ /**
299
+ * Get certified log for a deposit transaction from POD
300
+ * This is required to claim tokens on Sepolia from POD deposits
301
+ * @param txHash Transaction hash of the deposit on POD
302
+ * @returns Certified log with attestations and merkle proof
303
+ */
304
+ async getDepositCertifiedLog(txHash) {
305
+ // Fetch the transaction receipt with POD metadata
306
+ const receipt = await this.podProvider.getTransactionReceipt(txHash);
307
+ if (!receipt) {
308
+ throw new Error(`Transaction receipt not found for hash: ${txHash}`);
309
+ }
310
+ if (!receipt.pod_metadata || !receipt.pod_metadata.attestations) {
311
+ throw new Error(`Transaction ${txHash} does not have POD metadata with attestations`);
312
+ }
313
+ if (receipt.logs.length === 0) {
314
+ throw new Error(`No logs found in transaction ${txHash}`);
315
+ }
316
+ // Find the Deposit or DepositNative event
317
+ const depositLog = receipt.logs.find(log => {
318
+ const topics = log.topics;
319
+ if (topics.length === 0)
320
+ return false;
321
+ const depositEventSig = ethers_1.ethers.id('Deposit(uint256,address,address,address,uint256,uint256,uint256)');
322
+ const depositNativeEventSig = ethers_1.ethers.id('DepositNative(uint256,address,address,uint256,uint256,uint256)');
323
+ return topics[0] === depositEventSig || topics[0] === depositNativeEventSig;
324
+ });
325
+ if (!depositLog) {
326
+ throw new Error(`No Deposit or DepositNative event found in transaction ${txHash}`);
327
+ }
328
+ // Compute receipt root (hash of receipt without pod_metadata)
329
+ const receiptRoot = this.computeReceiptRoot(receipt);
330
+ // Generate aggregate signature from attestations
331
+ const sortedAttestations = [...receipt.pod_metadata.attestations].sort((a, b) => a.timestamp - b.timestamp);
332
+ const aggregateSignature = this.aggregateSignatures(sortedAttestations);
333
+ const sortedTimestamps = sortedAttestations.map(att => BigInt(Math.floor(att.timestamp / 1000000))); // Convert microseconds to seconds
334
+ // Generate merkle proof for the log
335
+ const proof = this.generateMerkleProof(receipt.logs, parseInt(depositLog.logIndex));
336
+ // Compute leaf hash
337
+ const logHash = this.hashLog({
338
+ addr: depositLog.address,
339
+ topics: depositLog.topics,
340
+ data: depositLog.data
341
+ });
342
+ const leaf = this.hashLeaf(`log_hashes[${depositLog.logIndex}]`, logHash);
343
+ return {
344
+ log: {
345
+ addr: depositLog.address,
346
+ topics: depositLog.topics,
347
+ data: depositLog.data
348
+ },
349
+ logIndex: BigInt(depositLog.logIndex),
350
+ certificate: {
351
+ leaf,
352
+ certifiedReceipt: {
353
+ receiptRoot,
354
+ aggregateSignature,
355
+ sortedAttestationTimestamps: sortedTimestamps
356
+ },
357
+ proof: {
358
+ path: proof
359
+ }
360
+ }
361
+ };
362
+ }
363
+ /**
364
+ * Compute receipt root hash
365
+ * @private
366
+ */
367
+ computeReceiptRoot(receipt) {
368
+ // Create a simplified receipt object without pod_metadata for hashing
369
+ const logs = receipt.logs.map(log => ({
370
+ address: log.address,
371
+ topics: log.topics,
372
+ data: log.data
373
+ }));
374
+ // Receipt RLP encoding fields (simplified - matches POD's receipt structure)
375
+ const receiptData = ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['uint8', 'uint256', 'bytes32', 'uint256', 'tuple(address,bytes32[],bytes)[]'], [
376
+ parseInt(receipt.status),
377
+ BigInt(receipt.cumulativeGasUsed),
378
+ receipt.logsBloom,
379
+ BigInt(receipt.gasUsed),
380
+ logs.map(log => [log.address, log.topics, log.data])
381
+ ]);
382
+ return ethers_1.ethers.keccak256(receiptData);
383
+ }
384
+ /**
385
+ * Aggregate signatures from attestations
386
+ * @private
387
+ */
388
+ aggregateSignatures(attestations) {
389
+ // Concatenate all signatures (r, s, v format)
390
+ const signatures = attestations.map(att => {
391
+ const r = att.signature.r.startsWith('0x') ? att.signature.r.slice(2) : att.signature.r;
392
+ const s = att.signature.s.startsWith('0x') ? att.signature.s.slice(2) : att.signature.s;
393
+ const v = att.signature.v.startsWith('0x') ? att.signature.v.slice(2) : att.signature.v;
394
+ return r + s + v.padStart(2, '0');
395
+ });
396
+ return '0x' + signatures.join('');
397
+ }
398
+ /**
399
+ * Hash a log entry
400
+ * @private
401
+ */
402
+ hashLog(log) {
403
+ return ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['address', 'bytes32[]', 'bytes'], [log.addr, log.topics, log.data]));
404
+ }
405
+ /**
406
+ * Hash a leaf for merkle tree
407
+ * @private
408
+ */
409
+ hashLeaf(key, value) {
410
+ return ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['bytes', 'bytes32'], [ethers_1.ethers.toUtf8Bytes(key), value]));
411
+ }
412
+ /**
413
+ * Generate merkle proof for a log at given index
414
+ * @private
415
+ */
416
+ generateMerkleProof(logs, logIndex) {
417
+ // Hash all logs
418
+ const logHashes = logs.map((log, idx) => this.hashLeaf(`log_hashes[${idx}]`, this.hashLog({
419
+ addr: log.address,
420
+ topics: log.topics,
421
+ data: log.data
422
+ })));
423
+ // Build merkle tree and generate proof
424
+ const proof = [];
425
+ let currentIndex = logIndex;
426
+ let currentLevel = [...logHashes];
427
+ while (currentLevel.length > 1) {
428
+ const nextLevel = [];
429
+ for (let i = 0; i < currentLevel.length; i += 2) {
430
+ if (i + 1 < currentLevel.length) {
431
+ // Pair exists
432
+ const left = currentLevel[i];
433
+ const right = currentLevel[i + 1];
434
+ // Add sibling to proof if current index is in this pair
435
+ if (i === currentIndex || i + 1 === currentIndex) {
436
+ proof.push(i === currentIndex ? right : left);
437
+ }
438
+ nextLevel.push(ethers_1.ethers.keccak256(ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'bytes32'], [left, right])));
439
+ }
440
+ else {
441
+ // Odd node, promote to next level
442
+ nextLevel.push(currentLevel[i]);
443
+ }
444
+ }
445
+ currentIndex = Math.floor(currentIndex / 2);
446
+ currentLevel = nextLevel;
447
+ }
448
+ return proof;
449
+ }
302
450
  }
303
451
  exports.PodBridgeTrackerClient = PodBridgeTrackerClient;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { PodBridgeActionClient } from './clients/action/client';
2
2
  export { PodBridgeTrackerClient } from './clients/tracker/client';
3
3
  export { POD_BRIDGE_ABI } from './libs/abi/bridge.abi';
4
- export { PodBridgeConfig, BridgeRequest, BridgeRequestWithType, DepositType, UnsignedTransaction, CertifiedLog, } from './libs/types/pod-bridge.types';
4
+ export { PodBridgeConfig, BridgeRequest, BridgeRequestWithType, DepositType, UnsignedTransaction, CertifiedLog, PodAttestation, PodMetadata, PodTransactionReceipt, } from './libs/types/pod-bridge.types';
@@ -2,9 +2,14 @@ export interface PodBridgeChainConfig {
2
2
  rpcUrl: string;
3
3
  contractAddress: string;
4
4
  }
5
+ /**
6
+ * Configuration for the bridge
7
+ * * bridged: Configuration for the bridged chain. BridgeDepositWithdraw contract address
8
+ * * pod: Configuration for the POD chain. BridgeMintBurn contract address
9
+ */
5
10
  export interface PodBridgeConfig {
6
- source: PodBridgeChainConfig;
7
- destination: PodBridgeChainConfig;
11
+ bridged: PodBridgeChainConfig;
12
+ pod: PodBridgeChainConfig;
8
13
  }
9
14
  export interface BridgeRequest {
10
15
  requestId: string;
@@ -41,9 +46,56 @@ export interface CertifiedLog {
41
46
  topics: string[];
42
47
  data: string;
43
48
  };
44
- sigs: Array<{
49
+ logIndex: bigint | string;
50
+ certificate: {
51
+ leaf: string;
52
+ certifiedReceipt: {
53
+ receiptRoot: string;
54
+ aggregateSignature: string;
55
+ sortedAttestationTimestamps: bigint[] | string[];
56
+ };
57
+ proof: {
58
+ path: string[];
59
+ };
60
+ };
61
+ }
62
+ export interface PodAttestation {
63
+ public_key: string;
64
+ signature: {
45
65
  r: string;
46
66
  s: string;
47
- v: number;
67
+ v: string;
68
+ yParity: string;
69
+ };
70
+ timestamp: number;
71
+ }
72
+ export interface PodMetadata {
73
+ attestations: PodAttestation[];
74
+ }
75
+ export interface PodTransactionReceipt {
76
+ blockHash: string;
77
+ blockNumber: string;
78
+ contractAddress: string | null;
79
+ cumulativeGasUsed: string;
80
+ effectiveGasPrice: string;
81
+ from: string;
82
+ gasUsed: string;
83
+ logs: Array<{
84
+ address: string;
85
+ topics: string[];
86
+ data: string;
87
+ blockNumber: string;
88
+ transactionHash: string;
89
+ transactionIndex: string;
90
+ blockHash: string;
91
+ logIndex: string;
92
+ removed: boolean;
48
93
  }>;
94
+ logsBloom: string;
95
+ pod_metadata: PodMetadata;
96
+ status: string;
97
+ to: string;
98
+ transactionHash: string;
99
+ transactionIndex: string;
100
+ type: string;
49
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapforce/pod-bridge-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "SDK for interacting with Bridges between pod and other chains",
5
5
  "keywords": [
6
6
  "pod",