Skip to content

Commit ffa54ee

Browse files
committed
feat: fetch withdrawal proof to claim token withdrawal
1 parent 6bb84c1 commit ffa54ee

File tree

5 files changed

+164
-1
lines changed

5 files changed

+164
-1
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ out
2121
# env
2222
.env.*
2323
!.env.example
24-
*.env
24+
*.env
25+
26+
CLAUDE.md

src/client/client.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,46 @@ export abstract class BaseTNClient<T extends EnvironmentType> {
321321
return action.listWalletRewards(chain, wallet, withPending);
322322
}
323323

324+
/**
325+
* Gets withdrawal proof for a specific wallet address on a blockchain network
326+
* Returns merkle proofs and validator signatures needed for withdrawal
327+
*
328+
* This method is used for non-custodial bridge withdrawals where users need to
329+
* manually claim their withdrawals by submitting proofs to the destination chain.
330+
* The proof includes validator signatures, merkle root, block hash, and amount.
331+
*
332+
* @param chain The chain identifier (e.g., "hoodi", "sepolia", etc.)
333+
* @param walletAddress The wallet address to get withdrawal proof for
334+
* @returns Promise that resolves to an array of withdrawal proof data
335+
*
336+
* @example
337+
* ```typescript
338+
* // Get withdrawal proofs for Hoodi
339+
* const proofs = await client.getWithdrawalProof("hoodi", "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb");
340+
*
341+
* // Proofs will be an array like:
342+
* // [{
343+
* // chain: "hoodi",
344+
* // chain_id: "3639",
345+
* // contract: "0x878D6aaeB6e746033f50B8dC268d54B4631554E7",
346+
* // created_at: 3080,
347+
* // recipient: "0x...",
348+
* // amount: "100000000000000000000",
349+
* // block_hash: <base64-encoded>,
350+
* // root: <base64-encoded>,
351+
* // proofs: [],
352+
* // signatures: [<base64-encoded-signatures>]
353+
* // }]
354+
* ```
355+
*
356+
* @note This method has been tested via integration tests in the node repository.
357+
* See: /home/micbun/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20/meta_extension_withdrawal_test.go
358+
*/
359+
async getWithdrawalProof(chain: string, walletAddress: string): Promise<any[]> {
360+
const action = this.loadAction();
361+
return action.getWithdrawalProof(chain, walletAddress);
362+
}
363+
324364
/**
325365
* Gets taxonomies for specific streams in batch.
326366
* High-level wrapper for ComposedAction.getTaxonomiesForStreams()

src/contracts-api/action.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,4 +1080,45 @@ export class Action {
10801080

10811081
return result.data?.result || [];
10821082
}
1083+
1084+
/**
1085+
* Gets withdrawal proof for a wallet address on a blockchain network
1086+
* Returns merkle proofs and validator signatures needed for withdrawal on target chain
1087+
*
1088+
* This method is used for non-custodial bridge withdrawals where users need to
1089+
* manually claim their withdrawals by submitting proofs to the destination chain.
1090+
*
1091+
* @param chain The chain identifier (e.g., "hoodi", "sepolia", etc.)
1092+
* @param walletAddress The wallet address to get withdrawal proof for
1093+
* @returns Promise that resolves to array of withdrawal proofs
1094+
*
1095+
* @example
1096+
* ```typescript
1097+
* // Get withdrawal proofs for Hoodi
1098+
* const proofs = await action.getWithdrawalProof("hoodi", "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb");
1099+
* // Returns: [{ chain: "hoodi", recipient: "0x...", amount: "100000000000000000000", ... }]
1100+
* ```
1101+
*
1102+
* @note This method has been tested via integration tests in the node repository.
1103+
* See: /home/micbun/trufnetwork/kwil-db/node/exts/erc20-bridge/erc20/meta_extension_withdrawal_test.go
1104+
*/
1105+
public async getWithdrawalProof(
1106+
chain: string,
1107+
walletAddress: string
1108+
): Promise<any[]> {
1109+
const result = await this.call<any[]>(
1110+
`${chain}_get_withdrawal_proof`,
1111+
{
1112+
$wallet_address: walletAddress,
1113+
}
1114+
);
1115+
1116+
return result
1117+
.mapRight((rows) => {
1118+
// Return the array of withdrawal proofs
1119+
// May be empty if no unclaimed withdrawals
1120+
return rows || [];
1121+
})
1122+
.throw();
1123+
}
10831124
}

src/internal.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,8 @@ export type {
7676
AttestationMetadata
7777
} from "./types/attestation";
7878

79+
// Bridge types
80+
export type { WithdrawalProof } from "./types/bridge";
81+
7982
// Visibility types
8083
export type { VisibilityEnum } from "./util/visibility";

src/types/bridge.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Types for bridge-related operations
3+
*/
4+
5+
/**
6+
* Withdrawal proof returned from getWithdrawalProof()
7+
*
8+
* This proof contains all the data needed for a user to claim their withdrawal
9+
* on the destination chain by submitting a transaction to the bridge contract.
10+
*
11+
* @example
12+
* ```typescript
13+
* const proofs = await client.getWithdrawalProof("hoodi", "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb");
14+
* const proof = proofs[0];
15+
*
16+
* // Decode base64 data to use in smart contract call
17+
* const blockHash = Buffer.from(proof.block_hash, 'base64').toString('hex');
18+
* const root = Buffer.from(proof.root, 'base64').toString('hex');
19+
* const signatures = proof.signatures.map(sig => Buffer.from(sig, 'base64'));
20+
* ```
21+
*/
22+
export interface WithdrawalProof {
23+
/**
24+
* The chain identifier (e.g., "hoodi", "sepolia")
25+
*/
26+
chain: string;
27+
28+
/**
29+
* The numeric chain ID (e.g., "3639" for Hoodi)
30+
*/
31+
chain_id: string;
32+
33+
/**
34+
* The bridge contract address on the destination chain
35+
*/
36+
contract: string;
37+
38+
/**
39+
* The block number when the epoch was created
40+
*/
41+
created_at: number;
42+
43+
/**
44+
* The recipient wallet address
45+
*/
46+
recipient: string;
47+
48+
/**
49+
* The withdrawal amount in wei (as string to handle large numbers)
50+
*/
51+
amount: string;
52+
53+
/**
54+
* The Kwil block hash (base64-encoded bytes)
55+
* Decode to bytes32 for smart contract call
56+
*/
57+
block_hash: string;
58+
59+
/**
60+
* The merkle root (base64-encoded bytes)
61+
* Decode to bytes32 for smart contract call
62+
*/
63+
root: string;
64+
65+
/**
66+
* Array of merkle proofs (base64-encoded bytes)
67+
* Usually empty for single withdrawals
68+
*/
69+
proofs: string[];
70+
71+
/**
72+
* Array of validator signatures (base64-encoded bytes)
73+
* Each signature is 65 bytes: r[32] || s[32] || v[1]
74+
* Decode and split into (v, r, s) for smart contract call
75+
*/
76+
signatures: string[];
77+
}

0 commit comments

Comments
 (0)