Spaces:
Sleeping
Sleeping
import streamlit as st | |
import os | |
import secrets | |
import hashlib | |
import tempfile | |
import subprocess | |
import json | |
from starknet_py.net.account.account import Account | |
from starknet_py.net.models import StarknetChainId | |
from starknet_py.net.signer.stark_curve_signer import KeyPair | |
from starknet_py.net.gateway_client import GatewayClient | |
from starknet_py.contract import Contract | |
# Set page config | |
st.set_page_config( | |
page_title="StarkNet 開發工作流程", | |
page_icon="🌟", | |
layout="wide" | |
) | |
# Application title | |
st.title("StarkNet 開發完整工作流程") | |
st.markdown(""" | |
這個應用演示了進行 StarkNet 開發的完整流程,包括: | |
1. 編寫和編譯 Cairo 合約 | |
2. 生成 StarkNet 密鑰對和地址 | |
3. 與 StarkNet 進行交互 | |
""") | |
# Sidebar | |
st.sidebar.header("StarkNet 開發工具") | |
selected_section = st.sidebar.radio( | |
"選擇步驟", | |
["編寫和編譯合約", "生成密鑰對和地址", "與 StarkNet 交互"] | |
) | |
# Function to compile Cairo contract | |
def compile_cairo_contract(cairo_code): | |
with tempfile.NamedTemporaryFile(suffix='.cairo', delete=False) as tmp_file: | |
tmp_file.write(cairo_code.encode()) | |
tmp_file_path = tmp_file.name | |
output_file = f"{tmp_file_path}_compiled.json" | |
try: | |
result = subprocess.run( | |
["cairo-compile", tmp_file_path, "--output", output_file], | |
capture_output=True, | |
text=True, | |
check=True | |
) | |
# Read compiled contract | |
with open(output_file, 'r') as f: | |
compiled_contract = json.load(f) | |
# Clean up temporary files | |
os.remove(tmp_file_path) | |
os.remove(output_file) | |
return compiled_contract, result.stdout | |
except subprocess.CalledProcessError as e: | |
return None, e.stderr | |
except Exception as e: | |
return None, str(e) | |
# Function to generate StarkNet keys | |
def generate_starknet_keys(): | |
# Generate random private key (251 bits for StarkNet) | |
private_key = secrets.randbits(251) | |
private_key_hex = hex(private_key) | |
# Create key pair | |
key_pair = KeyPair.from_private_key(private_key) | |
# Get public key | |
public_key = key_pair.public_key | |
public_key_hex = hex(public_key) | |
# Try to get an appropriate chain ID | |
try: | |
chain_id = StarknetChainId.GOERLI | |
except AttributeError: | |
try: | |
chain_id = StarknetChainId.SEPOLIA | |
except AttributeError: | |
# Use a generic testnet ID if above networks are not available | |
chain_id = 1536727068981429685321 | |
# Create account object | |
account = Account( | |
address=0, # Will be populated after deployment | |
client=None, # Not needed for this example | |
key_pair=key_pair, | |
chain=chain_id | |
) | |
# Get account address | |
address = account.address | |
return { | |
"private_key": private_key_hex, | |
"public_key": public_key_hex, | |
"address": hex(address), | |
"chain_id": chain_id, | |
"key_pair": key_pair | |
} | |
# Section 1: Write and Compile Cairo Contract | |
if selected_section == "編寫和編譯合約": | |
st.header("1. 編寫和編譯 Cairo 合約") | |
# Default Cairo contract template | |
default_cairo_code = """%lang cairo | |
@external | |
func transfer{syscall_ptr: felt*, range_check_ptr}( | |
from_address: felt, | |
to_address: felt, | |
amount: felt | |
) -> (success: felt): | |
# 這只是一個簡單的模擬轉帳函數 | |
# 在實際應用中,您需要進行餘額檢查和狀態更新 | |
return (1) # 返回成功 | |
end | |
@view | |
func get_balance{syscall_ptr: felt*, range_check_ptr}( | |
address: felt | |
) -> (balance: felt): | |
# 這只是一個簡單的模擬餘額查詢函數 | |
# 在實際應用中,您需要從存儲中讀取真實的餘額 | |
return (1000) # 返回模擬餘額 | |
end | |
""" | |
cairo_code = st.text_area("Cairo 合約代碼", default_cairo_code, height=400) | |
if st.button("編譯合約"): | |
with st.spinner("正在編譯 Cairo 合約..."): | |
compiled_contract, output = compile_cairo_contract(cairo_code) | |
if compiled_contract: | |
st.success("合約編譯成功!") | |
st.code(json.dumps(compiled_contract, indent=2), language="json") | |
# Save the compiled contract to session state for later use | |
st.session_state['compiled_contract'] = compiled_contract | |
else: | |
st.error("合約編譯失敗!") | |
st.code(output) | |
# Section 2: Generate StarkNet Keys | |
elif selected_section == "生成密鑰對和地址": | |
st.header("2. 生成 StarkNet 密鑰對和地址") | |
if st.button("生成新的密鑰對"): | |
with st.spinner("正在生成 StarkNet 密鑰..."): | |
keys_info = generate_starknet_keys() | |
# Save keys to session state | |
st.session_state['keys_info'] = keys_info | |
# Display keys information | |
st.subheader("StarkNet 密鑰信息") | |
st.markdown(f""" | |
- **私鑰**: `{keys_info['private_key']}` | |
- **公鑰**: `{keys_info['public_key']}` | |
- **地址**: `{keys_info['address']}` | |
- **網絡**: `{keys_info['chain_id']}` | |
""") | |
# Option to download keys as text file | |
keys_text = f"""私鑰: {keys_info['private_key']} | |
公鑰: {keys_info['public_key']} | |
地址: {keys_info['address']} | |
網絡: {keys_info['chain_id']}""" | |
st.download_button( | |
label="下載密鑰信息", | |
data=keys_text, | |
file_name="starknet_keys.txt", | |
mime="text/plain" | |
) | |
# Display saved keys if available | |
if 'keys_info' in st.session_state: | |
st.subheader("已保存的 StarkNet 密鑰信息") | |
st.markdown(f""" | |
- **私鑰**: `{st.session_state['keys_info']['private_key']}` | |
- **公鑰**: `{st.session_state['keys_info']['public_key']}` | |
- **地址**: `{st.session_state['keys_info']['address']}` | |
- **網絡**: `{st.session_state['keys_info']['chain_id']}` | |
""") | |
# Section 3: Interact with StarkNet | |
elif selected_section == "與 StarkNet 交互": | |
st.header("3. 與 StarkNet 交互") | |
# Network selection | |
network = st.selectbox( | |
"選擇 StarkNet 網絡", | |
["goerli", "sepolia", "mainnet"] | |
) | |
# Contract address input | |
contract_address = st.text_input("合約地址 (十六進制)", "0x") | |
# Check if we have compiled contract and keys | |
has_contract = 'compiled_contract' in st.session_state | |
has_keys = 'keys_info' in st.session_state | |
if not has_contract: | |
st.warning("請先在「編寫和編譯合約」步驟中編譯合約") | |
if not has_keys: | |
st.warning("請先在「生成密鑰對和地址」步驟中生成密鑰") | |
# Contract interaction section | |
if has_contract and has_keys and st.text_input("合約地址") != "0x": | |
st.subheader("合約交互") | |
# Function selection | |
function_option = st.radio( | |
"選擇函數", | |
["transfer", "get_balance"] | |
) | |
if function_option == "transfer": | |
# Transfer function parameters | |
st.subheader("轉帳參數") | |
from_address = st.text_input("來源地址", value=st.session_state['keys_info']['address']) | |
to_address = st.text_input("目標地址", "0x") | |
amount = st.number_input("金額", min_value=1, value=100) | |
if st.button("執行轉帳"): | |
st.info("此功能在演示模式下,實際調用需要連接到 StarkNet 網絡") | |
# Here we would implement actual StarkNet interaction | |
st.code(f""" | |
# 實際執行代碼將如下: | |
client = GatewayClient(net="{network}") | |
contract = Contract( | |
address=int("{contract_address}", 16), | |
abi=st.session_state['compiled_contract']['abi'], | |
client=client | |
) | |
response = await account.execute( | |
calls=[ | |
contract.functions["transfer"].prepare( | |
from_address=int("{from_address}", 16), | |
to_address=int("{to_address}", 16), | |
amount={amount} | |
) | |
] | |
) | |
""", language="python") | |
elif function_option == "get_balance": | |
# Get balance function parameters | |
st.subheader("餘額查詢參數") | |
address = st.text_input("查詢地址", value=st.session_state['keys_info']['address']) | |
if st.button("查詢餘額"): | |
st.info("此功能在演示模式下,實際調用需要連接到 StarkNet 網絡") | |
# Here we would implement actual StarkNet interaction | |
st.code(f""" | |
# 實際執行代碼將如下: | |
client = GatewayClient(net="{network}") | |
contract = Contract( | |
address=int("{contract_address}", 16), | |
abi=st.session_state['compiled_contract']['abi'], | |
client=client | |
) | |
balance = await contract.functions["get_balance"].call( | |
address=int("{address}", 16) | |
) | |
""", language="python") | |
# Show a mock result | |
st.success("模擬結果: 餘額 = 1000") | |
# Footer | |
st.markdown("---") | |
st.markdown("### StarkNet 開發資源") | |
st.markdown(""" | |
- [StarkNet 官方文檔](https://docs.starknet.io) | |
- [Cairo 語言文檔](https://cairo-lang.org) | |
- [StarkNet-py 庫文檔](https://github.com/software-mansion/starknet.py) | |
""") |