-
Notifications
You must be signed in to change notification settings - Fork 429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: config starknet chain #5228
base: main
Are you sure you want to change the base?
Changes from all commits
dedaa34
4f541e3
f10e542
36e2b00
182fc36
c6cc7e9
e13e08a
0546e10
2ee37d4
3f58da3
7770bd7
7d6a156
b90d0f7
c33d90f
e3fba6a
a45c50a
576744b
8f7d04d
e16b0e3
5f380e5
8ac3b22
074e7d4
b48be06
ed0f5a9
8090e97
af971af
814bdb0
a2f1528
8e6e976
7d4f834
af1cede
c945d53
a63d411
f5d8d47
7130424
ecb73ff
7702247
ee40565
52f0d55
1ad1470
055b2fd
51be725
32b0a7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
'@hyperlane-xyz/utils': minor | ||
'@hyperlane-xyz/sdk': minor | ||
'@hyperlane-xyz/widgets': patch | ||
--- | ||
|
||
Added Starknet protocol support |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/cli': minor | ||
--- | ||
|
||
Add Starknet protocol support to the chain configuration |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,10 @@ | ||
import { confirm, input, select } from '@inquirer/prompts'; | ||
import { ethers } from 'ethers'; | ||
import { keccak256 } from 'ethers/lib/utils.js'; | ||
import { | ||
Provider as StarknetProvider, | ||
provider as starknetProvider, | ||
} from 'starknet'; | ||
import { stringify as yamlStringify } from 'yaml'; | ||
|
||
import { | ||
|
@@ -10,13 +15,22 @@ import { | |
ExplorerFamily, | ||
ZChainName, | ||
} from '@hyperlane-xyz/sdk'; | ||
import { ProtocolType } from '@hyperlane-xyz/utils'; | ||
import { ProtocolType, assert, isAddressStarknet } from '@hyperlane-xyz/utils'; | ||
|
||
import { CommandContext } from '../context/types.js'; | ||
import { errorRed, log, logBlue, logGreen } from '../logger.js'; | ||
import { indentYamlOrJson, readYamlOrJson } from '../utils/files.js'; | ||
import { detectAndConfirmOrPrompt } from '../utils/input.js'; | ||
|
||
// Add constants at the top of the file | ||
const SUPPORTED_PROTOCOLS = [ | ||
ProtocolType.Ethereum, | ||
ProtocolType.Starknet, | ||
] as const; | ||
|
||
// Add type for supported protocols | ||
type SupportedProtocol = (typeof SUPPORTED_PROTOCOLS)[number]; | ||
|
||
export function readChainConfigs(filePath: string) { | ||
log(`Reading file configs in ${filePath}`); | ||
const chainMetadata = readYamlOrJson<ChainMetadata>(filePath); | ||
|
@@ -49,16 +63,25 @@ export async function createChainConfig({ | |
}) { | ||
logBlue('Creating a new chain config'); | ||
|
||
const protocol = (await select({ | ||
message: 'Select the chain protocol type:', | ||
choices: SUPPORTED_PROTOCOLS.map((value) => ({ value })), | ||
pageSize: SUPPORTED_PROTOCOLS.length, | ||
})) as SupportedProtocol; | ||
|
||
assert( | ||
SUPPORTED_PROTOCOLS.includes(protocol), | ||
`Protocol type ${protocol} not supported. Supported protocols: ${SUPPORTED_PROTOCOLS.join( | ||
', ', | ||
)}`, | ||
); | ||
|
||
const rpcUrl = await detectAndConfirmOrPrompt( | ||
async () => { | ||
await new ethers.providers.JsonRpcProvider().getNetwork(); | ||
return ethers.providers.JsonRpcProvider.defaultUrl(); | ||
}, | ||
createProtocolDefaultProviderDetector(protocol), | ||
'Enter http or https', | ||
'rpc url', | ||
'JSON RPC provider', | ||
); | ||
const provider = new ethers.providers.JsonRpcProvider(rpcUrl); | ||
|
||
const name = await input({ | ||
message: 'Enter chain name (one word, lower case)', | ||
|
@@ -70,55 +93,58 @@ export async function createChainConfig({ | |
default: name[0].toUpperCase() + name.slice(1), | ||
}); | ||
|
||
const chainId = parseInt( | ||
const chainId = formatChainIdBasedOnProtocol( | ||
await detectAndConfirmOrPrompt( | ||
async () => { | ||
const network = await provider.getNetwork(); | ||
return network.chainId.toString(); | ||
}, | ||
'Enter a (number)', | ||
createProtocolChainIdDetector(protocol, rpcUrl), | ||
protocol === ProtocolType.Starknet ? 'Enter a (hex)' : 'Enter a (number)', | ||
'chain id', | ||
'JSON RPC provider', | ||
), | ||
10, | ||
protocol, | ||
); | ||
|
||
const isTestnet = await confirm({ | ||
message: | ||
'Is this chain a testnet (a chain used for testing & development)?', | ||
}); | ||
|
||
const technicalStack = (await select({ | ||
choices: Object.entries(ChainTechnicalStack).map(([_, value]) => ({ | ||
value, | ||
})), | ||
message: 'Select the chain technical stack', | ||
pageSize: 10, | ||
})) as ChainTechnicalStack; | ||
|
||
const arbitrumNitroMetadata: Pick<ChainMetadata, 'index'> = {}; | ||
if (technicalStack === ChainTechnicalStack.ArbitrumNitro) { | ||
const indexFrom = await detectAndConfirmOrPrompt( | ||
async () => { | ||
return (await provider.getBlockNumber()).toString(); | ||
}, | ||
`Enter`, | ||
'starting block number for indexing', | ||
'JSON RPC provider', | ||
); | ||
let technicalStack: ChainTechnicalStack | undefined; | ||
let arbitrumNitroMetadata: Pick<ChainMetadata, 'index'> = {}; | ||
|
||
arbitrumNitroMetadata.index = { | ||
from: parseInt(indexFrom), | ||
}; | ||
if (protocol === ProtocolType.Ethereum) { | ||
technicalStack = (await select({ | ||
choices: Object.entries(ChainTechnicalStack).map(([_, value]) => ({ | ||
value, | ||
})), | ||
message: 'Select the chain technical stack', | ||
pageSize: 10, | ||
})) as ChainTechnicalStack; | ||
|
||
if (technicalStack === ChainTechnicalStack.ArbitrumNitro) { | ||
const provider = new ethers.providers.JsonRpcProvider(rpcUrl); | ||
const indexFrom = await detectAndConfirmOrPrompt( | ||
async () => { | ||
return (await provider.getBlockNumber()).toString(); | ||
}, | ||
`Enter`, | ||
'starting block number for indexing', | ||
'JSON RPC provider', | ||
); | ||
|
||
arbitrumNitroMetadata.index = { | ||
from: parseInt(indexFrom), | ||
}; | ||
} | ||
} | ||
|
||
const metadata: ChainMetadata = { | ||
name, | ||
displayName, | ||
chainId, | ||
domainId: chainId, | ||
protocol: ProtocolType.Ethereum, | ||
technicalStack, | ||
domainId: | ||
typeof chainId === 'string' ? stringChainIdToDomainId(chainId) : chainId, | ||
protocol: protocol, | ||
...(technicalStack && { technicalStack }), | ||
rpcUrls: [{ http: rpcUrl }], | ||
isTestnet, | ||
...arbitrumNitroMetadata, | ||
|
@@ -268,12 +294,16 @@ async function addGasConfig(metadata: ChainMetadata): Promise<void> { | |
} | ||
|
||
async function addNativeTokenConfig(metadata: ChainMetadata): Promise<void> { | ||
const wantNativeConfig = await confirm({ | ||
default: false, | ||
message: | ||
'Do you want to set native token properties for this chain config (defaults to ETH)', | ||
}); | ||
let symbol, name, decimals; | ||
const isStarknet = metadata.protocol === ProtocolType.Starknet; | ||
const wantNativeConfig = | ||
isStarknet || | ||
(await confirm({ | ||
default: false, | ||
message: | ||
'Do you want to set native token properties for this chain config (defaults to ETH)', | ||
})); | ||
|
||
let symbol, name, decimals, denom; | ||
if (wantNativeConfig) { | ||
symbol = await input({ | ||
message: "Enter the native token's symbol:", | ||
|
@@ -284,11 +314,68 @@ async function addNativeTokenConfig(metadata: ChainMetadata): Promise<void> { | |
decimals = await input({ | ||
message: "Enter the native token's decimals:", | ||
}); | ||
|
||
if (isStarknet) { | ||
denom = await input({ | ||
message: "Enter the native token's address:", | ||
validate: (value) => isAddressStarknet(value), | ||
}); | ||
} | ||
} | ||
|
||
metadata.nativeToken = { | ||
symbol: symbol ?? 'ETH', | ||
name: name ?? 'Ether', | ||
decimals: decimals ? parseInt(decimals, 10) : 18, | ||
...(denom && { denom }), // Only include denom if it was provided | ||
}; | ||
} | ||
|
||
// Update function signature | ||
function createProtocolDefaultProviderDetector(protocol: SupportedProtocol) { | ||
switch (protocol) { | ||
case ProtocolType.Ethereum: | ||
return async () => { | ||
return ethers.providers.JsonRpcProvider.defaultUrl(); | ||
}; | ||
case ProtocolType.Starknet: | ||
return async () => { | ||
return starknetProvider.getDefaultNodeUrl(); | ||
}; | ||
} | ||
} | ||
|
||
// Add return type annotations | ||
function createProtocolChainIdDetector( | ||
protocol: SupportedProtocol, | ||
rpcUrl: string, | ||
) { | ||
return async () => { | ||
switch (protocol) { | ||
case ProtocolType.Ethereum: { | ||
const network = await new ethers.providers.JsonRpcProvider( | ||
rpcUrl, | ||
).getNetwork(); | ||
return network.chainId.toString(); | ||
} | ||
case ProtocolType.Starknet: | ||
return new StarknetProvider({ nodeUrl: rpcUrl }).getChainId(); | ||
} | ||
}; | ||
} | ||
|
||
function formatChainIdBasedOnProtocol(chainId: string, protocol: ProtocolType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: formatChainIdForProtocol There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and should that be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right better to be |
||
if (protocol === ProtocolType.Starknet) return chainId; | ||
return parseInt(chainId, 10); | ||
} | ||
|
||
/** | ||
* Converts a string chain ID to a numeric domain ID using keccak256 | ||
* @param chainId The chain ID string to convert | ||
* @returns A numeric domain ID derived from the first 4 bytes of the hash | ||
*/ | ||
function stringChainIdToDomainId(chainId: string): number { | ||
const hash = keccak256(chainId); | ||
// keccak256 should never return null for a string input | ||
return parseInt(hash.slice(2, 10), 16); // Take first 4 bytes after '0x' | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any way to fetch this for the user without requiring input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On starknet mainnet there are two native tokens for fees - STRK and ETH, and AFAIK Paradex chain has an native token ETH address completely different from starknet mainnet @mshojaei-txfusion correct me if I am wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did not find any address on
starknet.js
to import, but we may be able to add a default native token address for starknet, and ask user to change it in case it was different.