Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,117 +1,54 @@
|
|
1 |
import streamlit as st
|
2 |
-
import os
|
3 |
import secrets
|
4 |
import hashlib
|
5 |
-
import
|
6 |
import subprocess
|
7 |
import json
|
8 |
from starknet_py.net.account.account import Account
|
9 |
from starknet_py.net.models import StarknetChainId
|
10 |
from starknet_py.net.signer.stark_curve_signer import KeyPair
|
11 |
-
from starknet_py.net.gateway_client import GatewayClient
|
12 |
-
from starknet_py.contract import Contract
|
13 |
|
14 |
-
# Set page config
|
15 |
st.set_page_config(
|
16 |
-
page_title="StarkNet
|
17 |
page_icon="🌟",
|
18 |
layout="wide"
|
19 |
)
|
20 |
|
21 |
-
|
22 |
-
st.
|
23 |
-
st.markdown("""
|
24 |
-
這個應用演示了進行 StarkNet 開發的完整流程,包括:
|
25 |
-
1. 編寫和編譯 Cairo 合約
|
26 |
-
2. 生成 StarkNet 密鑰對和地址
|
27 |
-
3. 與 StarkNet 進行交互
|
28 |
-
""")
|
29 |
|
30 |
-
# Sidebar
|
31 |
-
st.sidebar.
|
32 |
-
|
33 |
-
"
|
34 |
-
|
35 |
-
|
|
|
|
|
36 |
|
37 |
-
#
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
output_file = f"{tmp_file_path}_compiled.json"
|
44 |
-
|
45 |
-
try:
|
46 |
-
result = subprocess.run(
|
47 |
-
["cairo-compile", tmp_file_path, "--output", output_file],
|
48 |
-
capture_output=True,
|
49 |
-
text=True,
|
50 |
-
check=True
|
51 |
-
)
|
52 |
-
|
53 |
-
# Read compiled contract
|
54 |
-
with open(output_file, 'r') as f:
|
55 |
-
compiled_contract = json.load(f)
|
56 |
-
|
57 |
-
# Clean up temporary files
|
58 |
-
os.remove(tmp_file_path)
|
59 |
-
os.remove(output_file)
|
60 |
-
|
61 |
-
return compiled_contract, result.stdout
|
62 |
-
except subprocess.CalledProcessError as e:
|
63 |
-
return None, e.stderr
|
64 |
-
except Exception as e:
|
65 |
-
return None, str(e)
|
66 |
-
|
67 |
-
# Function to generate StarkNet keys
|
68 |
-
def generate_starknet_keys():
|
69 |
-
# Generate random private key (251 bits for StarkNet)
|
70 |
-
private_key = secrets.randbits(251)
|
71 |
-
private_key_hex = hex(private_key)
|
72 |
-
|
73 |
-
# Create key pair
|
74 |
-
key_pair = KeyPair.from_private_key(private_key)
|
75 |
-
|
76 |
-
# Get public key
|
77 |
-
public_key = key_pair.public_key
|
78 |
-
public_key_hex = hex(public_key)
|
79 |
-
|
80 |
-
# Try to get an appropriate chain ID
|
81 |
-
try:
|
82 |
-
chain_id = StarknetChainId.GOERLI
|
83 |
-
except AttributeError:
|
84 |
-
try:
|
85 |
-
chain_id = StarknetChainId.SEPOLIA
|
86 |
-
except AttributeError:
|
87 |
-
# Use a generic testnet ID if above networks are not available
|
88 |
-
chain_id = 1536727068981429685321
|
89 |
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
client=None, # Not needed for this example
|
94 |
-
key_pair=key_pair,
|
95 |
-
chain=chain_id
|
96 |
-
)
|
97 |
|
98 |
-
|
99 |
-
|
100 |
|
101 |
-
|
102 |
-
"private_key": private_key_hex,
|
103 |
-
"public_key": public_key_hex,
|
104 |
-
"address": hex(address),
|
105 |
-
"chain_id": chain_id,
|
106 |
-
"key_pair": key_pair
|
107 |
-
}
|
108 |
|
109 |
-
#
|
110 |
-
|
111 |
-
st.header("
|
112 |
|
113 |
-
# Default
|
114 |
-
|
|
|
115 |
|
116 |
@external
|
117 |
func transfer{syscall_ptr: felt*, range_check_ptr}(
|
@@ -119,177 +56,227 @@ func transfer{syscall_ptr: felt*, range_check_ptr}(
|
|
119 |
to_address: felt,
|
120 |
amount: felt
|
121 |
) -> (success: felt):
|
122 |
-
#
|
123 |
-
#
|
124 |
-
return (1) #
|
125 |
end
|
126 |
|
127 |
@view
|
128 |
func get_balance{syscall_ptr: felt*, range_check_ptr}(
|
129 |
address: felt
|
130 |
) -> (balance: felt):
|
131 |
-
#
|
132 |
-
#
|
133 |
-
return (1000) #
|
134 |
end
|
135 |
"""
|
136 |
|
137 |
-
|
|
|
|
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
142 |
|
143 |
-
|
144 |
-
|
145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
|
147 |
-
#
|
148 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
149 |
else:
|
150 |
-
st.error("
|
151 |
-
|
|
|
152 |
|
153 |
-
#
|
154 |
-
elif
|
155 |
-
st.header("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
-
|
158 |
-
|
159 |
-
|
|
|
|
|
|
|
160 |
|
161 |
-
|
162 |
-
st.session_state['
|
163 |
|
164 |
-
|
165 |
-
st.
|
166 |
-
st.markdown(f"""
|
167 |
-
- **私鑰**: `{keys_info['private_key']}`
|
168 |
-
- **公鑰**: `{keys_info['public_key']}`
|
169 |
-
- **地址**: `{keys_info['address']}`
|
170 |
-
- **網絡**: `{keys_info['chain_id']}`
|
171 |
-
""")
|
172 |
|
173 |
-
|
174 |
-
|
175 |
-
公鑰: {keys_info['public_key']}
|
176 |
-
地址: {keys_info['address']}
|
177 |
-
網絡: {keys_info['chain_id']}"""
|
178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
st.download_button(
|
180 |
-
label="
|
181 |
-
data=
|
182 |
file_name="starknet_keys.txt",
|
183 |
mime="text/plain"
|
184 |
)
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
st.markdown(f"""
|
190 |
-
- **私鑰**: `{st.session_state['keys_info']['private_key']}`
|
191 |
-
- **公鑰**: `{st.session_state['keys_info']['public_key']}`
|
192 |
-
- **地址**: `{st.session_state['keys_info']['address']}`
|
193 |
-
- **網絡**: `{st.session_state['keys_info']['chain_id']}`
|
194 |
-
""")
|
195 |
|
196 |
-
#
|
197 |
-
elif
|
198 |
-
st.header("
|
199 |
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
["goerli", "sepolia", "mainnet"]
|
204 |
-
)
|
205 |
-
|
206 |
-
# Contract address input
|
207 |
-
contract_address = st.text_input("合約地址 (十六進制)", "0x")
|
208 |
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
|
213 |
-
|
214 |
-
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
st.subheader("合約交互")
|
222 |
-
|
223 |
-
# Function selection
|
224 |
-
function_option = st.radio(
|
225 |
-
"選擇函數",
|
226 |
-
["transfer", "get_balance"]
|
227 |
-
)
|
228 |
-
|
229 |
-
if function_option == "transfer":
|
230 |
-
# Transfer function parameters
|
231 |
-
st.subheader("轉帳參數")
|
232 |
-
from_address = st.text_input("來源地址", value=st.session_state['keys_info']['address'])
|
233 |
-
to_address = st.text_input("目標地址", "0x")
|
234 |
-
amount = st.number_input("金額", min_value=1, value=100)
|
235 |
-
|
236 |
-
if st.button("執行轉帳"):
|
237 |
-
st.info("此功能在演示模式下,實際調用需要連接到 StarkNet 網絡")
|
238 |
-
|
239 |
-
# Here we would implement actual StarkNet interaction
|
240 |
-
st.code(f"""
|
241 |
-
# 實際��行代碼將如下:
|
242 |
-
client = GatewayClient(net="{network}")
|
243 |
|
244 |
-
|
245 |
-
|
246 |
-
abi=st.session_state['compiled_contract']['abi'],
|
247 |
-
client=client
|
248 |
-
)
|
249 |
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
from_address=int("{from_address}", 16),
|
254 |
-
to_address=int("{to_address}", 16),
|
255 |
-
amount={amount}
|
256 |
-
)
|
257 |
-
]
|
258 |
-
)
|
259 |
-
""", language="python")
|
260 |
-
|
261 |
-
elif function_option == "get_balance":
|
262 |
-
# Get balance function parameters
|
263 |
-
st.subheader("餘額查詢參數")
|
264 |
-
address = st.text_input("查詢地址", value=st.session_state['keys_info']['address'])
|
265 |
-
|
266 |
-
if st.button("查詢餘額"):
|
267 |
-
st.info("此功能在演示模式下,實際調用需要連接到 StarkNet 網絡")
|
268 |
-
|
269 |
-
# Here we would implement actual StarkNet interaction
|
270 |
-
st.code(f"""
|
271 |
-
# 實際執行代碼將如下:
|
272 |
-
client = GatewayClient(net="{network}")
|
273 |
|
|
|
274 |
contract = Contract(
|
275 |
-
address=
|
276 |
-
abi=
|
277 |
-
client=client
|
278 |
)
|
279 |
|
280 |
-
|
281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
)
|
283 |
-
""", language="python")
|
284 |
-
|
285 |
-
# Show a mock result
|
286 |
-
st.success("模擬結果: 餘額 = 1000")
|
287 |
|
288 |
-
#
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
|
|
2 |
import secrets
|
3 |
import hashlib
|
4 |
+
import os
|
5 |
import subprocess
|
6 |
import json
|
7 |
from starknet_py.net.account.account import Account
|
8 |
from starknet_py.net.models import StarknetChainId
|
9 |
from starknet_py.net.signer.stark_curve_signer import KeyPair
|
|
|
|
|
10 |
|
|
|
11 |
st.set_page_config(
|
12 |
+
page_title="StarkNet Development Toolkit",
|
13 |
page_icon="🌟",
|
14 |
layout="wide"
|
15 |
)
|
16 |
|
17 |
+
st.title("StarkNet Development Toolkit")
|
18 |
+
st.markdown("A complete workflow for StarkNet development including contract compilation, key generation, and more.")
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
+
# Sidebar for navigation
|
21 |
+
st.sidebar.title("Navigation")
|
22 |
+
page = st.sidebar.radio("Choose a step:", [
|
23 |
+
"1. Introduction",
|
24 |
+
"2. Write & Compile Contract",
|
25 |
+
"3. Generate Keys",
|
26 |
+
"4. Interact with StarkNet"
|
27 |
+
])
|
28 |
|
29 |
+
# Page 1: Introduction
|
30 |
+
if page == "1. Introduction":
|
31 |
+
st.header("Introduction to StarkNet Development")
|
32 |
+
st.write("""
|
33 |
+
StarkNet is a permissionless decentralized ZK-Rollup operating as an L2 network over Ethereum.
|
34 |
+
This application helps you with the full development workflow:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
+
1. **Write and compile Cairo contracts** - Create smart contracts with Cairo language
|
37 |
+
2. **Generate key pairs and addresses** - Create cryptographic keys for StarkNet
|
38 |
+
3. **Interact with StarkNet** - Deploy contracts and interact with the network
|
|
|
|
|
|
|
|
|
39 |
|
40 |
+
Use the sidebar to navigate through different steps of the workflow.
|
41 |
+
""")
|
42 |
|
43 |
+
st.info("This is a development tool. For production use, please ensure proper security measures.")
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
+
# Page 2: Write & Compile Contract
|
46 |
+
elif page == "2. Write & Compile Contract":
|
47 |
+
st.header("Write & Compile Cairo Contract")
|
48 |
|
49 |
+
# Default example contract
|
50 |
+
default_contract = """
|
51 |
+
%lang cairo
|
52 |
|
53 |
@external
|
54 |
func transfer{syscall_ptr: felt*, range_check_ptr}(
|
|
|
56 |
to_address: felt,
|
57 |
amount: felt
|
58 |
) -> (success: felt):
|
59 |
+
# This is just a simple simulated transfer function
|
60 |
+
# In a real application, you would need to perform balance checks and state updates
|
61 |
+
return (1) # Return success
|
62 |
end
|
63 |
|
64 |
@view
|
65 |
func get_balance{syscall_ptr: felt*, range_check_ptr}(
|
66 |
address: felt
|
67 |
) -> (balance: felt):
|
68 |
+
# This is just a simple simulated balance query function
|
69 |
+
# In a real application, you would read the actual balance from storage
|
70 |
+
return (1000) # Return simulated balance
|
71 |
end
|
72 |
"""
|
73 |
|
74 |
+
# Contract editor
|
75 |
+
st.subheader("Contract Editor")
|
76 |
+
contract_code = st.text_area("Edit your Cairo contract:", default_contract, height=400)
|
77 |
|
78 |
+
# Compile button
|
79 |
+
if st.button("Compile Contract"):
|
80 |
+
try:
|
81 |
+
# Save the contract to a file
|
82 |
+
with open('token_contract.cairo', 'w') as file:
|
83 |
+
file.write(contract_code)
|
84 |
|
85 |
+
# Run the compilation command
|
86 |
+
with st.spinner("Compiling..."):
|
87 |
+
result = subprocess.run(
|
88 |
+
["cairo-compile", "token_contract.cairo", "--output", "token_compiled.json"],
|
89 |
+
capture_output=True,
|
90 |
+
text=True
|
91 |
+
)
|
92 |
+
|
93 |
+
if result.returncode == 0:
|
94 |
+
st.success("Contract compiled successfully!")
|
95 |
+
|
96 |
+
# Display compilation output
|
97 |
+
with open("token_compiled.json", "r") as f:
|
98 |
+
compiled_json = json.load(f)
|
99 |
|
100 |
+
# Allow downloading the compiled contract
|
101 |
+
st.download_button(
|
102 |
+
label="Download Compiled Contract",
|
103 |
+
data=json.dumps(compiled_json, indent=2),
|
104 |
+
file_name="token_compiled.json",
|
105 |
+
mime="application/json"
|
106 |
+
)
|
107 |
else:
|
108 |
+
st.error(f"Compilation failed: {result.stderr}")
|
109 |
+
except Exception as e:
|
110 |
+
st.error(f"Error during compilation: {str(e)}")
|
111 |
|
112 |
+
# Page 3: Generate Keys
|
113 |
+
elif page == "3. Generate Keys":
|
114 |
+
st.header("Generate StarkNet Keys")
|
115 |
+
|
116 |
+
col1, col2 = st.columns(2)
|
117 |
+
|
118 |
+
with col1:
|
119 |
+
st.subheader("Key Generation")
|
120 |
+
|
121 |
+
# Network selection
|
122 |
+
st.write("Select StarkNet Network:")
|
123 |
+
network_options = {
|
124 |
+
"Goerli Testnet": "GOERLI",
|
125 |
+
"Sepolia Testnet": "SEPOLIA",
|
126 |
+
"Mainnet": "MAINNET",
|
127 |
+
"Custom": "CUSTOM"
|
128 |
+
}
|
129 |
+
network = st.selectbox("Network", list(network_options.keys()))
|
130 |
+
|
131 |
+
if network == "Custom":
|
132 |
+
chain_id_value = st.number_input("Enter Chain ID value:", value=1536727068981429685321)
|
133 |
+
else:
|
134 |
+
try:
|
135 |
+
chain_id_attr = getattr(StarknetChainId, network_options[network])
|
136 |
+
chain_id_value = chain_id_attr.value
|
137 |
+
except (AttributeError, KeyError):
|
138 |
+
chain_id_value = 1536727068981429685321
|
139 |
+
st.warning(f"Using default chain ID: {chain_id_value}")
|
140 |
+
|
141 |
+
if st.button("Generate New Keys"):
|
142 |
+
with st.spinner("Generating..."):
|
143 |
+
# Generate a random private key
|
144 |
+
private_key = secrets.randbits(251)
|
145 |
+
private_key_hex = hex(private_key)
|
146 |
+
|
147 |
+
# Create key pair
|
148 |
+
key_pair = KeyPair.from_private_key(private_key)
|
149 |
+
|
150 |
+
# Get the public key
|
151 |
+
public_key = key_pair.public_key
|
152 |
+
public_key_hex = hex(public_key)
|
153 |
+
|
154 |
+
# Create account object
|
155 |
+
account = Account(
|
156 |
+
address=0, # Will be populated after deployment
|
157 |
+
client=None, # No client needed in this example
|
158 |
+
key_pair=key_pair,
|
159 |
+
chain=chain_id_value
|
160 |
+
)
|
161 |
+
|
162 |
+
# Get the account address
|
163 |
+
address = account.address
|
164 |
+
|
165 |
+
# Store in session state
|
166 |
+
st.session_state['private_key'] = private_key_hex
|
167 |
+
st.session_state['public_key'] = public_key_hex
|
168 |
+
st.session_state['address'] = hex(address)
|
169 |
+
st.session_state['chain_id'] = chain_id_value
|
170 |
+
|
171 |
+
st.success("Keys generated successfully!")
|
172 |
|
173 |
+
with col2:
|
174 |
+
st.subheader("Key Information")
|
175 |
+
|
176 |
+
if 'private_key' in st.session_state:
|
177 |
+
st.markdown("**Private Key:**")
|
178 |
+
st.code(st.session_state['private_key'])
|
179 |
|
180 |
+
st.markdown("**Public Key:**")
|
181 |
+
st.code(st.session_state['public_key'])
|
182 |
|
183 |
+
st.markdown("**Address:**")
|
184 |
+
st.code(st.session_state['address'])
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
|
186 |
+
st.markdown("**Chain ID:**")
|
187 |
+
st.code(str(st.session_state['chain_id']))
|
|
|
|
|
|
|
188 |
|
189 |
+
# Create downloadable key file
|
190 |
+
key_info = f"""Private Key: {st.session_state['private_key']}
|
191 |
+
Public Key: {st.session_state['public_key']}
|
192 |
+
Address: {st.session_state['address']}
|
193 |
+
Chain ID: {st.session_state['chain_id']}
|
194 |
+
"""
|
195 |
st.download_button(
|
196 |
+
label="Download Key Information",
|
197 |
+
data=key_info,
|
198 |
file_name="starknet_keys.txt",
|
199 |
mime="text/plain"
|
200 |
)
|
201 |
+
|
202 |
+
st.warning("⚠️ Keep your private key secure! Never share it with anyone.")
|
203 |
+
else:
|
204 |
+
st.info("Generate keys to see the information here.")
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
|
206 |
+
# Page 4: Interact with StarkNet
|
207 |
+
elif page == "4. Interact with StarkNet":
|
208 |
+
st.header("Interact with StarkNet")
|
209 |
|
210 |
+
st.info("""
|
211 |
+
This section provides sample code for interacting with StarkNet.
|
212 |
+
For actual interaction, you would need:
|
|
|
|
|
|
|
|
|
|
|
213 |
|
214 |
+
1. A deployed contract address
|
215 |
+
2. The contract ABI
|
216 |
+
3. A funded account
|
217 |
|
218 |
+
Below is sample code that you can modify for your needs.
|
219 |
+
""")
|
220 |
|
221 |
+
interaction_code = """
|
222 |
+
# StarkNet Interaction Example
|
223 |
+
from starknet_py.net.gateway_client import GatewayClient
|
224 |
+
from starknet_py.net.models import StarknetChainId
|
225 |
+
from starknet_py.contract import Contract
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
|
227 |
+
# Set up StarkNet client (using Goerli testnet)
|
228 |
+
client = GatewayClient(net="goerli") # Or "mainnet" for the mainnet
|
|
|
|
|
|
|
229 |
|
230 |
+
# Replace with your contract's address and ABI
|
231 |
+
contract_address = 0x123456789 # Replace with the real contract address
|
232 |
+
contract_abi = [...] # Contract's ABI, typically obtained from compilation output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
|
234 |
+
# Load the deployed contract
|
235 |
contract = Contract(
|
236 |
+
address=contract_address,
|
237 |
+
abi=contract_abi,
|
238 |
+
client=client,
|
239 |
)
|
240 |
|
241 |
+
# Replace with your account information
|
242 |
+
private_key = "YOUR_PRIVATE_KEY"
|
243 |
+
key_pair = KeyPair.from_private_key(int(private_key, 16))
|
244 |
+
account = Account(
|
245 |
+
address=0x..., # Your account address
|
246 |
+
client=client,
|
247 |
+
key_pair=key_pair,
|
248 |
+
chain=StarknetChainId.GOERLI
|
249 |
)
|
|
|
|
|
|
|
|
|
250 |
|
251 |
+
# Example: Call a contract function
|
252 |
+
async def transfer_tokens():
|
253 |
+
response = await account.execute(
|
254 |
+
calls=[
|
255 |
+
contract.functions["transfer"].prepare(
|
256 |
+
from_address=account.address,
|
257 |
+
to_address=0x987654321, # Receiver address
|
258 |
+
amount=100
|
259 |
+
)
|
260 |
+
]
|
261 |
+
)
|
262 |
+
print(f"Transaction hash: {response.transaction_hash}")
|
263 |
+
|
264 |
+
# Example: Query contract state
|
265 |
+
async def check_balance(address):
|
266 |
+
balance = await contract.functions["get_balance"].call(
|
267 |
+
address=address
|
268 |
+
)
|
269 |
+
print(f"Balance: {balance.balance}")
|
270 |
+
"""
|
271 |
+
|
272 |
+
st.code(interaction_code, language="python")
|
273 |
+
|
274 |
+
st.subheader("Resources")
|
275 |
+
st.markdown("""
|
276 |
+
- [StarkNet Documentation](https://docs.starknet.io/)
|
277 |
+
- [Cairo Programming Language](https://cairo-lang.org/)
|
278 |
+
- [starknet-py Library](https://github.com/software-mansion/starknet.py)
|
279 |
+
""")
|
280 |
+
|
281 |
+
st.sidebar.markdown("---")
|
282 |
+
st.sidebar.info("This application is for educational purposes. Always follow best security practices when working with cryptographic keys.")
|