Skip to content
This repository was archived by the owner on Dec 23, 2025. It is now read-only.

Commit 1fc5dc8

Browse files
authored
Merge branch 'main' into hub-utils
2 parents 31c50f1 + c9b19ea commit 1fc5dc8

File tree

7 files changed

+197
-70
lines changed

7 files changed

+197
-70
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@reservoir0x/relay-protocol-sdk",
3-
"version": "0.0.60",
3+
"version": "0.0.63",
44
"description": "Relay protocol SDK",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ import {
6464
SubmitWithdrawRequest,
6565
getSubmitWithdrawRequestHash,
6666
getWithdrawalAddress,
67-
} from "./messages/v2.2/allocator";
67+
WithdrawalAddressParams,
68+
WithdrawalInitiationMessage,
69+
WithdrawalInitiatedMessage,
70+
WithdrawalAddressRequest,
71+
} from "./messages/v2.2/withdrawal-execution";
6872

6973
import {
7074
TokenIdComponents,
@@ -136,17 +140,21 @@ export {
136140
encodeAction,
137141
decodeAction,
138142

139-
// Allocator
140-
SubmitWithdrawRequest,
141-
getSubmitWithdrawRequestHash,
142-
getWithdrawalAddress,
143-
144-
//
143+
// Hub utils
145144
TokenIdComponents,
146145
VirtualAddressComponents,
147146
TokenId,
148147
VirtualAddress,
149148
getCheckSummedAddress,
150149
generateAddress,
151150
generateTokenId,
151+
152+
// Onchain withdrawals
153+
SubmitWithdrawRequest,
154+
getSubmitWithdrawRequestHash,
155+
getWithdrawalAddress,
156+
WithdrawalAddressParams,
157+
WithdrawalInitiationMessage,
158+
WithdrawalInitiatedMessage,
159+
WithdrawalAddressRequest,
152160
};

src/messages/v2.2/allocator.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/messages/v2.2/execution.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export type ExecutionMessage = {
3131
metadata?: ExecutionMessageMetadata[];
3232
};
3333

34+
export type ExecutionMetadata = Omit<
35+
ExecutionMessageMetadata,
36+
"oracleContract" | "oracleChainId"
37+
>;
38+
3439
export const getExecutionMessageId = (message: ExecutionMessage) => {
3540
return hashStruct({
3641
types: {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { Hex, Address, encodePacked, keccak256 } from "viem";
2+
3+
export interface SubmitWithdrawRequest {
4+
chainId: string; // chainId of the destination chain on which the user will withdraw
5+
depository: string; // address of the depository account
6+
currency: string;
7+
amount: string; // Amount to withdraw
8+
spender: string; // address of the account that owns the balance in the Hub contract (can be an alias)
9+
receiver: string; // Address of the account on the destination chain
10+
data: string; // add tional data
11+
nonce: string; // Nonce for replay protection
12+
}
13+
14+
export const getSubmitWithdrawRequestHash = (
15+
request: SubmitWithdrawRequest
16+
) => {
17+
// EIP712 type from RelayAllocator
18+
const PAYLOAD_TYPEHASH = keccak256(
19+
"SubmitWithdrawRequest(uint256 chainId,string depository,string currency,uint256 amount,address spender,string receiver,bytes data,bytes32 nonce)" as Hex
20+
);
21+
22+
// Create EIP712 digest
23+
const digest = keccak256(
24+
encodePacked(
25+
[
26+
"bytes32",
27+
"uint256",
28+
"bytes32",
29+
"bytes32",
30+
"uint256",
31+
"address",
32+
"bytes32",
33+
"bytes32",
34+
"bytes32",
35+
],
36+
[
37+
PAYLOAD_TYPEHASH,
38+
BigInt(request.chainId),
39+
keccak256(request.depository as Hex),
40+
keccak256(request.currency as Hex),
41+
BigInt(request.amount),
42+
request.spender as Address,
43+
keccak256(request.receiver as Hex),
44+
keccak256(request.data as Hex),
45+
request.nonce as Hex,
46+
]
47+
)
48+
);
49+
50+
// The withdrawal address is the digest itself (as a hex string)
51+
return digest;
52+
};
53+
54+
export type WithdrawalAddressParams = {
55+
depositoryAddress: string;
56+
depositoryChainId: bigint;
57+
currency: string;
58+
recipientAddress: string;
59+
owner: string;
60+
ownerChainId: string;
61+
amount: bigint;
62+
withdrawalNonce: string;
63+
};
64+
65+
/**
66+
* Compute deterministic withdrawal address
67+
*
68+
* @param depositoryAddress the depository contract holding the funds on origin chain
69+
* @param depositoryChainId the chain id of the depository contract currently holding the funds
70+
* @param currency the id of the currency as expressed on origin chain (string)
71+
* @param recipientAddress the address that will receive the withdrawn funds on destination chain
72+
* @param owner the address that owns the balance before the withdrawal is initiated
73+
* @param amount the balance to withdraw
74+
* @param withdrawalNonce nonce to prevent collisions for similar withdrawals
75+
* @returns withdrawal address (in lower case)
76+
*/
77+
export function getWithdrawalAddress(
78+
withdrawalParams: WithdrawalAddressParams
79+
): string {
80+
// pack and hash data
81+
const nonce = keccak256(
82+
encodePacked(["string"], [withdrawalParams.withdrawalNonce])
83+
);
84+
const hash = keccak256(
85+
encodePacked(
86+
[
87+
"address",
88+
"uint256",
89+
"string",
90+
"address",
91+
"address",
92+
"uint256",
93+
"bytes32",
94+
],
95+
[
96+
withdrawalParams.depositoryAddress as `0x${string}`,
97+
withdrawalParams.depositoryChainId,
98+
withdrawalParams.currency,
99+
withdrawalParams.recipientAddress as `0x${string}`,
100+
withdrawalParams.owner as `0x${string}`,
101+
withdrawalParams.amount,
102+
nonce,
103+
]
104+
)
105+
);
106+
107+
// get 40 bytes for an address
108+
const withdrawalAddress = hash.slice(2).slice(-40).toLowerCase();
109+
return `0x${withdrawalAddress}` as `0x${string}`;
110+
}
111+
112+
// for oracle requests, we replace the hub chain id by a slug (e.g. 'base')
113+
// and we pass the amount as a string
114+
export type WithdrawalAddressRequest = Omit<
115+
WithdrawalAddressParams,
116+
"depositoryChainId" | "amount"
117+
> & {
118+
depositoryChainSlug: string;
119+
amount: string;
120+
};
121+
122+
// types for oracle routes
123+
export type WithdrawalInitiationMessage = {
124+
data: WithdrawalAddressRequest & { settlementChainId: string };
125+
result: {
126+
withdrawalAddress: string;
127+
};
128+
};
129+
130+
export type WithdrawalInitiatedMessage = {
131+
data: WithdrawalAddressRequest & {
132+
settlementChainId: string;
133+
};
134+
result: {
135+
proofOfWithdrawalAddressBalance: string;
136+
withdrawalAddress: string;
137+
};
138+
};

src/utils.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,22 @@ export const encodeAddress = (address: string, vmType: VmType): Uint8Array => {
7070

7171
if (address.startsWith("bc1")) {
7272
const lower = address.toLowerCase();
73-
const decoded = bech32.decode(lower, 90);
74-
return decoded.prefix === "bc" && decoded.words[0] === 0
75-
? "bech32"
76-
: "bech32m";
73+
74+
// Try Bech32 first (v0)
75+
try {
76+
const decoded = bech32.decode(lower, 90);
77+
if (decoded.prefix === "bc" && decoded.words[0] === 0) {
78+
return "bech32";
79+
}
80+
} catch (_) {}
81+
82+
// Try Bech32m (v1+)
83+
try {
84+
const decoded = bech32m.decode(lower, 90);
85+
if (decoded.prefix === "bc" && decoded.words[0] >= 1) {
86+
return "bech32m";
87+
}
88+
} catch (_) {}
7789
}
7890

7991
throw new Error("Unsupported address format");

test/withdrawal-execution.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it, expect } from "vitest";
2+
import { getWithdrawalAddress } from "../src/messages/v2.2/withdrawal-execution";
3+
import { getAddress } from "viem";
4+
5+
describe("getWithdrawalAddress", () => {
6+
it("should return a valid withdrawal address", () => {
7+
const params = {
8+
depositoryAddress: "0x1234567890123456789012345678901234567890",
9+
depositoryChainId: 1n,
10+
currency: "10340230",
11+
recipientAddress: "0x9876543210987654321098765432109876543210",
12+
owner: "0x9876543210987654321098765432109876543210",
13+
amount: 1000n,
14+
withdrawalNonce: "haha",
15+
};
16+
17+
const address = getWithdrawalAddress(params);
18+
expect(address).toMatch(/^0x[0-9a-f]{40}$/i);
19+
expect(address).toBeTruthy();
20+
expect(getAddress(address).toLowerCase()).toMatch(address);
21+
});
22+
});

0 commit comments

Comments
 (0)