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

Commit 4b00b8a

Browse files
authored
utils to compute withdrawal address (#24)
* withdrawal address * add oracle messages types * add to index * use currency * pass lug in oracle messages * update comment * better names * add owner * pass only address * return blockNumber * pass blocknumber and params * replace block number by nonce * nonce in params * pass amount as string to oracle service * bump version * update type
1 parent 02b4920 commit 4b00b8a

File tree

6 files changed

+173
-61
lines changed

6 files changed

+173
-61
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.61",
44
"description": "Relay protocol SDK",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/index.ts

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

6972
export {
7073
// Order
@@ -126,8 +129,11 @@ export {
126129
encodeAction,
127130
decodeAction,
128131

129-
// Allocator
132+
// Onchain withdrawals
130133
SubmitWithdrawRequest,
131134
getSubmitWithdrawRequestHash,
132135
getWithdrawalAddress,
136+
WithdrawalAddressParams,
137+
WithdrawalInitiationMessage,
138+
WithdrawalInitiatedMessage,
133139
};

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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
owner: string;
59+
recipientAddress: string;
60+
amount: bigint;
61+
withdrawalNonce: string;
62+
};
63+
64+
/**
65+
* Compute deterministic withdrawal address
66+
*
67+
* @param depositoryAddress the depository contract holding the funds on origin chain
68+
* @param depositoryChainId the chain id of the depository contract currently holding the funds
69+
* @param currency the id of the currency as expressed on origin chain (string)
70+
* @param recipientAddress the address that will receive the withdrawn funds on destination chain
71+
* @param owner the address that owns the balance before the withdrawal is initiated
72+
* @param amount the balance to withdraw
73+
* @param withdrawalNonce nonce to prevent collisions for similar withdrawals
74+
* @returns withdrawal address (in lower case)
75+
*/
76+
export function getWithdrawalAddress(
77+
withdrawalParams: WithdrawalAddressParams
78+
): string {
79+
// pack and hash data
80+
const nonce = keccak256(
81+
encodePacked(["string"], [withdrawalParams.withdrawalNonce])
82+
);
83+
const hash = keccak256(
84+
encodePacked(
85+
[
86+
"address",
87+
"uint256",
88+
"string",
89+
"address",
90+
"address",
91+
"uint256",
92+
"bytes32",
93+
],
94+
[
95+
withdrawalParams.depositoryAddress as `0x${string}`,
96+
withdrawalParams.depositoryChainId,
97+
withdrawalParams.currency,
98+
withdrawalParams.recipientAddress as `0x${string}`,
99+
withdrawalParams.owner as `0x${string}`,
100+
withdrawalParams.amount,
101+
nonce,
102+
]
103+
)
104+
);
105+
106+
// get 40 bytes for an address
107+
const withdrawalAddress = hash.slice(2).slice(-40).toLowerCase();
108+
return `0x${withdrawalAddress}` as `0x${string}`;
109+
}
110+
111+
// for oracle requests, we replace the hub chain id by a slug (e.g. 'base')
112+
// and we pass the amount as a string
113+
export type WithdrawalAddressRequest = Omit<
114+
WithdrawalAddressParams,
115+
"depositoryChainId" | "amount"
116+
> & {
117+
depositoryChainSlug: string;
118+
amount: string;
119+
};
120+
121+
// types for oracle routes
122+
export type WithdrawalInitiationMessage = {
123+
data: WithdrawalAddressRequest & { settlementChainId: string };
124+
result: {
125+
withdrawalAddress: string;
126+
};
127+
};
128+
129+
export type WithdrawalInitiatedMessage = {
130+
data: WithdrawalAddressRequest & {
131+
settlementChainId: string;
132+
};
133+
result: {
134+
proofOfWithdrawalAddressBalance: string;
135+
withdrawalAddress: string;
136+
};
137+
};

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)