foundry-client 1.0.0__tar.gz
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.
- foundry_client-1.0.0/LICENSE +21 -0
- foundry_client-1.0.0/PKG-INFO +311 -0
- foundry_client-1.0.0/README.md +281 -0
- foundry_client-1.0.0/foundry_client/__init__.py +4 -0
- foundry_client-1.0.0/foundry_client/client.py +262 -0
- foundry_client-1.0.0/foundry_client.egg-info/PKG-INFO +311 -0
- foundry_client-1.0.0/foundry_client.egg-info/SOURCES.txt +10 -0
- foundry_client-1.0.0/foundry_client.egg-info/dependency_links.txt +1 -0
- foundry_client-1.0.0/foundry_client.egg-info/requires.txt +3 -0
- foundry_client-1.0.0/foundry_client.egg-info/top_level.txt +1 -0
- foundry_client-1.0.0/setup.cfg +4 -0
- foundry_client-1.0.0/setup.py +31 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FoundryNet
|
|
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.
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foundry-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for FoundryNet - Universal DePIN Protocol for Work Settlement
|
|
5
|
+
Home-page: https://github.com/foundrynet/foundry_net_MINT
|
|
6
|
+
Author: FoundryNet
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: PyNaCl>=1.5.0
|
|
19
|
+
Requires-Dist: base58>=2.1.1
|
|
20
|
+
Requires-Dist: requests>=2.31.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
FoundryNet
|
|
32
|
+
Earn MINT tokens for verified manufacturing work.
|
|
33
|
+
FoundryNet rewards 3D printers, CNC machines, robots, and production equipment with cryptocurrency for completing jobs. No staking, no capital required—just connect your machine and start earning.
|
|
34
|
+
|
|
35
|
+
Quick Start
|
|
36
|
+
Installation
|
|
37
|
+
bashnpm install tweetnacl bs58 uuid
|
|
38
|
+
Download foundry-client.js from the GitHub repository.
|
|
39
|
+
Basic Usage
|
|
40
|
+
javascriptimport { FoundryClient } from './foundry-client.js';
|
|
41
|
+
|
|
42
|
+
// Initialize client
|
|
43
|
+
const client = new FoundryClient({
|
|
44
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
45
|
+
debug: true
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Register machine (first time only - saves credentials locally)
|
|
49
|
+
await client.init({
|
|
50
|
+
type: '3d-printer',
|
|
51
|
+
model: 'Ender 3'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Start a job
|
|
55
|
+
const jobHash = client.generateJobHash('benchy.gcode');
|
|
56
|
+
await client.submitJob(jobHash, {
|
|
57
|
+
job_type: 'print',
|
|
58
|
+
filename: 'benchy.gcode'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Complete job and earn MINT
|
|
62
|
+
const result = await client.completeJob(
|
|
63
|
+
jobHash,
|
|
64
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
console.log(`Earned ${result.reward} MINT!`);
|
|
68
|
+
console.log(`View transaction: ${result.solscan}`);
|
|
69
|
+
That's it. Your machine is now earning.
|
|
70
|
+
|
|
71
|
+
How It Works
|
|
72
|
+
1. Register Your Machine
|
|
73
|
+
Connect any machine that can make HTTP requests and sign messages (ed25519).
|
|
74
|
+
Supported:
|
|
75
|
+
|
|
76
|
+
3D Printers (OctoPrint, Klipper, Marlin)
|
|
77
|
+
CNC Mills/Routers (GRBL, LinuxCNC)
|
|
78
|
+
Laser Cutters
|
|
79
|
+
Pick-and-Place Robots
|
|
80
|
+
Custom DIY machines
|
|
81
|
+
|
|
82
|
+
2. Submit Jobs
|
|
83
|
+
When your machine starts productive work:
|
|
84
|
+
javascriptconst jobHash = client.generateJobHash('my_part.gcode');
|
|
85
|
+
await client.submitJob(jobHash, {
|
|
86
|
+
job_type: 'print',
|
|
87
|
+
filename: 'benchy.gcode'
|
|
88
|
+
});
|
|
89
|
+
3. Earn MINT
|
|
90
|
+
When work completes successfully:
|
|
91
|
+
javascriptawait client.completeJob(jobHash, yourSolanaWallet);
|
|
92
|
+
// Instant payout: 3 MINT per job
|
|
93
|
+
Current Rewards:
|
|
94
|
+
|
|
95
|
+
3 MINT per completed job
|
|
96
|
+
Maximum 400 MINT per machine per 24 hours
|
|
97
|
+
Jobs must run minimum 60 seconds
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
Integration Examples
|
|
101
|
+
OctoPrint Plugin
|
|
102
|
+
pythonfrom foundrynet import FoundryClient
|
|
103
|
+
|
|
104
|
+
# In plugin initialization
|
|
105
|
+
client = FoundryClient(api_url='https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts')
|
|
106
|
+
client.init(metadata={'type': 'printer', 'model': 'Prusa MK3'})
|
|
107
|
+
|
|
108
|
+
# On print start
|
|
109
|
+
def on_print_started(filename):
|
|
110
|
+
job_hash = client.generate_job_hash(filename)
|
|
111
|
+
client.submit_job(job_hash, {'filename': filename})
|
|
112
|
+
|
|
113
|
+
# On print complete
|
|
114
|
+
def on_print_done(job_hash):
|
|
115
|
+
client.complete_job(job_hash, user_wallet)
|
|
116
|
+
Klipper/Moonraker
|
|
117
|
+
python# Listen to Moonraker webhooks
|
|
118
|
+
def on_print_started(data):
|
|
119
|
+
job_hash = client.generate_job_hash(data['filename'])
|
|
120
|
+
client.submit_job(job_hash, {'filename': data['filename']})
|
|
121
|
+
|
|
122
|
+
def on_print_complete(data):
|
|
123
|
+
client.complete_job(current_job_hash, user_wallet)
|
|
124
|
+
GRBL/CNC
|
|
125
|
+
javascript// On program start
|
|
126
|
+
const jobHash = client.generateJobHash(programName);
|
|
127
|
+
await client.submitJob(jobHash, {
|
|
128
|
+
job_type: 'cnc',
|
|
129
|
+
program: programName
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// On program complete (M2/M30)
|
|
133
|
+
await client.completeJob(jobHash, wallet);
|
|
134
|
+
Custom/DIY
|
|
135
|
+
javascript// Your machine, your rules
|
|
136
|
+
const client = new FoundryClient({
|
|
137
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await client.init({
|
|
141
|
+
type: 'custom',
|
|
142
|
+
model: 'Pick and Place Robot'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// When task starts
|
|
146
|
+
const jobHash = client.generateJobHash(taskId);
|
|
147
|
+
await client.submitJob(jobHash, {
|
|
148
|
+
job_type: 'robot',
|
|
149
|
+
task_id: taskId
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// When task completes
|
|
153
|
+
await client.completeJob(jobHash, wallet);
|
|
154
|
+
|
|
155
|
+
API Reference
|
|
156
|
+
FoundryClient(config)
|
|
157
|
+
javascriptconst client = new FoundryClient({
|
|
158
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
159
|
+
retryAttempts: 3, // Network retry count
|
|
160
|
+
retryDelay: 2000, // ms between retries
|
|
161
|
+
debug: false // Enable verbose logging
|
|
162
|
+
});
|
|
163
|
+
client.init(metadata)
|
|
164
|
+
Initialize machine. Loads existing credentials or generates new ones.
|
|
165
|
+
javascriptawait client.init({
|
|
166
|
+
type: 'printer', // printer|cnc|robot|laser|custom
|
|
167
|
+
model: 'Ender 3 V2',
|
|
168
|
+
firmware: 'Klipper'
|
|
169
|
+
});
|
|
170
|
+
// Saves credentials to .foundry_credentials.json
|
|
171
|
+
Returns:
|
|
172
|
+
javascript{
|
|
173
|
+
existing: true, // Whether credentials already existed
|
|
174
|
+
machineUuid: "abc-123", // Your machine ID
|
|
175
|
+
identity: {...} // New credentials (if first time)
|
|
176
|
+
}
|
|
177
|
+
client.submitJob(jobHash, payload)
|
|
178
|
+
Register job start.
|
|
179
|
+
javascriptawait client.submitJob('job_unique_hash', {
|
|
180
|
+
job_type: 'print',
|
|
181
|
+
filename: 'part.gcode',
|
|
182
|
+
estimated_time: 3600
|
|
183
|
+
});
|
|
184
|
+
Returns:
|
|
185
|
+
javascript{
|
|
186
|
+
success: true,
|
|
187
|
+
job_hash: "job_unique_hash"
|
|
188
|
+
}
|
|
189
|
+
client.completeJob(jobHash, recipientWallet)
|
|
190
|
+
Complete job and earn MINT.
|
|
191
|
+
javascriptconst result = await client.completeJob(
|
|
192
|
+
'job_unique_hash',
|
|
193
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
194
|
+
);
|
|
195
|
+
Returns:
|
|
196
|
+
javascript{
|
|
197
|
+
success: true,
|
|
198
|
+
reward: 3,
|
|
199
|
+
tx_signature: "5x7y...",
|
|
200
|
+
solscan: "https://solscan.io/tx/..."
|
|
201
|
+
}
|
|
202
|
+
client.generateJobHash(filename, additionalData)
|
|
203
|
+
Generate deterministic job hash.
|
|
204
|
+
javascriptconst jobHash = client.generateJobHash('benchy.gcode');
|
|
205
|
+
// Returns: "job_abc123_1234567890"
|
|
206
|
+
|
|
207
|
+
Rules & Limits
|
|
208
|
+
|
|
209
|
+
3 MINT per completed job
|
|
210
|
+
400 MINT maximum per machine per 24 hours
|
|
211
|
+
Jobs must run minimum 60 seconds
|
|
212
|
+
Each job hash must be unique (no replays)
|
|
213
|
+
Signature timestamp must be within 5 minutes
|
|
214
|
+
|
|
215
|
+
What Counts as a Job?
|
|
216
|
+
Any discrete unit of productive work:
|
|
217
|
+
|
|
218
|
+
One 3D print from start to finish
|
|
219
|
+
One CNC machining operation
|
|
220
|
+
One robot task cycle
|
|
221
|
+
One laser cut/engrave job
|
|
222
|
+
|
|
223
|
+
What Doesn't Count?
|
|
224
|
+
|
|
225
|
+
Machine idle time
|
|
226
|
+
Calibration/homing
|
|
227
|
+
Test runs
|
|
228
|
+
Failed or cancelled jobs
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
Security
|
|
232
|
+
Proof of Productivity (PoP)
|
|
233
|
+
Every job completion requires:
|
|
234
|
+
|
|
235
|
+
Unique job hash (prevents replay attacks)
|
|
236
|
+
Ed25519 signature from registered machine keypair
|
|
237
|
+
Recent timestamp (within 5 minutes)
|
|
238
|
+
Minimum duration (60 seconds)
|
|
239
|
+
Rate limiting (400 MINT/24hrs per machine)
|
|
240
|
+
|
|
241
|
+
Your Machine Keys
|
|
242
|
+
|
|
243
|
+
Generated locally on first init()
|
|
244
|
+
Stored in .foundry_credentials.json
|
|
245
|
+
Never leave your machine
|
|
246
|
+
Back them up securely
|
|
247
|
+
|
|
248
|
+
Anti-Fraud
|
|
249
|
+
|
|
250
|
+
Job hash uniqueness enforced
|
|
251
|
+
Signature verification prevents impersonation
|
|
252
|
+
Rate limits prevent spam
|
|
253
|
+
Duration checks prevent instant gaming
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
Troubleshooting
|
|
257
|
+
"Job completion failed"
|
|
258
|
+
|
|
259
|
+
Check your machine credentials are loaded (client.init())
|
|
260
|
+
Verify job was submitted before completing
|
|
261
|
+
Ensure wallet address is valid Solana address
|
|
262
|
+
|
|
263
|
+
"Rate limit exceeded"
|
|
264
|
+
|
|
265
|
+
You've earned 400 MINT in the last 24 hours
|
|
266
|
+
Wait for the rolling window to reset
|
|
267
|
+
Check dashboard for current limits
|
|
268
|
+
|
|
269
|
+
"Signature verification failed"
|
|
270
|
+
|
|
271
|
+
Machine credentials may be corrupted
|
|
272
|
+
Delete .foundry_credentials.json and re-run init()
|
|
273
|
+
Ensure system time is accurate (for timestamps)
|
|
274
|
+
|
|
275
|
+
"Job duration too short"
|
|
276
|
+
|
|
277
|
+
Jobs must run minimum 60 seconds
|
|
278
|
+
Ensure you're measuring actual work time
|
|
279
|
+
Don't complete jobs immediately after starting
|
|
280
|
+
|
|
281
|
+
"Treasury depleted"
|
|
282
|
+
|
|
283
|
+
System maintenance in progress
|
|
284
|
+
Jobs are queued and will process when treasury refills
|
|
285
|
+
Check status updates
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
Getting a Wallet?
|
|
289
|
+
New to crypto?
|
|
290
|
+
Here's how to receive MINT:
|
|
291
|
+
|
|
292
|
+
Install Phantom Wallet (browser extension)
|
|
293
|
+
Create new wallet
|
|
294
|
+
Copy your Solana address
|
|
295
|
+
Use that address in completeJob()
|
|
296
|
+
|
|
297
|
+
MINT tokens appear instantly in your wallet after job completion.
|
|
298
|
+
|
|
299
|
+
Vision
|
|
300
|
+
FoundryNet is building payment infrastructure for autonomous industry.
|
|
301
|
+
We're starting simple: machines prove work happened, get paid instantly. The protocol handles identity, verification, and settlement. Everything else such as quality systems, marketplaces, reputation—gets built on top.
|
|
302
|
+
This is infrastructure for when AI agents coordinate supply chains and manufacturing capacity becomes programmable.
|
|
303
|
+
|
|
304
|
+
Tokenomics
|
|
305
|
+
Token: MINT (Solana SPL token)
|
|
306
|
+
Contract: 84hLMyG9cnC3Pd9LrWn4NtKMEhQHh4AvxbkcDMQtgem
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Initial treasury funded by protocol creator
|
|
310
|
+
Rewards distributed for completed jobs
|
|
311
|
+
LP available on Raydium (MINT/SOL)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
FoundryNet
|
|
2
|
+
Earn MINT tokens for verified manufacturing work.
|
|
3
|
+
FoundryNet rewards 3D printers, CNC machines, robots, and production equipment with cryptocurrency for completing jobs. No staking, no capital required—just connect your machine and start earning.
|
|
4
|
+
|
|
5
|
+
Quick Start
|
|
6
|
+
Installation
|
|
7
|
+
bashnpm install tweetnacl bs58 uuid
|
|
8
|
+
Download foundry-client.js from the GitHub repository.
|
|
9
|
+
Basic Usage
|
|
10
|
+
javascriptimport { FoundryClient } from './foundry-client.js';
|
|
11
|
+
|
|
12
|
+
// Initialize client
|
|
13
|
+
const client = new FoundryClient({
|
|
14
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
15
|
+
debug: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Register machine (first time only - saves credentials locally)
|
|
19
|
+
await client.init({
|
|
20
|
+
type: '3d-printer',
|
|
21
|
+
model: 'Ender 3'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Start a job
|
|
25
|
+
const jobHash = client.generateJobHash('benchy.gcode');
|
|
26
|
+
await client.submitJob(jobHash, {
|
|
27
|
+
job_type: 'print',
|
|
28
|
+
filename: 'benchy.gcode'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Complete job and earn MINT
|
|
32
|
+
const result = await client.completeJob(
|
|
33
|
+
jobHash,
|
|
34
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
console.log(`Earned ${result.reward} MINT!`);
|
|
38
|
+
console.log(`View transaction: ${result.solscan}`);
|
|
39
|
+
That's it. Your machine is now earning.
|
|
40
|
+
|
|
41
|
+
How It Works
|
|
42
|
+
1. Register Your Machine
|
|
43
|
+
Connect any machine that can make HTTP requests and sign messages (ed25519).
|
|
44
|
+
Supported:
|
|
45
|
+
|
|
46
|
+
3D Printers (OctoPrint, Klipper, Marlin)
|
|
47
|
+
CNC Mills/Routers (GRBL, LinuxCNC)
|
|
48
|
+
Laser Cutters
|
|
49
|
+
Pick-and-Place Robots
|
|
50
|
+
Custom DIY machines
|
|
51
|
+
|
|
52
|
+
2. Submit Jobs
|
|
53
|
+
When your machine starts productive work:
|
|
54
|
+
javascriptconst jobHash = client.generateJobHash('my_part.gcode');
|
|
55
|
+
await client.submitJob(jobHash, {
|
|
56
|
+
job_type: 'print',
|
|
57
|
+
filename: 'benchy.gcode'
|
|
58
|
+
});
|
|
59
|
+
3. Earn MINT
|
|
60
|
+
When work completes successfully:
|
|
61
|
+
javascriptawait client.completeJob(jobHash, yourSolanaWallet);
|
|
62
|
+
// Instant payout: 3 MINT per job
|
|
63
|
+
Current Rewards:
|
|
64
|
+
|
|
65
|
+
3 MINT per completed job
|
|
66
|
+
Maximum 400 MINT per machine per 24 hours
|
|
67
|
+
Jobs must run minimum 60 seconds
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Integration Examples
|
|
71
|
+
OctoPrint Plugin
|
|
72
|
+
pythonfrom foundrynet import FoundryClient
|
|
73
|
+
|
|
74
|
+
# In plugin initialization
|
|
75
|
+
client = FoundryClient(api_url='https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts')
|
|
76
|
+
client.init(metadata={'type': 'printer', 'model': 'Prusa MK3'})
|
|
77
|
+
|
|
78
|
+
# On print start
|
|
79
|
+
def on_print_started(filename):
|
|
80
|
+
job_hash = client.generate_job_hash(filename)
|
|
81
|
+
client.submit_job(job_hash, {'filename': filename})
|
|
82
|
+
|
|
83
|
+
# On print complete
|
|
84
|
+
def on_print_done(job_hash):
|
|
85
|
+
client.complete_job(job_hash, user_wallet)
|
|
86
|
+
Klipper/Moonraker
|
|
87
|
+
python# Listen to Moonraker webhooks
|
|
88
|
+
def on_print_started(data):
|
|
89
|
+
job_hash = client.generate_job_hash(data['filename'])
|
|
90
|
+
client.submit_job(job_hash, {'filename': data['filename']})
|
|
91
|
+
|
|
92
|
+
def on_print_complete(data):
|
|
93
|
+
client.complete_job(current_job_hash, user_wallet)
|
|
94
|
+
GRBL/CNC
|
|
95
|
+
javascript// On program start
|
|
96
|
+
const jobHash = client.generateJobHash(programName);
|
|
97
|
+
await client.submitJob(jobHash, {
|
|
98
|
+
job_type: 'cnc',
|
|
99
|
+
program: programName
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// On program complete (M2/M30)
|
|
103
|
+
await client.completeJob(jobHash, wallet);
|
|
104
|
+
Custom/DIY
|
|
105
|
+
javascript// Your machine, your rules
|
|
106
|
+
const client = new FoundryClient({
|
|
107
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await client.init({
|
|
111
|
+
type: 'custom',
|
|
112
|
+
model: 'Pick and Place Robot'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// When task starts
|
|
116
|
+
const jobHash = client.generateJobHash(taskId);
|
|
117
|
+
await client.submitJob(jobHash, {
|
|
118
|
+
job_type: 'robot',
|
|
119
|
+
task_id: taskId
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// When task completes
|
|
123
|
+
await client.completeJob(jobHash, wallet);
|
|
124
|
+
|
|
125
|
+
API Reference
|
|
126
|
+
FoundryClient(config)
|
|
127
|
+
javascriptconst client = new FoundryClient({
|
|
128
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
129
|
+
retryAttempts: 3, // Network retry count
|
|
130
|
+
retryDelay: 2000, // ms between retries
|
|
131
|
+
debug: false // Enable verbose logging
|
|
132
|
+
});
|
|
133
|
+
client.init(metadata)
|
|
134
|
+
Initialize machine. Loads existing credentials or generates new ones.
|
|
135
|
+
javascriptawait client.init({
|
|
136
|
+
type: 'printer', // printer|cnc|robot|laser|custom
|
|
137
|
+
model: 'Ender 3 V2',
|
|
138
|
+
firmware: 'Klipper'
|
|
139
|
+
});
|
|
140
|
+
// Saves credentials to .foundry_credentials.json
|
|
141
|
+
Returns:
|
|
142
|
+
javascript{
|
|
143
|
+
existing: true, // Whether credentials already existed
|
|
144
|
+
machineUuid: "abc-123", // Your machine ID
|
|
145
|
+
identity: {...} // New credentials (if first time)
|
|
146
|
+
}
|
|
147
|
+
client.submitJob(jobHash, payload)
|
|
148
|
+
Register job start.
|
|
149
|
+
javascriptawait client.submitJob('job_unique_hash', {
|
|
150
|
+
job_type: 'print',
|
|
151
|
+
filename: 'part.gcode',
|
|
152
|
+
estimated_time: 3600
|
|
153
|
+
});
|
|
154
|
+
Returns:
|
|
155
|
+
javascript{
|
|
156
|
+
success: true,
|
|
157
|
+
job_hash: "job_unique_hash"
|
|
158
|
+
}
|
|
159
|
+
client.completeJob(jobHash, recipientWallet)
|
|
160
|
+
Complete job and earn MINT.
|
|
161
|
+
javascriptconst result = await client.completeJob(
|
|
162
|
+
'job_unique_hash',
|
|
163
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
164
|
+
);
|
|
165
|
+
Returns:
|
|
166
|
+
javascript{
|
|
167
|
+
success: true,
|
|
168
|
+
reward: 3,
|
|
169
|
+
tx_signature: "5x7y...",
|
|
170
|
+
solscan: "https://solscan.io/tx/..."
|
|
171
|
+
}
|
|
172
|
+
client.generateJobHash(filename, additionalData)
|
|
173
|
+
Generate deterministic job hash.
|
|
174
|
+
javascriptconst jobHash = client.generateJobHash('benchy.gcode');
|
|
175
|
+
// Returns: "job_abc123_1234567890"
|
|
176
|
+
|
|
177
|
+
Rules & Limits
|
|
178
|
+
|
|
179
|
+
3 MINT per completed job
|
|
180
|
+
400 MINT maximum per machine per 24 hours
|
|
181
|
+
Jobs must run minimum 60 seconds
|
|
182
|
+
Each job hash must be unique (no replays)
|
|
183
|
+
Signature timestamp must be within 5 minutes
|
|
184
|
+
|
|
185
|
+
What Counts as a Job?
|
|
186
|
+
Any discrete unit of productive work:
|
|
187
|
+
|
|
188
|
+
One 3D print from start to finish
|
|
189
|
+
One CNC machining operation
|
|
190
|
+
One robot task cycle
|
|
191
|
+
One laser cut/engrave job
|
|
192
|
+
|
|
193
|
+
What Doesn't Count?
|
|
194
|
+
|
|
195
|
+
Machine idle time
|
|
196
|
+
Calibration/homing
|
|
197
|
+
Test runs
|
|
198
|
+
Failed or cancelled jobs
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
Security
|
|
202
|
+
Proof of Productivity (PoP)
|
|
203
|
+
Every job completion requires:
|
|
204
|
+
|
|
205
|
+
Unique job hash (prevents replay attacks)
|
|
206
|
+
Ed25519 signature from registered machine keypair
|
|
207
|
+
Recent timestamp (within 5 minutes)
|
|
208
|
+
Minimum duration (60 seconds)
|
|
209
|
+
Rate limiting (400 MINT/24hrs per machine)
|
|
210
|
+
|
|
211
|
+
Your Machine Keys
|
|
212
|
+
|
|
213
|
+
Generated locally on first init()
|
|
214
|
+
Stored in .foundry_credentials.json
|
|
215
|
+
Never leave your machine
|
|
216
|
+
Back them up securely
|
|
217
|
+
|
|
218
|
+
Anti-Fraud
|
|
219
|
+
|
|
220
|
+
Job hash uniqueness enforced
|
|
221
|
+
Signature verification prevents impersonation
|
|
222
|
+
Rate limits prevent spam
|
|
223
|
+
Duration checks prevent instant gaming
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
Troubleshooting
|
|
227
|
+
"Job completion failed"
|
|
228
|
+
|
|
229
|
+
Check your machine credentials are loaded (client.init())
|
|
230
|
+
Verify job was submitted before completing
|
|
231
|
+
Ensure wallet address is valid Solana address
|
|
232
|
+
|
|
233
|
+
"Rate limit exceeded"
|
|
234
|
+
|
|
235
|
+
You've earned 400 MINT in the last 24 hours
|
|
236
|
+
Wait for the rolling window to reset
|
|
237
|
+
Check dashboard for current limits
|
|
238
|
+
|
|
239
|
+
"Signature verification failed"
|
|
240
|
+
|
|
241
|
+
Machine credentials may be corrupted
|
|
242
|
+
Delete .foundry_credentials.json and re-run init()
|
|
243
|
+
Ensure system time is accurate (for timestamps)
|
|
244
|
+
|
|
245
|
+
"Job duration too short"
|
|
246
|
+
|
|
247
|
+
Jobs must run minimum 60 seconds
|
|
248
|
+
Ensure you're measuring actual work time
|
|
249
|
+
Don't complete jobs immediately after starting
|
|
250
|
+
|
|
251
|
+
"Treasury depleted"
|
|
252
|
+
|
|
253
|
+
System maintenance in progress
|
|
254
|
+
Jobs are queued and will process when treasury refills
|
|
255
|
+
Check status updates
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
Getting a Wallet?
|
|
259
|
+
New to crypto?
|
|
260
|
+
Here's how to receive MINT:
|
|
261
|
+
|
|
262
|
+
Install Phantom Wallet (browser extension)
|
|
263
|
+
Create new wallet
|
|
264
|
+
Copy your Solana address
|
|
265
|
+
Use that address in completeJob()
|
|
266
|
+
|
|
267
|
+
MINT tokens appear instantly in your wallet after job completion.
|
|
268
|
+
|
|
269
|
+
Vision
|
|
270
|
+
FoundryNet is building payment infrastructure for autonomous industry.
|
|
271
|
+
We're starting simple: machines prove work happened, get paid instantly. The protocol handles identity, verification, and settlement. Everything else such as quality systems, marketplaces, reputation—gets built on top.
|
|
272
|
+
This is infrastructure for when AI agents coordinate supply chains and manufacturing capacity becomes programmable.
|
|
273
|
+
|
|
274
|
+
Tokenomics
|
|
275
|
+
Token: MINT (Solana SPL token)
|
|
276
|
+
Contract: 84hLMyG9cnC3Pd9LrWn4NtKMEhQHh4AvxbkcDMQtgem
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
Initial treasury funded by protocol creator
|
|
280
|
+
Rewards distributed for completed jobs
|
|
281
|
+
LP available on Raydium (MINT/SOL)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import nacl.signing
|
|
2
|
+
import nacl.encoding
|
|
3
|
+
import base58
|
|
4
|
+
import requests
|
|
5
|
+
import json
|
|
6
|
+
import hashlib
|
|
7
|
+
import time
|
|
8
|
+
import os
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
class FoundryClient:
|
|
15
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
16
|
+
config = config or {}
|
|
17
|
+
self.api_url = config.get('api_url', 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts')
|
|
18
|
+
self.retry_attempts = config.get('retry_attempts', 3)
|
|
19
|
+
self.retry_delay = config.get('retry_delay', 2.0)
|
|
20
|
+
self.debug = config.get('debug', False)
|
|
21
|
+
self.credentials_file = config.get('credentials_file', '.foundry_credentials.json')
|
|
22
|
+
|
|
23
|
+
self.machine_uuid: Optional[str] = None
|
|
24
|
+
self.signing_key: Optional[nacl.signing.SigningKey] = None
|
|
25
|
+
self.verify_key: Optional[nacl.signing.VerifyKey] = None
|
|
26
|
+
|
|
27
|
+
def log(self, level: str, message: str, data: Optional[Dict] = None):
|
|
28
|
+
if not self.debug and level == 'debug':
|
|
29
|
+
return
|
|
30
|
+
timestamp = datetime.utcnow().isoformat()
|
|
31
|
+
print(f"[FoundryNet {timestamp}] [{level.upper()}] {message}", data or {})
|
|
32
|
+
|
|
33
|
+
def _retry(self, fn, context: str = ''):
|
|
34
|
+
last_error = None
|
|
35
|
+
for attempt in range(1, self.retry_attempts + 1):
|
|
36
|
+
try:
|
|
37
|
+
return fn()
|
|
38
|
+
except Exception as error:
|
|
39
|
+
last_error = error
|
|
40
|
+
self.log('warn', f"{context} failed (attempt {attempt}/{self.retry_attempts})",
|
|
41
|
+
{'error': str(error)})
|
|
42
|
+
if attempt < self.retry_attempts:
|
|
43
|
+
delay = self.retry_delay * attempt
|
|
44
|
+
self.log('debug', f"Retrying in {delay}s...")
|
|
45
|
+
time.sleep(delay)
|
|
46
|
+
|
|
47
|
+
self.log('error', f"{context} failed after {self.retry_attempts} attempts",
|
|
48
|
+
{'error': str(last_error)})
|
|
49
|
+
raise last_error
|
|
50
|
+
|
|
51
|
+
def generate_machine_id(self) -> Dict[str, str]:
|
|
52
|
+
self.machine_uuid = str(uuid4())
|
|
53
|
+
self.signing_key = nacl.signing.SigningKey.generate()
|
|
54
|
+
self.verify_key = self.signing_key.verify_key
|
|
55
|
+
|
|
56
|
+
identity = {
|
|
57
|
+
'machine_uuid': self.machine_uuid,
|
|
58
|
+
'public_key': base58.b58encode(bytes(self.verify_key)).decode('utf-8'),
|
|
59
|
+
'secret_key': base58.b58encode(bytes(self.signing_key)).decode('utf-8'),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
self.log('info', 'Generated new machine identity', {
|
|
63
|
+
'machine_uuid': identity['machine_uuid'],
|
|
64
|
+
'public_key': identity['public_key'],
|
|
65
|
+
})
|
|
66
|
+
return identity
|
|
67
|
+
|
|
68
|
+
def load_machine_id(self, machine_uuid: str, secret_key_base58: str):
|
|
69
|
+
try:
|
|
70
|
+
self.machine_uuid = machine_uuid
|
|
71
|
+
secret_key_bytes = base58.b58decode(secret_key_base58)
|
|
72
|
+
self.signing_key = nacl.signing.SigningKey(secret_key_bytes)
|
|
73
|
+
self.verify_key = self.signing_key.verify_key
|
|
74
|
+
self.log('info', 'Loaded machine identity', {'machine_uuid': machine_uuid})
|
|
75
|
+
except Exception as error:
|
|
76
|
+
self.log('error', 'Failed to load machine identity', {'error': str(error)})
|
|
77
|
+
raise ValueError(f"Invalid machine credentials: {error}")
|
|
78
|
+
|
|
79
|
+
def save_credentials(self, identity: Dict[str, str]):
|
|
80
|
+
cred_path = Path.cwd() / self.credentials_file
|
|
81
|
+
credentials = {
|
|
82
|
+
'machine_uuid': identity['machine_uuid'],
|
|
83
|
+
'secret_key': identity['secret_key'],
|
|
84
|
+
'public_key': identity['public_key'],
|
|
85
|
+
'created_at': datetime.utcnow().isoformat(),
|
|
86
|
+
}
|
|
87
|
+
with open(cred_path, 'w') as f:
|
|
88
|
+
json.dump(credentials, f, indent=2)
|
|
89
|
+
self.log('debug', 'Credentials saved', {'file': str(cred_path)})
|
|
90
|
+
|
|
91
|
+
def load_credentials(self) -> bool:
|
|
92
|
+
try:
|
|
93
|
+
cred_path = Path.cwd() / self.credentials_file
|
|
94
|
+
if cred_path.exists():
|
|
95
|
+
with open(cred_path, 'r') as f:
|
|
96
|
+
creds = json.load(f)
|
|
97
|
+
self.load_machine_id(creds['machine_uuid'], creds['secret_key'])
|
|
98
|
+
self.log('info', 'Loaded existing credentials',
|
|
99
|
+
{'machine_uuid': self.machine_uuid})
|
|
100
|
+
return True
|
|
101
|
+
except Exception:
|
|
102
|
+
self.log('debug', 'No existing credentials found or corrupted')
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def init(self, metadata: Optional[Dict] = None) -> Dict[str, Any]:
|
|
106
|
+
metadata = metadata or {}
|
|
107
|
+
|
|
108
|
+
if self.load_credentials():
|
|
109
|
+
self.log('info', 'Using existing machine credentials',
|
|
110
|
+
{'machine_uuid': self.machine_uuid})
|
|
111
|
+
return {'existing': True, 'machine_uuid': self.machine_uuid}
|
|
112
|
+
|
|
113
|
+
identity = self.generate_machine_id()
|
|
114
|
+
self.save_credentials(identity)
|
|
115
|
+
self.register_machine(metadata)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
'existing': False,
|
|
119
|
+
'identity': identity,
|
|
120
|
+
'machine_uuid': self.machine_uuid
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def register_machine(self, metadata: Optional[Dict] = None) -> Dict:
|
|
124
|
+
metadata = metadata or {}
|
|
125
|
+
|
|
126
|
+
if not self.machine_uuid or not self.verify_key:
|
|
127
|
+
raise ValueError('Generate or load machine ID first')
|
|
128
|
+
|
|
129
|
+
def _register():
|
|
130
|
+
response = requests.post(
|
|
131
|
+
f"{self.api_url}/register-machine",
|
|
132
|
+
json={
|
|
133
|
+
'machine_uuid': self.machine_uuid,
|
|
134
|
+
'machine_pubkey_base58': base58.b58encode(bytes(self.verify_key)).decode('utf-8'),
|
|
135
|
+
'metadata': metadata,
|
|
136
|
+
},
|
|
137
|
+
headers={'Content-Type': 'application/json'}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if not response.ok:
|
|
141
|
+
raise Exception(f"Registration failed: {response.text}")
|
|
142
|
+
|
|
143
|
+
result = response.json()
|
|
144
|
+
self.log('info', 'Machine registered successfully',
|
|
145
|
+
{'machine_uuid': self.machine_uuid})
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
return self._retry(_register, 'Machine registration')
|
|
149
|
+
|
|
150
|
+
def submit_job(self, job_hash: str, complexity: float = 1.0,
|
|
151
|
+
payload: Optional[Dict] = None) -> Dict:
|
|
152
|
+
payload = payload or {}
|
|
153
|
+
|
|
154
|
+
if not self.machine_uuid:
|
|
155
|
+
raise ValueError('Machine not initialized')
|
|
156
|
+
|
|
157
|
+
normalized = round(complexity * 100) / 100
|
|
158
|
+
|
|
159
|
+
MIN_COMPLEXITY = 0.5
|
|
160
|
+
MAX_COMPLEXITY = 2.0
|
|
161
|
+
TOLERANCE = 0.01
|
|
162
|
+
|
|
163
|
+
if normalized < MIN_COMPLEXITY - TOLERANCE or normalized > MAX_COMPLEXITY + TOLERANCE:
|
|
164
|
+
raise ValueError(f"Complexity must be {MIN_COMPLEXITY}-{MAX_COMPLEXITY}, got {normalized}")
|
|
165
|
+
|
|
166
|
+
def _submit():
|
|
167
|
+
response = requests.post(
|
|
168
|
+
f"{self.api_url}/submit-job",
|
|
169
|
+
json={
|
|
170
|
+
'machine_uuid': self.machine_uuid,
|
|
171
|
+
'job_hash': job_hash,
|
|
172
|
+
'complexity': normalized,
|
|
173
|
+
'payload': payload,
|
|
174
|
+
},
|
|
175
|
+
headers={'Content-Type': 'application/json'}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if response.status_code == 409:
|
|
179
|
+
self.log('warn', 'Job already exists', {'job_hash': job_hash})
|
|
180
|
+
return {'success': True, 'duplicate': True, 'job_hash': job_hash}
|
|
181
|
+
|
|
182
|
+
if not response.ok:
|
|
183
|
+
text = response.text
|
|
184
|
+
try:
|
|
185
|
+
error = response.json().get('error', text)
|
|
186
|
+
except:
|
|
187
|
+
error = text
|
|
188
|
+
raise Exception(f"Job submission failed: {error}")
|
|
189
|
+
|
|
190
|
+
result = response.json()
|
|
191
|
+
self.log('debug', 'Job submitted', {'job_hash': job_hash, 'complexity': normalized})
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
return self._retry(_submit, 'Job submission')
|
|
195
|
+
|
|
196
|
+
def complete_job(self, job_hash: str, recipient_wallet: str) -> Dict:
|
|
197
|
+
if not self.machine_uuid or not self.signing_key:
|
|
198
|
+
raise ValueError('Machine not initialized')
|
|
199
|
+
|
|
200
|
+
timestamp = datetime.utcnow().isoformat()
|
|
201
|
+
message = f"{job_hash}|{recipient_wallet}|{timestamp}"
|
|
202
|
+
message_bytes = message.encode('utf-8')
|
|
203
|
+
|
|
204
|
+
signed = self.signing_key.sign(message_bytes)
|
|
205
|
+
signature = signed.signature
|
|
206
|
+
|
|
207
|
+
def _complete():
|
|
208
|
+
response = requests.post(
|
|
209
|
+
f"{self.api_url}/complete-job",
|
|
210
|
+
json={
|
|
211
|
+
'machine_uuid': self.machine_uuid,
|
|
212
|
+
'job_hash': job_hash,
|
|
213
|
+
'recipient_wallet': recipient_wallet,
|
|
214
|
+
'completion_proof': {
|
|
215
|
+
'timestamp': timestamp,
|
|
216
|
+
'signature_base58': base58.b58encode(signature).decode('utf-8'),
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
headers={'Content-Type': 'application/json'}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if not response.ok:
|
|
223
|
+
raise Exception(f"Job completion failed: {response.text}")
|
|
224
|
+
|
|
225
|
+
result = response.json()
|
|
226
|
+
self.log('info', 'Job completed - MINT earned!', {
|
|
227
|
+
'job_hash': job_hash,
|
|
228
|
+
'reward': result.get('reward_net'),
|
|
229
|
+
'tx_signature': result.get('tx_signature'),
|
|
230
|
+
'activity_ratio': result.get('activity_ratio'),
|
|
231
|
+
})
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
return self._retry(_complete, 'Job completion')
|
|
235
|
+
|
|
236
|
+
def generate_job_hash(self, filename: str, additional_data: str = '') -> str:
|
|
237
|
+
data = f"{self.machine_uuid}|{filename}|{int(time.time() * 1000)}|{additional_data}"
|
|
238
|
+
hash_obj = hashlib.sha256(data.encode('utf-8'))
|
|
239
|
+
hash_hex = hash_obj.hexdigest()
|
|
240
|
+
return f"job_{hash_hex[:16]}_{int(time.time() * 1000)}"
|
|
241
|
+
|
|
242
|
+
def get_metrics(self) -> Dict:
|
|
243
|
+
def _fetch():
|
|
244
|
+
response = requests.get(
|
|
245
|
+
f"{self.api_url}/metrics",
|
|
246
|
+
headers={'Content-Type': 'application/json'}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if not response.ok:
|
|
250
|
+
raise Exception("Failed to fetch metrics")
|
|
251
|
+
|
|
252
|
+
return response.json()
|
|
253
|
+
|
|
254
|
+
return self._retry(_fetch, 'Fetch metrics')
|
|
255
|
+
|
|
256
|
+
def get_machine_uuid(self) -> str:
|
|
257
|
+
return self.machine_uuid or ''
|
|
258
|
+
|
|
259
|
+
def get_public_key(self) -> str:
|
|
260
|
+
if self.verify_key:
|
|
261
|
+
return base58.b58encode(bytes(self.verify_key)).decode('utf-8')
|
|
262
|
+
return ''
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foundry-client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for FoundryNet - Universal DePIN Protocol for Work Settlement
|
|
5
|
+
Home-page: https://github.com/foundrynet/foundry_net_MINT
|
|
6
|
+
Author: FoundryNet
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: PyNaCl>=1.5.0
|
|
19
|
+
Requires-Dist: base58>=2.1.1
|
|
20
|
+
Requires-Dist: requests>=2.31.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
FoundryNet
|
|
32
|
+
Earn MINT tokens for verified manufacturing work.
|
|
33
|
+
FoundryNet rewards 3D printers, CNC machines, robots, and production equipment with cryptocurrency for completing jobs. No staking, no capital required—just connect your machine and start earning.
|
|
34
|
+
|
|
35
|
+
Quick Start
|
|
36
|
+
Installation
|
|
37
|
+
bashnpm install tweetnacl bs58 uuid
|
|
38
|
+
Download foundry-client.js from the GitHub repository.
|
|
39
|
+
Basic Usage
|
|
40
|
+
javascriptimport { FoundryClient } from './foundry-client.js';
|
|
41
|
+
|
|
42
|
+
// Initialize client
|
|
43
|
+
const client = new FoundryClient({
|
|
44
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
45
|
+
debug: true
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Register machine (first time only - saves credentials locally)
|
|
49
|
+
await client.init({
|
|
50
|
+
type: '3d-printer',
|
|
51
|
+
model: 'Ender 3'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Start a job
|
|
55
|
+
const jobHash = client.generateJobHash('benchy.gcode');
|
|
56
|
+
await client.submitJob(jobHash, {
|
|
57
|
+
job_type: 'print',
|
|
58
|
+
filename: 'benchy.gcode'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Complete job and earn MINT
|
|
62
|
+
const result = await client.completeJob(
|
|
63
|
+
jobHash,
|
|
64
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
console.log(`Earned ${result.reward} MINT!`);
|
|
68
|
+
console.log(`View transaction: ${result.solscan}`);
|
|
69
|
+
That's it. Your machine is now earning.
|
|
70
|
+
|
|
71
|
+
How It Works
|
|
72
|
+
1. Register Your Machine
|
|
73
|
+
Connect any machine that can make HTTP requests and sign messages (ed25519).
|
|
74
|
+
Supported:
|
|
75
|
+
|
|
76
|
+
3D Printers (OctoPrint, Klipper, Marlin)
|
|
77
|
+
CNC Mills/Routers (GRBL, LinuxCNC)
|
|
78
|
+
Laser Cutters
|
|
79
|
+
Pick-and-Place Robots
|
|
80
|
+
Custom DIY machines
|
|
81
|
+
|
|
82
|
+
2. Submit Jobs
|
|
83
|
+
When your machine starts productive work:
|
|
84
|
+
javascriptconst jobHash = client.generateJobHash('my_part.gcode');
|
|
85
|
+
await client.submitJob(jobHash, {
|
|
86
|
+
job_type: 'print',
|
|
87
|
+
filename: 'benchy.gcode'
|
|
88
|
+
});
|
|
89
|
+
3. Earn MINT
|
|
90
|
+
When work completes successfully:
|
|
91
|
+
javascriptawait client.completeJob(jobHash, yourSolanaWallet);
|
|
92
|
+
// Instant payout: 3 MINT per job
|
|
93
|
+
Current Rewards:
|
|
94
|
+
|
|
95
|
+
3 MINT per completed job
|
|
96
|
+
Maximum 400 MINT per machine per 24 hours
|
|
97
|
+
Jobs must run minimum 60 seconds
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
Integration Examples
|
|
101
|
+
OctoPrint Plugin
|
|
102
|
+
pythonfrom foundrynet import FoundryClient
|
|
103
|
+
|
|
104
|
+
# In plugin initialization
|
|
105
|
+
client = FoundryClient(api_url='https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts')
|
|
106
|
+
client.init(metadata={'type': 'printer', 'model': 'Prusa MK3'})
|
|
107
|
+
|
|
108
|
+
# On print start
|
|
109
|
+
def on_print_started(filename):
|
|
110
|
+
job_hash = client.generate_job_hash(filename)
|
|
111
|
+
client.submit_job(job_hash, {'filename': filename})
|
|
112
|
+
|
|
113
|
+
# On print complete
|
|
114
|
+
def on_print_done(job_hash):
|
|
115
|
+
client.complete_job(job_hash, user_wallet)
|
|
116
|
+
Klipper/Moonraker
|
|
117
|
+
python# Listen to Moonraker webhooks
|
|
118
|
+
def on_print_started(data):
|
|
119
|
+
job_hash = client.generate_job_hash(data['filename'])
|
|
120
|
+
client.submit_job(job_hash, {'filename': data['filename']})
|
|
121
|
+
|
|
122
|
+
def on_print_complete(data):
|
|
123
|
+
client.complete_job(current_job_hash, user_wallet)
|
|
124
|
+
GRBL/CNC
|
|
125
|
+
javascript// On program start
|
|
126
|
+
const jobHash = client.generateJobHash(programName);
|
|
127
|
+
await client.submitJob(jobHash, {
|
|
128
|
+
job_type: 'cnc',
|
|
129
|
+
program: programName
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// On program complete (M2/M30)
|
|
133
|
+
await client.completeJob(jobHash, wallet);
|
|
134
|
+
Custom/DIY
|
|
135
|
+
javascript// Your machine, your rules
|
|
136
|
+
const client = new FoundryClient({
|
|
137
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await client.init({
|
|
141
|
+
type: 'custom',
|
|
142
|
+
model: 'Pick and Place Robot'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// When task starts
|
|
146
|
+
const jobHash = client.generateJobHash(taskId);
|
|
147
|
+
await client.submitJob(jobHash, {
|
|
148
|
+
job_type: 'robot',
|
|
149
|
+
task_id: taskId
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// When task completes
|
|
153
|
+
await client.completeJob(jobHash, wallet);
|
|
154
|
+
|
|
155
|
+
API Reference
|
|
156
|
+
FoundryClient(config)
|
|
157
|
+
javascriptconst client = new FoundryClient({
|
|
158
|
+
apiUrl: 'https://lsijwmklicmqtuqxhgnu.supabase.co/functions/v1/main-ts',
|
|
159
|
+
retryAttempts: 3, // Network retry count
|
|
160
|
+
retryDelay: 2000, // ms between retries
|
|
161
|
+
debug: false // Enable verbose logging
|
|
162
|
+
});
|
|
163
|
+
client.init(metadata)
|
|
164
|
+
Initialize machine. Loads existing credentials or generates new ones.
|
|
165
|
+
javascriptawait client.init({
|
|
166
|
+
type: 'printer', // printer|cnc|robot|laser|custom
|
|
167
|
+
model: 'Ender 3 V2',
|
|
168
|
+
firmware: 'Klipper'
|
|
169
|
+
});
|
|
170
|
+
// Saves credentials to .foundry_credentials.json
|
|
171
|
+
Returns:
|
|
172
|
+
javascript{
|
|
173
|
+
existing: true, // Whether credentials already existed
|
|
174
|
+
machineUuid: "abc-123", // Your machine ID
|
|
175
|
+
identity: {...} // New credentials (if first time)
|
|
176
|
+
}
|
|
177
|
+
client.submitJob(jobHash, payload)
|
|
178
|
+
Register job start.
|
|
179
|
+
javascriptawait client.submitJob('job_unique_hash', {
|
|
180
|
+
job_type: 'print',
|
|
181
|
+
filename: 'part.gcode',
|
|
182
|
+
estimated_time: 3600
|
|
183
|
+
});
|
|
184
|
+
Returns:
|
|
185
|
+
javascript{
|
|
186
|
+
success: true,
|
|
187
|
+
job_hash: "job_unique_hash"
|
|
188
|
+
}
|
|
189
|
+
client.completeJob(jobHash, recipientWallet)
|
|
190
|
+
Complete job and earn MINT.
|
|
191
|
+
javascriptconst result = await client.completeJob(
|
|
192
|
+
'job_unique_hash',
|
|
193
|
+
'YOUR_SOLANA_WALLET_ADDRESS'
|
|
194
|
+
);
|
|
195
|
+
Returns:
|
|
196
|
+
javascript{
|
|
197
|
+
success: true,
|
|
198
|
+
reward: 3,
|
|
199
|
+
tx_signature: "5x7y...",
|
|
200
|
+
solscan: "https://solscan.io/tx/..."
|
|
201
|
+
}
|
|
202
|
+
client.generateJobHash(filename, additionalData)
|
|
203
|
+
Generate deterministic job hash.
|
|
204
|
+
javascriptconst jobHash = client.generateJobHash('benchy.gcode');
|
|
205
|
+
// Returns: "job_abc123_1234567890"
|
|
206
|
+
|
|
207
|
+
Rules & Limits
|
|
208
|
+
|
|
209
|
+
3 MINT per completed job
|
|
210
|
+
400 MINT maximum per machine per 24 hours
|
|
211
|
+
Jobs must run minimum 60 seconds
|
|
212
|
+
Each job hash must be unique (no replays)
|
|
213
|
+
Signature timestamp must be within 5 minutes
|
|
214
|
+
|
|
215
|
+
What Counts as a Job?
|
|
216
|
+
Any discrete unit of productive work:
|
|
217
|
+
|
|
218
|
+
One 3D print from start to finish
|
|
219
|
+
One CNC machining operation
|
|
220
|
+
One robot task cycle
|
|
221
|
+
One laser cut/engrave job
|
|
222
|
+
|
|
223
|
+
What Doesn't Count?
|
|
224
|
+
|
|
225
|
+
Machine idle time
|
|
226
|
+
Calibration/homing
|
|
227
|
+
Test runs
|
|
228
|
+
Failed or cancelled jobs
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
Security
|
|
232
|
+
Proof of Productivity (PoP)
|
|
233
|
+
Every job completion requires:
|
|
234
|
+
|
|
235
|
+
Unique job hash (prevents replay attacks)
|
|
236
|
+
Ed25519 signature from registered machine keypair
|
|
237
|
+
Recent timestamp (within 5 minutes)
|
|
238
|
+
Minimum duration (60 seconds)
|
|
239
|
+
Rate limiting (400 MINT/24hrs per machine)
|
|
240
|
+
|
|
241
|
+
Your Machine Keys
|
|
242
|
+
|
|
243
|
+
Generated locally on first init()
|
|
244
|
+
Stored in .foundry_credentials.json
|
|
245
|
+
Never leave your machine
|
|
246
|
+
Back them up securely
|
|
247
|
+
|
|
248
|
+
Anti-Fraud
|
|
249
|
+
|
|
250
|
+
Job hash uniqueness enforced
|
|
251
|
+
Signature verification prevents impersonation
|
|
252
|
+
Rate limits prevent spam
|
|
253
|
+
Duration checks prevent instant gaming
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
Troubleshooting
|
|
257
|
+
"Job completion failed"
|
|
258
|
+
|
|
259
|
+
Check your machine credentials are loaded (client.init())
|
|
260
|
+
Verify job was submitted before completing
|
|
261
|
+
Ensure wallet address is valid Solana address
|
|
262
|
+
|
|
263
|
+
"Rate limit exceeded"
|
|
264
|
+
|
|
265
|
+
You've earned 400 MINT in the last 24 hours
|
|
266
|
+
Wait for the rolling window to reset
|
|
267
|
+
Check dashboard for current limits
|
|
268
|
+
|
|
269
|
+
"Signature verification failed"
|
|
270
|
+
|
|
271
|
+
Machine credentials may be corrupted
|
|
272
|
+
Delete .foundry_credentials.json and re-run init()
|
|
273
|
+
Ensure system time is accurate (for timestamps)
|
|
274
|
+
|
|
275
|
+
"Job duration too short"
|
|
276
|
+
|
|
277
|
+
Jobs must run minimum 60 seconds
|
|
278
|
+
Ensure you're measuring actual work time
|
|
279
|
+
Don't complete jobs immediately after starting
|
|
280
|
+
|
|
281
|
+
"Treasury depleted"
|
|
282
|
+
|
|
283
|
+
System maintenance in progress
|
|
284
|
+
Jobs are queued and will process when treasury refills
|
|
285
|
+
Check status updates
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
Getting a Wallet?
|
|
289
|
+
New to crypto?
|
|
290
|
+
Here's how to receive MINT:
|
|
291
|
+
|
|
292
|
+
Install Phantom Wallet (browser extension)
|
|
293
|
+
Create new wallet
|
|
294
|
+
Copy your Solana address
|
|
295
|
+
Use that address in completeJob()
|
|
296
|
+
|
|
297
|
+
MINT tokens appear instantly in your wallet after job completion.
|
|
298
|
+
|
|
299
|
+
Vision
|
|
300
|
+
FoundryNet is building payment infrastructure for autonomous industry.
|
|
301
|
+
We're starting simple: machines prove work happened, get paid instantly. The protocol handles identity, verification, and settlement. Everything else such as quality systems, marketplaces, reputation—gets built on top.
|
|
302
|
+
This is infrastructure for when AI agents coordinate supply chains and manufacturing capacity becomes programmable.
|
|
303
|
+
|
|
304
|
+
Tokenomics
|
|
305
|
+
Token: MINT (Solana SPL token)
|
|
306
|
+
Contract: 84hLMyG9cnC3Pd9LrWn4NtKMEhQHh4AvxbkcDMQtgem
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Initial treasury funded by protocol creator
|
|
310
|
+
Rewards distributed for completed jobs
|
|
311
|
+
LP available on Raydium (MINT/SOL)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
foundry_client/__init__.py
|
|
5
|
+
foundry_client/client.py
|
|
6
|
+
foundry_client.egg-info/PKG-INFO
|
|
7
|
+
foundry_client.egg-info/SOURCES.txt
|
|
8
|
+
foundry_client.egg-info/dependency_links.txt
|
|
9
|
+
foundry_client.egg-info/requires.txt
|
|
10
|
+
foundry_client.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
foundry_client
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="foundry-client",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
author="FoundryNet",
|
|
10
|
+
description="Python client for FoundryNet - Universal DePIN Protocol for Work Settlement",
|
|
11
|
+
long_description=long_description,
|
|
12
|
+
long_description_content_type="text/markdown",
|
|
13
|
+
url="https://github.com/foundrynet/foundry_net_MINT",
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
],
|
|
25
|
+
python_requires=">=3.8",
|
|
26
|
+
install_requires=[
|
|
27
|
+
"PyNaCl>=1.5.0",
|
|
28
|
+
"base58>=2.1.1",
|
|
29
|
+
"requests>=2.31.0",
|
|
30
|
+
],
|
|
31
|
+
)
|