@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.
- package/eslint.config.js +2 -0
- package/package.json +26 -0
- package/src/address.test.ts +21 -0
- package/src/address.ts +27 -0
- package/src/index.ts +7 -0
- package/src/intent.test.ts +13 -0
- package/src/intent.ts +11 -0
- package/src/token.test.ts +34 -0
- package/src/token.ts +17 -0
- package/src/utils.ts +7 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +9 -0
package/eslint.config.js
ADDED
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,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
package/tsconfig.json
ADDED