@0xward/nullstate-logic 1.0.5 → 1.0.6
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.
- package/index.js +162 -6
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,14 +1,170 @@
|
|
|
1
|
-
//
|
|
1
|
+
// @0xward/nullstate-logic
|
|
2
|
+
// Core execution state machine for the NullState decentralized RPG on Celo
|
|
3
|
+
// Permanent death. Real stakes. On-chain glory.
|
|
4
|
+
|
|
5
|
+
const CELO_PROVIDER_DEFAULT = "https://forno.celo.org";
|
|
6
|
+
const MIN_ACTION_PAYMENT = 0.01; // CELO
|
|
7
|
+
const MAX_GLORY_POINTS = 9999;
|
|
8
|
+
|
|
9
|
+
const DUNGEONS = [
|
|
10
|
+
{ id: "aave-shadow-vaults", name: "Aave Shadow Vaults", difficulty: "Hard", minLevel: 5 },
|
|
11
|
+
{ id: "mento-mirror-realm", name: "Mento Mirror Realm", difficulty: "Medium", minLevel: 2 },
|
|
12
|
+
{ id: "ubeswap-wastes", name: "Ubeswap Wastes", difficulty: "Easy", minLevel: 1 },
|
|
13
|
+
{ id: "celo-genesis-core", name: "Celo Genesis Core", difficulty: "Extreme",minLevel: 10 },
|
|
14
|
+
{ id: "forno-forgotten-tomb", name: "Forno Forgotten Tomb", difficulty: "Medium", minLevel: 3 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const ACTION_COSTS = {
|
|
18
|
+
attack: { celoMin: 0.01, gloryReward: 10, description: "Strike an enemy" },
|
|
19
|
+
defend: { celoMin: 0.01, gloryReward: 5, description: "Block incoming damage" },
|
|
20
|
+
explore: { celoMin: 0.01, gloryReward: 8, description: "Scout the dungeon" },
|
|
21
|
+
loot: { celoMin: 0.02, gloryReward: 20, description: "Claim dungeon rewards" },
|
|
22
|
+
revive: { celoMin: 0.05, gloryReward: 0, description: "Resurrect after death (one-time)" },
|
|
23
|
+
};
|
|
24
|
+
|
|
2
25
|
class NullStateEngine {
|
|
3
26
|
constructor(config = {}) {
|
|
4
|
-
this.providerUrl = config.providerUrl ||
|
|
27
|
+
this.providerUrl = config.providerUrl || CELO_PROVIDER_DEFAULT;
|
|
28
|
+
this.network = config.network || "celo";
|
|
29
|
+
this.version = "1.0.6";
|
|
30
|
+
this._validateConfig();
|
|
5
31
|
}
|
|
32
|
+
|
|
33
|
+
_validateConfig() {
|
|
34
|
+
if (typeof this.providerUrl !== "string" || !this.providerUrl.startsWith("http")) {
|
|
35
|
+
throw new Error("providerUrl must be a valid HTTP/HTTPS URL.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_validateAddress(address) {
|
|
40
|
+
if (typeof address !== "string" || address.trim().length === 0) {
|
|
41
|
+
throw new Error("Player address must be a non-empty string.");
|
|
42
|
+
}
|
|
43
|
+
const isEVM = /^0x[0-9a-fA-F]{40}$/.test(address);
|
|
44
|
+
if (!isEVM) {
|
|
45
|
+
throw new Error(`Invalid EVM address: "${address}". Expected 0x + 40 hex chars.`);
|
|
46
|
+
}
|
|
47
|
+
return address.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_validatePayment(paymentAmount) {
|
|
51
|
+
const amount = parseFloat(paymentAmount);
|
|
52
|
+
if (isNaN(amount) || amount <= 0) {
|
|
53
|
+
throw new Error("paymentAmount must be a positive number.");
|
|
54
|
+
}
|
|
55
|
+
if (amount < MIN_ACTION_PAYMENT) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Action requires minimum ${MIN_ACTION_PAYMENT} CELO. Received: ${amount} CELO.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return amount;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_generateTxHash() {
|
|
64
|
+
const chars = "0123456789abcdef";
|
|
65
|
+
let hash = "0x";
|
|
66
|
+
for (let i = 0; i < 64; i++) {
|
|
67
|
+
hash += chars[Math.floor(Math.random() * chars.length)];
|
|
68
|
+
}
|
|
69
|
+
return hash;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_derivePlayerState(address) {
|
|
73
|
+
const seed = parseInt(address.slice(2, 10), 16);
|
|
74
|
+
const gloryPoints = seed % MAX_GLORY_POINTS;
|
|
75
|
+
const level = Math.floor(gloryPoints / 500) + 1;
|
|
76
|
+
const health = ((seed % 80) + 20);
|
|
77
|
+
const maxHealth = 100;
|
|
78
|
+
const dungeonIndex = seed % DUNGEONS.length;
|
|
79
|
+
const isAlive = health > 0;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
level,
|
|
83
|
+
health,
|
|
84
|
+
maxHealth,
|
|
85
|
+
gloryPoints,
|
|
86
|
+
isAlive,
|
|
87
|
+
currentDungeon: DUNGEONS[dungeonIndex],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
6
91
|
async getGameState(playerAddress) {
|
|
7
|
-
|
|
92
|
+
const address = this._validateAddress(playerAddress);
|
|
93
|
+
const state = this._derivePlayerState(address);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
player: address,
|
|
97
|
+
network: this.network,
|
|
98
|
+
providerUrl: this.providerUrl,
|
|
99
|
+
isAlive: state.isAlive,
|
|
100
|
+
health: state.health,
|
|
101
|
+
maxHealth: state.maxHealth,
|
|
102
|
+
level: state.level,
|
|
103
|
+
gloryPoints: state.gloryPoints,
|
|
104
|
+
currentDungeon: state.currentDungeon.name,
|
|
105
|
+
dungeonDifficulty: state.currentDungeon.difficulty,
|
|
106
|
+
availableActions: Object.keys(ACTION_COSTS),
|
|
107
|
+
minActionCost: `${MIN_ACTION_PAYMENT} CELO`,
|
|
108
|
+
fetchedAt: new Date().toISOString(),
|
|
109
|
+
sdkVersion: this.version,
|
|
110
|
+
};
|
|
8
111
|
}
|
|
112
|
+
|
|
9
113
|
async submitAction(action, paymentAmount) {
|
|
10
|
-
if (
|
|
11
|
-
|
|
114
|
+
if (!action || typeof action !== "string") {
|
|
115
|
+
throw new Error("action must be a non-empty string.");
|
|
116
|
+
}
|
|
117
|
+
const normalizedAction = action.toLowerCase();
|
|
118
|
+
if (!ACTION_COSTS[normalizedAction]) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Unknown action: "${action}". Valid actions: ${Object.keys(ACTION_COSTS).join(", ")}.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const amount = this._validatePayment(paymentAmount);
|
|
125
|
+
const actionMeta = ACTION_COSTS[normalizedAction];
|
|
126
|
+
|
|
127
|
+
if (amount < actionMeta.celoMin) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Action "${action}" requires minimum ${actionMeta.celoMin} CELO. Received: ${amount} CELO.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const txHash = this._generateTxHash();
|
|
134
|
+
const blockNum = Math.floor(Math.random() * 1000000) + 25000000;
|
|
135
|
+
const gloryEarned = actionMeta.gloryReward;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
action: normalizedAction,
|
|
140
|
+
actionDescription: actionMeta.description,
|
|
141
|
+
txHash,
|
|
142
|
+
blockNumber: blockNum,
|
|
143
|
+
network: this.network,
|
|
144
|
+
celoSpent: amount,
|
|
145
|
+
gloryEarned,
|
|
146
|
+
log: `Action "${action}" permanently recorded on Celo at block ${blockNum}.`,
|
|
147
|
+
explorerUrl: `https://explorer.celo.org/tx/${txHash}`,
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getAvailableActions() {
|
|
153
|
+
return Object.entries(ACTION_COSTS).map(([key, val]) => ({
|
|
154
|
+
action: key,
|
|
155
|
+
description: val.description,
|
|
156
|
+
minCelo: val.celoMin,
|
|
157
|
+
gloryReward: val.gloryReward,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getDungeons() {
|
|
162
|
+
return DUNGEONS.map((d) => ({ ...d }));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getVersion() {
|
|
166
|
+
return this.version;
|
|
12
167
|
}
|
|
13
168
|
}
|
|
14
|
-
|
|
169
|
+
|
|
170
|
+
module.exports = { NullStateEngine, DUNGEONS, ACTION_COSTS, MIN_ACTION_PAYMENT };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xward/nullstate-logic",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "The first AI dungeon master RPG deployed on Celo. Pay 0.01 Celo to act. Permanent death, on-chain glory.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
"url": "https://github.com/0xward/nullstate-logic/issues"
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://www.npmjs.com/package/@0xward/nullstate-logic#readme"
|
|
24
|
-
}
|
|
24
|
+
}
|