+1
.env.example
+1
.env.example
···
···
1
+
PORT: 80
+9
.gitignore
+9
.gitignore
+21
LICENSE
+21
LICENSE
···
···
1
+
MIT License
2
+
3
+
Copyright (c) 2025 Johannes Reckers.
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.
+218
README.md
+218
README.md
···
···
1
+
# Shard Currency API
2
+
3
+
A **very small**, fictional currency API for games and community systems.
4
+
5
+
* One balance per user
6
+
* No real-world money
7
+
* Users can **never** change their own balance
8
+
* Balance changes are performed only by **actors** via scoped tokens
9
+
10
+
---
11
+
12
+
## Core Concepts
13
+
14
+
### User
15
+
16
+
A human account with exactly one balance.
17
+
18
+
### Actor
19
+
20
+
A trusted external service (Minecraft server, Discord bot, admin tool, etc.) that may create transactions.
21
+
22
+
### Transaction
23
+
24
+
An immutable record of a balance change.
25
+
26
+
### Link Code
27
+
28
+
A short-lived, one-time code used to create an actor and issue its token.
29
+
30
+
---
31
+
32
+
## Authentication
33
+
34
+
### User Auth
35
+
36
+
```http
37
+
POST /v1/auth/register
38
+
POST /v1/auth/login
39
+
```
40
+
41
+
User tokens:
42
+
43
+
* allow viewing balance
44
+
* allow viewing transactions
45
+
* allow requesting link codes
46
+
* **never allow transactions**
47
+
48
+
---
49
+
50
+
## User
51
+
52
+
```json
53
+
{
54
+
"id": "usr_01HXYZ",
55
+
"email": "user@example.com",
56
+
"balance": 420,
57
+
"created_at": "2025-09-01T12:00:00Z"
58
+
}
59
+
```
60
+
61
+
---
62
+
63
+
## Actor
64
+
65
+
Actors represent *authority*, not users.
66
+
67
+
```json
68
+
{
69
+
"id": "act_01HABC",
70
+
"name": "Survival SMP",
71
+
"permissions": {
72
+
"max_single_transaction": 100,
73
+
"daily_cap": 5000,
74
+
"allow_negative": true
75
+
},
76
+
"expires_at": "2025-10-10T00:00:00Z"
77
+
}
78
+
```
79
+
80
+
Notes:
81
+
82
+
* Permissions are defined by the API / API_KEY holder
83
+
* Public clients cannot choose permissions
84
+
85
+
---
86
+
87
+
## Actor Token
88
+
89
+
Issued when a link code is consumed.
90
+
91
+
```json
92
+
{
93
+
"actor_id": "act_01HABC",
94
+
"token": "act_live_abc123",
95
+
"expires_at": "2025-10-10T00:00:00Z"
96
+
}
97
+
```
98
+
99
+
Used as:
100
+
101
+
```http
102
+
Authorization: Bearer act_live_abc123
103
+
```
104
+
105
+
---
106
+
107
+
## Link Codes
108
+
109
+
### Request a Link Code
110
+
111
+
Used by a trusted UI (e.g. Discord bot using `API_KEY`).
112
+
113
+
```http
114
+
POST /v1/links/request
115
+
Authorization: ApiKey ${API_KEY}
116
+
```
117
+
118
+
```json
119
+
{
120
+
"expires_in": 300
121
+
}
122
+
```
123
+
124
+
Response:
125
+
126
+
```json
127
+
{
128
+
"code": "Z9FQ-2KDL"
129
+
}
130
+
```
131
+
132
+
---
133
+
134
+
### Consume a Link Code
135
+
136
+
Used by the external service (e.g. Minecraft server).
137
+
138
+
```http
139
+
POST /v1/links/consume
140
+
```
141
+
142
+
```json
143
+
{
144
+
"code": "Z9FQ-2KDL",
145
+
"name": "Survival SMP"
146
+
}
147
+
```
148
+
149
+
Response:
150
+
151
+
```json
152
+
{
153
+
"actor_id": "act_01HABC",
154
+
"token": "act_live_abc123",
155
+
"expires_at": "2025-10-10T00:00:00Z"
156
+
}
157
+
```
158
+
159
+
---
160
+
161
+
## Transactions
162
+
163
+
Only actors or the system may create transactions.
164
+
165
+
```http
166
+
POST /v1/transactions
167
+
Authorization: Bearer ACTOR_TOKEN
168
+
```
169
+
170
+
```json
171
+
{
172
+
"user_id": "usr_01HXYZ",
173
+
"amount": 25,
174
+
"reason": "Quest reward"
175
+
}
176
+
```
177
+
178
+
Rules:
179
+
180
+
* `amount > 0` → credit
181
+
* `amount < 0` → debit
182
+
* Amount must be within actor limits
183
+
* Transactions are append-only
184
+
185
+
---
186
+
187
+
## Transaction Record
188
+
189
+
```json
190
+
{
191
+
"id": "txn_01HZAB",
192
+
"user_id": "usr_01HXYZ",
193
+
"amount": 25,
194
+
"reason": "Quest reward",
195
+
"actor_id": "act_01HABC",
196
+
"created_at": "2025-09-10T18:42:00Z"
197
+
}
198
+
```
199
+
200
+
---
201
+
202
+
## API_KEY (Host-Level Access)
203
+
204
+
```http
205
+
Authorization: ApiKey ${API_KEY}
206
+
```
207
+
208
+
Capabilities:
209
+
210
+
* Create link codes
211
+
* Create actors directly
212
+
* Create transactions without actor limits
213
+
214
+
All actions are still logged.
215
+
216
+
⚠️ Never expose this key publicly.
217
+
218
+
---
+26
bun.lock
+26
bun.lock
···
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"configVersion": 1,
4
+
"workspaces": {
5
+
"": {
6
+
"name": "shard",
7
+
"dependencies": {
8
+
"hono": "^4.11.1",
9
+
},
10
+
"devDependencies": {
11
+
"@types/bun": "^1.3.5",
12
+
},
13
+
},
14
+
},
15
+
"packages": {
16
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
17
+
18
+
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
19
+
20
+
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
21
+
22
+
"hono": ["hono@4.11.1", "", {}, "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg=="],
23
+
24
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
25
+
}
26
+
}
+27
package.json
+27
package.json
···
···
1
+
{
2
+
"name": "shard",
3
+
"version": "0.1.0",
4
+
"description": "Fictional currency management API, for integration in games, discord bots and more!",
5
+
"main": "src/index.ts",
6
+
"scripts": {
7
+
"start": "bun .",
8
+
"dev": "bun --watch ."
9
+
},
10
+
"keywords": [
11
+
"shard",
12
+
"currency",
13
+
"management",
14
+
"api",
15
+
"integration",
16
+
"games",
17
+
"discord"
18
+
],
19
+
"author": "Johannes Reckers",
20
+
"license": "MIT",
21
+
"dependencies": {
22
+
"hono": "^4.11.1"
23
+
},
24
+
"devDependencies": {
25
+
"@types/bun": "^1.3.5"
26
+
}
27
+
}
+25
src/index.ts
+25
src/index.ts
···
···
1
+
// Import Dependencies
2
+
import { Hono } from "hono";
3
+
import { version } from "../package.json";
4
+
5
+
// Import Routers
6
+
import { v1 } from "./routes/v1";
7
+
8
+
// Instantiate new Hono instance
9
+
const app = new Hono();
10
+
11
+
// Server information notice
12
+
app.get("/", (c) =>
13
+
c.text(
14
+
`This domain is running a Shard currency server instance, on version ${version}\n\nFor more information, visit https://tangled.org/thevoid.cafe/shard`,
15
+
),
16
+
);
17
+
18
+
// Router mappings
19
+
app.route("/v1", v1);
20
+
21
+
// Export Hono instance configuration
22
+
export default {
23
+
fetch: app.fetch,
24
+
port: Bun.env.PORT || 80,
25
+
};
+5
src/routes/v1/auth/index.ts
+5
src/routes/v1/auth/index.ts
+11
src/routes/v1/index.ts
+11
src/routes/v1/index.ts
+5
src/routes/v1/links/index.ts
+5
src/routes/v1/links/index.ts