@safe-vanity-blockie/safe-config 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 safe-vanity-blockie contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @safe-vanity-blockie/safe-config
2
+
3
+ Resolves the canonical Safe contract addresses for a chain and turns a Safe setup (owners,
4
+ threshold, version) into the initializer and `saltNonce` inputs that
5
+ [`@safe-vanity-blockie/core`](https://www.npmjs.com/package/@safe-vanity-blockie/core) needs to
6
+ derive a counterfactual address.
7
+
8
+ Unlike `core`, this package talks to a chain: it reads the chain ID from an RPC endpoint once, then
9
+ looks up the proxy factory and singleton deployed there.
10
+
11
+ ## Install
12
+
13
+ Requires Node.js >= 22.
14
+
15
+ ```
16
+ npm install @safe-vanity-blockie/safe-config
17
+ ```
18
+
19
+ ## Exports
20
+
21
+ | Export | Purpose |
22
+ | --- | --- |
23
+ | `loadSafeConstants` | resolve proxy factory, singleton and init code hash for a chain |
24
+ | `verifyWithProtocolKit` | cross-check a derived address against the official Safe Protocol Kit |
25
+ | `ZKSYNC_CHAIN_IDS` | chains whose CREATE2 derivation differs, so an address is not portable |
26
+ | `SafeSetup`, `SetupInput` | the setup shape and its input form |
27
+
28
+ `verifyWithProtocolKit` exists because deriving an address yourself is only trustworthy if it agrees
29
+ with the reference implementation. The CLI and the web app both run it before showing a result.
30
+
31
+ ## zkSync
32
+
33
+ zkSync-family chains use a different CREATE2 scheme, so an address mined for one of them is **not**
34
+ the same on other chains. `ZKSYNC_CHAIN_IDS` is how callers detect that case and say so.
35
+
36
+ ## Links
37
+
38
+ - [Repository](https://github.com/tmjssz/safe-vanity-blockie)
39
+ - [`safe-vanity-blockie`](https://www.npmjs.com/package/safe-vanity-blockie) — the CLI
40
+
41
+ MIT
@@ -0,0 +1,2 @@
1
+ export { loadSafeConstants, type SafeSetup, type SetupInput, verifyWithProtocolKit, ZKSYNC_CHAIN_IDS, } from './setup.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { loadSafeConstants, verifyWithProtocolKit, ZKSYNC_CHAIN_IDS, } from './setup.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAGjB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,YAAY,CAAA"}
@@ -0,0 +1,45 @@
1
+ import { SafeProvider } from '@safe-global/protocol-kit';
2
+ import type { SafeVersion } from '@safe-global/types-kit';
3
+ import { type SafeConstants } from '@safe-vanity-blockie/core';
4
+ import { type Hex } from 'viem';
5
+ export interface SetupInput {
6
+ rpcUrl: string;
7
+ owners: string[];
8
+ threshold: number;
9
+ safeVersion: SafeVersion;
10
+ /** Force the L1 singleton on an L2 chain. Must match what deployment will use. */
11
+ isL1SafeSingleton?: boolean;
12
+ }
13
+ export interface SafeSetup {
14
+ chainId: bigint;
15
+ constants: SafeConstants;
16
+ constantsHex: {
17
+ initializerHash: Hex;
18
+ factory: Hex;
19
+ initCodeHash: Hex;
20
+ };
21
+ safeProvider: SafeProvider;
22
+ safeAccountConfig: {
23
+ owners: string[];
24
+ threshold: number;
25
+ };
26
+ safeVersion: SafeVersion;
27
+ isL1SafeSingleton?: boolean;
28
+ }
29
+ /**
30
+ * zkSync Era and friends derive CREATE2 addresses with a different formula (spec §3.1).
31
+ * These are the chain IDs protocol-kit itself gates its zkSync formula on internally
32
+ * (ZKSYNC_MAINNET = 324, ZKSYNC_TESTNET = 300, ZKSYNC_LENS = 232), per the installed
33
+ * @safe-global/protocol-kit@8.0.5 source. protocol-kit additionally gates on
34
+ * `safeVersion <= 1.4.1`, which covers every version this CLI supports (1.4.1 and 1.3.0),
35
+ * so a plain set membership check on chain ID is sufficient here.
36
+ */
37
+ export declare const ZKSYNC_CHAIN_IDS: ReadonlySet<bigint>;
38
+ /**
39
+ * Reads chainId and the three constants that stay fixed for a given
40
+ * (owners, threshold, safeVersion). Runs once on the main thread; workers get plain hex.
41
+ */
42
+ export declare function loadSafeConstants(input: SetupInput): Promise<SafeSetup>;
43
+ /** Cross-checks one mined result against protocol-kit. Run this every session (spec §11). */
44
+ export declare function verifyWithProtocolKit(setup: SafeSetup, saltNonce: string, address: string): Promise<void>;
45
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,YAAY,EACb,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAU,KAAK,GAAG,EAAa,MAAM,MAAM,CAAA;AAElD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,WAAW,CAAA;IACxB,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,aAAa,CAAA;IACxB,YAAY,EAAE;QAAE,eAAe,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,GAAG,CAAC;QAAC,YAAY,EAAE,GAAG,CAAA;KAAE,CAAA;IACvE,YAAY,EAAE,YAAY,CAAA;IAC1B,iBAAiB,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,WAAW,EAAE,WAAW,CAAA;IACxB,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA+B,CAAA;AAEhF;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkD7E;AAED,6FAA6F;AAC7F,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAcf"}
package/dist/setup.js ADDED
@@ -0,0 +1,74 @@
1
+ import { encodeSetupCallData, getSafeContract, getSafeProxyFactoryContract, predictSafeAddress, SafeProvider, } from '@safe-global/protocol-kit';
2
+ import { hexToBytes } from '@safe-vanity-blockie/core';
3
+ import { concat, keccak256 } from 'viem';
4
+ /**
5
+ * zkSync Era and friends derive CREATE2 addresses with a different formula (spec §3.1).
6
+ * These are the chain IDs protocol-kit itself gates its zkSync formula on internally
7
+ * (ZKSYNC_MAINNET = 324, ZKSYNC_TESTNET = 300, ZKSYNC_LENS = 232), per the installed
8
+ * @safe-global/protocol-kit@8.0.5 source. protocol-kit additionally gates on
9
+ * `safeVersion <= 1.4.1`, which covers every version this CLI supports (1.4.1 and 1.3.0),
10
+ * so a plain set membership check on chain ID is sufficient here.
11
+ */
12
+ export const ZKSYNC_CHAIN_IDS = new Set([324n, 300n, 232n]);
13
+ /**
14
+ * Reads chainId and the three constants that stay fixed for a given
15
+ * (owners, threshold, safeVersion). Runs once on the main thread; workers get plain hex.
16
+ */
17
+ export async function loadSafeConstants(input) {
18
+ const safeProvider = new SafeProvider({ provider: input.rpcUrl });
19
+ const chainId = await safeProvider.getChainId();
20
+ if (ZKSYNC_CHAIN_IDS.has(chainId)) {
21
+ throw new Error(`chain ${chainId} is zkSync-based and derives contract addresses with a different formula; ` +
22
+ 'this tool would predict the wrong address. Use a standard EVM chain.');
23
+ }
24
+ const safeAccountConfig = { owners: input.owners, threshold: input.threshold };
25
+ const factoryContract = await getSafeProxyFactoryContract({
26
+ safeProvider,
27
+ safeVersion: input.safeVersion,
28
+ });
29
+ const safeContract = await getSafeContract({
30
+ safeProvider,
31
+ safeVersion: input.safeVersion,
32
+ isL1SafeSingleton: input.isL1SafeSingleton,
33
+ });
34
+ const initializer = await encodeSetupCallData({
35
+ safeProvider,
36
+ safeAccountConfig,
37
+ safeContract,
38
+ customSafeVersion: input.safeVersion,
39
+ });
40
+ const initializerHash = keccak256(initializer);
41
+ // proxyCreationCode() returns a single-element tuple, not a bare string.
42
+ const [proxyCreationCode] = await factoryContract.proxyCreationCode();
43
+ const encodedSingleton = safeProvider.encodeParameters('address', [safeContract.getAddress()]);
44
+ const initCodeHash = keccak256(concat([proxyCreationCode, encodedSingleton]));
45
+ const factory = factoryContract.getAddress();
46
+ return {
47
+ chainId,
48
+ constants: {
49
+ initializerHash: hexToBytes(initializerHash),
50
+ factory: hexToBytes(factory),
51
+ initCodeHash: hexToBytes(initCodeHash),
52
+ },
53
+ constantsHex: { initializerHash, factory, initCodeHash },
54
+ safeProvider,
55
+ safeAccountConfig,
56
+ safeVersion: input.safeVersion,
57
+ isL1SafeSingleton: input.isL1SafeSingleton,
58
+ };
59
+ }
60
+ /** Cross-checks one mined result against protocol-kit. Run this every session (spec §11). */
61
+ export async function verifyWithProtocolKit(setup, saltNonce, address) {
62
+ const predicted = await predictSafeAddress({
63
+ safeProvider: setup.safeProvider,
64
+ chainId: setup.chainId,
65
+ safeAccountConfig: setup.safeAccountConfig,
66
+ safeDeploymentConfig: { saltNonce, safeVersion: setup.safeVersion },
67
+ isL1SafeSingleton: setup.isL1SafeSingleton,
68
+ });
69
+ if (predicted.toLowerCase() !== address.toLowerCase()) {
70
+ throw new Error(`self-check failed for saltNonce ${saltNonce}: fast derivation gave ${address}, ` +
71
+ `protocol-kit predictSafeAddress gave ${predicted.toLowerCase()}`);
72
+ }
73
+ }
74
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@safe-vanity-blockie/safe-config",
3
+ "version": "0.3.0",
4
+ "description": "Resolve canonical Safe contract addresses and build the setup inputs for counterfactual address derivation",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/tmjssz/safe-vanity-blockie.git",
11
+ "directory": "packages/safe-config"
12
+ },
13
+ "type": "module",
14
+ "license": "MIT",
15
+ "keywords": [
16
+ "safe",
17
+ "safe-global",
18
+ "create2",
19
+ "counterfactual",
20
+ "protocol-kit",
21
+ "ethereum",
22
+ "zksync"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src",
35
+ "LICENSE"
36
+ ],
37
+ "sideEffects": false,
38
+ "dependencies": {
39
+ "@safe-global/protocol-kit": "^8.0.5",
40
+ "@safe-global/types-kit": "^4.0.1",
41
+ "viem": "^2.55.11",
42
+ "@safe-vanity-blockie/core": "0.3.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.json",
46
+ "test": "vitest run",
47
+ "test:network": "vitest run --config vitest.network.config.ts",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit"
49
+ }
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export {
2
+ loadSafeConstants,
3
+ type SafeSetup,
4
+ type SetupInput,
5
+ verifyWithProtocolKit,
6
+ ZKSYNC_CHAIN_IDS,
7
+ } from './setup.js'
package/src/setup.ts ADDED
@@ -0,0 +1,116 @@
1
+ import {
2
+ encodeSetupCallData,
3
+ getSafeContract,
4
+ getSafeProxyFactoryContract,
5
+ predictSafeAddress,
6
+ SafeProvider,
7
+ } from '@safe-global/protocol-kit'
8
+ import type { SafeVersion } from '@safe-global/types-kit'
9
+ import { hexToBytes, type SafeConstants } from '@safe-vanity-blockie/core'
10
+ import { concat, type Hex, keccak256 } from 'viem'
11
+
12
+ export interface SetupInput {
13
+ rpcUrl: string
14
+ owners: string[]
15
+ threshold: number
16
+ safeVersion: SafeVersion
17
+ /** Force the L1 singleton on an L2 chain. Must match what deployment will use. */
18
+ isL1SafeSingleton?: boolean
19
+ }
20
+
21
+ export interface SafeSetup {
22
+ chainId: bigint
23
+ constants: SafeConstants
24
+ constantsHex: { initializerHash: Hex; factory: Hex; initCodeHash: Hex }
25
+ safeProvider: SafeProvider
26
+ safeAccountConfig: { owners: string[]; threshold: number }
27
+ safeVersion: SafeVersion
28
+ isL1SafeSingleton?: boolean
29
+ }
30
+
31
+ /**
32
+ * zkSync Era and friends derive CREATE2 addresses with a different formula (spec §3.1).
33
+ * These are the chain IDs protocol-kit itself gates its zkSync formula on internally
34
+ * (ZKSYNC_MAINNET = 324, ZKSYNC_TESTNET = 300, ZKSYNC_LENS = 232), per the installed
35
+ * @safe-global/protocol-kit@8.0.5 source. protocol-kit additionally gates on
36
+ * `safeVersion <= 1.4.1`, which covers every version this CLI supports (1.4.1 and 1.3.0),
37
+ * so a plain set membership check on chain ID is sufficient here.
38
+ */
39
+ export const ZKSYNC_CHAIN_IDS: ReadonlySet<bigint> = new Set([324n, 300n, 232n])
40
+
41
+ /**
42
+ * Reads chainId and the three constants that stay fixed for a given
43
+ * (owners, threshold, safeVersion). Runs once on the main thread; workers get plain hex.
44
+ */
45
+ export async function loadSafeConstants(input: SetupInput): Promise<SafeSetup> {
46
+ const safeProvider = new SafeProvider({ provider: input.rpcUrl })
47
+ const chainId = await safeProvider.getChainId()
48
+
49
+ if (ZKSYNC_CHAIN_IDS.has(chainId)) {
50
+ throw new Error(
51
+ `chain ${chainId} is zkSync-based and derives contract addresses with a different formula; ` +
52
+ 'this tool would predict the wrong address. Use a standard EVM chain.',
53
+ )
54
+ }
55
+
56
+ const safeAccountConfig = { owners: input.owners, threshold: input.threshold }
57
+
58
+ const factoryContract = await getSafeProxyFactoryContract({
59
+ safeProvider,
60
+ safeVersion: input.safeVersion,
61
+ })
62
+ const safeContract = await getSafeContract({
63
+ safeProvider,
64
+ safeVersion: input.safeVersion,
65
+ isL1SafeSingleton: input.isL1SafeSingleton,
66
+ })
67
+
68
+ const initializer = await encodeSetupCallData({
69
+ safeProvider,
70
+ safeAccountConfig,
71
+ safeContract,
72
+ customSafeVersion: input.safeVersion,
73
+ })
74
+ const initializerHash = keccak256(initializer as Hex)
75
+
76
+ // proxyCreationCode() returns a single-element tuple, not a bare string.
77
+ const [proxyCreationCode] = await factoryContract.proxyCreationCode()
78
+ const encodedSingleton = safeProvider.encodeParameters('address', [safeContract.getAddress()])
79
+ const initCodeHash = keccak256(concat([proxyCreationCode as Hex, encodedSingleton as Hex]))
80
+ const factory = factoryContract.getAddress() as Hex
81
+
82
+ return {
83
+ chainId,
84
+ constants: {
85
+ initializerHash: hexToBytes(initializerHash),
86
+ factory: hexToBytes(factory),
87
+ initCodeHash: hexToBytes(initCodeHash),
88
+ },
89
+ constantsHex: { initializerHash, factory, initCodeHash },
90
+ safeProvider,
91
+ safeAccountConfig,
92
+ safeVersion: input.safeVersion,
93
+ isL1SafeSingleton: input.isL1SafeSingleton,
94
+ }
95
+ }
96
+
97
+ /** Cross-checks one mined result against protocol-kit. Run this every session (spec §11). */
98
+ export async function verifyWithProtocolKit(
99
+ setup: SafeSetup,
100
+ saltNonce: string,
101
+ address: string,
102
+ ): Promise<void> {
103
+ const predicted = await predictSafeAddress({
104
+ safeProvider: setup.safeProvider,
105
+ chainId: setup.chainId,
106
+ safeAccountConfig: setup.safeAccountConfig,
107
+ safeDeploymentConfig: { saltNonce, safeVersion: setup.safeVersion },
108
+ isL1SafeSingleton: setup.isL1SafeSingleton,
109
+ })
110
+ if (predicted.toLowerCase() !== address.toLowerCase()) {
111
+ throw new Error(
112
+ `self-check failed for saltNonce ${saltNonce}: fast derivation gave ${address}, ` +
113
+ `protocol-kit predictSafeAddress gave ${predicted.toLowerCase()}`,
114
+ )
115
+ }
116
+ }