@relay-protocol/hub-utils 0.1.0

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.
@@ -0,0 +1,2 @@
1
+ const sharedConfig = require("@relay-protocol/eslint-config")
2
+ module.exports = [...sharedConfig]
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@relay-protocol/hub-utils",
3
+ "version": "0.1.0",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsup src/index.ts --format cjs,esm --dts",
9
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
10
+ "lint": "lint",
11
+ "clean": "rm -rf dist",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
15
+ "devDependencies": {
16
+ "@relay-protocol/eslint-config": "workspace:*",
17
+ "@relay-protocol/tsconfig": "workspace:*",
18
+ "eslint": "9.36.0",
19
+ "tsup": "8.5.0",
20
+ "typescript": "5.9.3",
21
+ "vitest": "3.2.4"
22
+ },
23
+ "dependencies": {
24
+ "@relay-protocol/types": "workspace:*"
25
+ }
26
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, test } from "vitest"
2
+ import { generateAddress } from "./address"
3
+ import { addressesTestCases } from "@relay-protocol/fixtures"
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
+ })
package/src/address.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { VirtualAddress, VirtualAddressComponents } from "@relay-protocol/types"
2
+ import { ethers } from "ethers"
3
+
4
+ import { getCheckSummedAddress } from "./utils"
5
+
6
+ /**
7
+ * Generates a virtual Ethereum address from token components
8
+ * @param components The token components (family, chainId, address)
9
+ * @returns A checksummed Ethereum address derived from the token ID
10
+ * @remarks This function first generates a token ID using the components,
11
+ * then converts the last 20 bytes of the hash to an Ethereum address.
12
+ * This is equivalent to the Solidity: address(uint160(uint256(addressHash)))
13
+ */
14
+
15
+ export function generateAddress(
16
+ components: VirtualAddressComponents
17
+ ): VirtualAddress {
18
+ const { chainId, address, family } = components
19
+ const addressHash = ethers.keccak256(
20
+ ethers.solidityPacked(
21
+ ["string", "uint256", family === "ethereum-vm" ? "address" : "string"],
22
+ [family, chainId, getCheckSummedAddress(family, address)]
23
+ )
24
+ )
25
+ const addressBytes = addressHash.slice(2).slice(-40)
26
+ return ethers.getAddress("0x" + addressBytes) as `0x${string}`
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Collection of utility functions for the Relay Protocol Hub
3
+ */
4
+
5
+ export * from "./token"
6
+ export * from "./address"
7
+ export * from "./intent"
@@ -0,0 +1,13 @@
1
+ import { describe, expect, test } from "vitest"
2
+ import { intentTestCases } from "@relay-protocol/fixtures"
3
+ import { generateIntentAddress } from "./intent"
4
+ import { ethers } from "ethers"
5
+
6
+ describe("Intent Ids", () => {
7
+ test.each(intentTestCases)("$name", ({ id, expectedAddress }) => {
8
+ const address = generateIntentAddress(id)
9
+ expect(address).toBe(expectedAddress)
10
+ // address with correct checksum
11
+ expect(ethers.getAddress(expectedAddress)).toBe(expectedAddress)
12
+ })
13
+ })
package/src/intent.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { ethers } from "ethers"
2
+ import { VirtualAddress } from "@relay-protocol/types"
3
+
4
+ export const generateIntentAddress = (intentId: string): VirtualAddress => {
5
+ const addressHash = ethers.keccak256(
6
+ ethers.solidityPacked(["string"], [intentId])
7
+ )
8
+ return ethers.getAddress(
9
+ "0x" + addressHash.slice(2).slice(-40)
10
+ ) as `0x${string}`
11
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from "vitest"
2
+ import { generateTokenId } from "./token"
3
+ import { tokenIdTestCases } from "@relay-protocol/fixtures"
4
+ import { TokenIdComponents } from "@relay-protocol/types"
5
+
6
+ describe("Token ID Generation", () => {
7
+ test.each(tokenIdTestCases)("$name", ({ input, expectedValue }) => {
8
+ const tokenId = generateTokenId(input)
9
+
10
+ const differentInput = {
11
+ ...input,
12
+ chainId: input.chainId + 1n,
13
+ }
14
+ const differentTokenId = generateTokenId(differentInput)
15
+ expect(tokenId).not.toBe(differentTokenId)
16
+ expect(tokenId).toBe(expectedValue)
17
+ })
18
+
19
+ test("should handle case-insensitive EVM addresses", () => {
20
+ const addr = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
21
+ const input1: TokenIdComponents = {
22
+ address: addr,
23
+ chainId: 1n,
24
+ family: "ethereum-vm",
25
+ }
26
+ const input2: TokenIdComponents = {
27
+ address: addr.toLowerCase(),
28
+ chainId: 1n,
29
+ family: "ethereum-vm",
30
+ }
31
+
32
+ expect(generateTokenId(input1)).toBe(generateTokenId(input2))
33
+ })
34
+ })
package/src/token.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { ethers } from "ethers"
2
+ import { TokenIdComponents, TokenId } from "@relay-protocol/types"
3
+ import { getCheckSummedAddress } from "./utils"
4
+
5
+ /**
6
+ * Generates a token ID based on the chain type, chain ID, and address
7
+ * @param components The token components (family, chainId, address)
8
+ * @returns The keccak256 hash of the token components
9
+ */
10
+ export function generateTokenId(components: TokenIdComponents): TokenId {
11
+ const { family, chainId, address } = components
12
+ const packedData = ethers.solidityPacked(
13
+ ["string", "uint256", family === "ethereum-vm" ? "address" : "string"],
14
+ [family, chainId, getCheckSummedAddress(family, address)]
15
+ )
16
+ return BigInt(ethers.keccak256(packedData))
17
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { ethers } from "ethers"
2
+
3
+ export const getCheckSummedAddress = (family: string, address: string) => {
4
+ const checksummedAddress =
5
+ family === "ethereum-vm" ? ethers.getAddress(address) : address
6
+ return checksummedAddress
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@relay-protocol/tsconfig",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config"
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: true,
7
+ include: ["src/**/*.test.ts"],
8
+ },
9
+ })