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

Commit 079b885

Browse files
authored
move hub utils to sdk (#25)
* move hub utils to sdk * add to index * fix path
1 parent c9b19ea commit 079b885

File tree

6 files changed

+227
-0
lines changed

6 files changed

+227
-0
lines changed

src/hub/hub-utils.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { getAddress, keccak256, encodePacked } from "viem";
2+
import { VmType } from "../utils";
3+
4+
export interface TokenIdComponents {
5+
family: VmType;
6+
chainId: bigint;
7+
address: string;
8+
}
9+
10+
export interface VirtualAddressComponents {
11+
family: VmType;
12+
chainId: bigint;
13+
address: string;
14+
}
15+
16+
export type TokenId = bigint;
17+
export type VirtualAddress = `0x${string}`;
18+
19+
export const getCheckSummedAddress = (family: string, address: string) => {
20+
const checksummedAddress =
21+
family === "ethereum-vm" ? getAddress(address) : address;
22+
return checksummedAddress;
23+
};
24+
25+
/**
26+
* Generates a virtual Ethereum address from token components
27+
* @param components The token components (family, chainId, address)
28+
* @returns A checksummed Ethereum address derived from the token ID
29+
* @remarks This function first generates a token ID using the components,
30+
* then converts the last 20 bytes of the hash to an Ethereum address.
31+
* This is equivalent to the Solidity: address(uint160(uint256(addressHash)))
32+
*/
33+
34+
export function generateAddress(
35+
components: VirtualAddressComponents
36+
): VirtualAddress {
37+
const { chainId, address, family } = components;
38+
const addressHash = keccak256(
39+
encodePacked(
40+
["string", "uint256", family === "ethereum-vm" ? "address" : "string"],
41+
[family, chainId, getCheckSummedAddress(family, address)]
42+
)
43+
);
44+
const addressBytes = addressHash.slice(2).slice(-40);
45+
return getAddress("0x" + addressBytes) as `0x${string}`;
46+
}
47+
48+
/**
49+
* Generates a token ID based on the chain type, chain ID, and address
50+
* @param components The token components (family, chainId, address)
51+
* @returns The keccak256 hash of the token components
52+
*/
53+
export function generateTokenId(components: TokenIdComponents): TokenId {
54+
const { family, chainId, address } = components;
55+
const packedData = encodePacked(
56+
["string", "uint256", family === "ethereum-vm" ? "address" : "string"],
57+
[family, chainId, getCheckSummedAddress(family, address)]
58+
);
59+
return BigInt(keccak256(packedData));
60+
}

src/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ import {
7070
WithdrawalAddressRequest,
7171
} from "./messages/v2.2/withdrawal-execution";
7272

73+
import {
74+
TokenIdComponents,
75+
VirtualAddressComponents,
76+
TokenId,
77+
VirtualAddress,
78+
getCheckSummedAddress,
79+
generateAddress,
80+
generateTokenId,
81+
} from "./hub/hub-utils";
82+
7383
export {
7484
// Order
7585
Order,
@@ -130,6 +140,15 @@ export {
130140
encodeAction,
131141
decodeAction,
132142

143+
// Hub utils
144+
TokenIdComponents,
145+
VirtualAddressComponents,
146+
TokenId,
147+
VirtualAddress,
148+
getCheckSummedAddress,
149+
generateAddress,
150+
generateTokenId,
151+
133152
// Onchain withdrawals
134153
SubmitWithdrawRequest,
135154
getSubmitWithdrawRequestHash,

test/hub-utils/address.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, test } from "vitest";
2+
import { generateAddress } from "../../src/hub/hub-utils";
3+
import { addressesTestCases } from "./fixtures/address";
4+
import { ethers } from "ethers";
5+
6+
describe("Virtual Addresses", () => {
7+
test.each(addressesTestCases)("$name", ({ input, expectedAddress }) => {
8+
const address = generateAddress(input);
9+
10+
const differentInput = {
11+
...input,
12+
chainId: input.chainId + 1n,
13+
};
14+
const differentAddress = generateAddress(differentInput);
15+
expect(address).not.toBe(differentAddress);
16+
expect(address).toBe(expectedAddress);
17+
18+
// address with correct checksum
19+
expect(ethers.getAddress(expectedAddress)).toBe(expectedAddress);
20+
});
21+
});

test/hub-utils/fixtures/address.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { VirtualAddressComponents } from "@relay-protocol/types"
2+
3+
export const addressesTestCases: Array<{
4+
name: string
5+
input: VirtualAddressComponents
6+
expectedAddress: `0x${string}`
7+
}> = [
8+
{
9+
expectedAddress: "0xb1AF659094F7CF6c3FfE7e4d056d968B0Fe58663",
10+
input: {
11+
address: "0x0000000000000000000000000000000000000000",
12+
chainId: 1n,
13+
family: "ethereum-vm",
14+
},
15+
name: "ETH on Ethereum", // '0x' + 64 hex characters
16+
},
17+
{
18+
expectedAddress: "0xa1e17A109f1909b54C5611c6655AFcbAF1F09239",
19+
input: {
20+
address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
21+
chainId: 1n,
22+
family: "bitcoin-vm",
23+
},
24+
name: "Bitcoin",
25+
},
26+
{
27+
expectedAddress: "0xAa261e59fd53c7B115f1aae3918D416629ace745",
28+
input: {
29+
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
30+
chainId: 1n,
31+
family: "solana-vm",
32+
},
33+
name: "USDC on Solana",
34+
},
35+
{
36+
expectedAddress: "0xe3c144E770F8547Df5aF51A2d4E84C9c289CcC3a",
37+
input: {
38+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
39+
chainId: 8453n,
40+
family: "ethereum-vm",
41+
},
42+
name: "USDC on Base",
43+
},
44+
]

test/hub-utils/fixtures/tokenId.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { TokenIdComponents } from "@relay-protocol/types"
2+
export const tokenIdTestCases: Array<{
3+
name: string
4+
input: TokenIdComponents
5+
expectedValue: bigint
6+
}> = [
7+
{
8+
expectedValue:
9+
5126370114286486119248922823807248445856144931672230102669788761404601632355n,
10+
input: {
11+
address: "0x0000000000000000000000000000000000000000",
12+
chainId: 1n,
13+
family: "ethereum-vm",
14+
},
15+
name: "ETH on Ethereum",
16+
},
17+
{
18+
expectedValue:
19+
101142405549722680701516949243527989485095939267215334056209565926507227943481n,
20+
input: {
21+
address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
22+
chainId: 1n,
23+
family: "bitcoin-vm",
24+
},
25+
name: "Bitcoin",
26+
},
27+
{
28+
expectedValue:
29+
108890717977569292143568470585265267208172758058844132994285904278323093890885n,
30+
input: {
31+
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
32+
chainId: 1n,
33+
family: "solana-vm",
34+
},
35+
name: "USDC on Solana",
36+
},
37+
{
38+
expectedValue:
39+
30815307311220170804965801606391678921022824512560571593430839734064343993402n,
40+
input: {
41+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
42+
chainId: 8453n,
43+
family: "ethereum-vm",
44+
},
45+
name: "USDC on Base",
46+
},
47+
]

test/hub-utils/token.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, expect, test } from "vitest";
2+
import {
3+
generateTokenId,
4+
type TokenIdComponents,
5+
} from "../../src/hub/hub-utils";
6+
import { tokenIdTestCases } from "./fixtures/tokenId";
7+
8+
describe("Token ID Generation", () => {
9+
test.each(tokenIdTestCases)("$name", ({ input, expectedValue }) => {
10+
const tokenId = generateTokenId(input);
11+
12+
const differentInput = {
13+
...input,
14+
chainId: input.chainId + 1n,
15+
};
16+
const differentTokenId = generateTokenId(differentInput);
17+
expect(tokenId).not.toBe(differentTokenId);
18+
expect(tokenId).toBe(expectedValue);
19+
});
20+
21+
test("should handle case-insensitive EVM addresses", () => {
22+
const addr = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
23+
const input1: TokenIdComponents = {
24+
address: addr,
25+
chainId: 1n,
26+
family: "ethereum-vm",
27+
};
28+
const input2: TokenIdComponents = {
29+
address: addr.toLowerCase(),
30+
chainId: 1n,
31+
family: "ethereum-vm",
32+
};
33+
34+
expect(generateTokenId(input1)).toBe(generateTokenId(input2));
35+
});
36+
});

0 commit comments

Comments
 (0)