@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 +97 -40
- package/dist/clients/action/client.d.ts +4 -4
- package/dist/clients/action/client.js +4 -4
- package/dist/clients/tracker/client.d.ts +37 -5
- package/dist/clients/tracker/client.js +175 -27
- package/dist/index.d.ts +1 -1
- package/dist/libs/types/pod-bridge.types.d.ts +56 -4
- package/package.json +1 -1
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
|
-
|
|
49
|
+
bridged: {
|
|
50
50
|
rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
|
|
51
51
|
contractAddress: '0x...' // Source chain bridge contract
|
|
52
52
|
},
|
|
53
|
-
|
|
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
|
-
//
|
|
105
|
-
const
|
|
106
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
372
|
+
bridged: {
|
|
370
373
|
rpcUrl: 'https://sepolia.infura.io/v3/YOUR_KEY',
|
|
371
|
-
contractAddress: '
|
|
374
|
+
contractAddress: '0xSepoliaBridgeContract'
|
|
372
375
|
},
|
|
373
|
-
|
|
374
|
-
rpcUrl: 'https://
|
|
375
|
-
contractAddress: '
|
|
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
|
|
382
|
-
const
|
|
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
|
-
|
|
389
|
-
contractAddress: config.
|
|
391
|
+
destinationWalletAddress: await sepoliaSigner.getAddress(),
|
|
392
|
+
contractAddress: config.bridged.contractAddress
|
|
390
393
|
});
|
|
391
394
|
|
|
392
|
-
const tx = await
|
|
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
|
|
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.
|
|
413
|
+
contractAddress: config.pod.contractAddress
|
|
411
414
|
});
|
|
412
415
|
|
|
413
|
-
// Submit claim on
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
5
|
-
private readonly
|
|
6
|
-
private readonly
|
|
7
|
-
private readonly
|
|
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.
|
|
12
|
-
this.
|
|
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.
|
|
15
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
43
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
114
|
-
this.
|
|
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.
|
|
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
|
-
|
|
216
|
+
}
|
|
220
217
|
try {
|
|
221
218
|
// Get claim events from destination chain
|
|
222
|
-
const claimFilter = this.
|
|
223
|
-
const claimNativeFilter = this.
|
|
219
|
+
const claimFilter = this.podBridge.filters.Claim();
|
|
220
|
+
const claimNativeFilter = this.podBridge.filters.ClaimNative();
|
|
224
221
|
const [claimLogs, claimNativeLogs] = await Promise.all([
|
|
225
|
-
this.
|
|
226
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
}
|