zcashname-sdk 0.8.1 → 0.8.3
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/dist/zns.cjs +125 -2
- package/dist/zns.d.cts +41 -1
- package/dist/zns.d.ts +41 -1
- package/dist/zns.js +123 -1
- package/package.json +1 -1
package/dist/zns.cjs
CHANGED
|
@@ -35,7 +35,8 @@ __export(zns_exports, {
|
|
|
35
35
|
LIST_COMMISSION: () => LIST_COMMISSION,
|
|
36
36
|
MAINNET_UIVK: () => MAINNET_UIVK,
|
|
37
37
|
TESTNET_UIVK: () => TESTNET_UIVK,
|
|
38
|
-
ZNS: () => ZNS
|
|
38
|
+
ZNS: () => ZNS,
|
|
39
|
+
ZNS_ACTIONS: () => ZNS_ACTIONS
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(zns_exports);
|
|
41
42
|
var ed25519 = __toESM(require("@noble/ed25519"), 1);
|
|
@@ -54,7 +55,14 @@ var REGISTRY_ADDRESSES = {
|
|
|
54
55
|
testnet: "utest1f32kn6c4zvn54xr8wfsnxmj9hzpu2mwgtxzpzwcw34906tdccdvzs0z2dx38lly7tpan77x6udt8pjczqm22ymsdhlz9j0tk5yq664nl",
|
|
55
56
|
mainnet: "u1k0evt0ahj5qdt6y9ftsxndl8lrkm4ff6rp00u04cjpmqj6hxl9t8hfsxftmn3ht34e03lljh89czn2h8qn67rwrs8x0hm3lsxsucp9q9"
|
|
56
57
|
};
|
|
58
|
+
var ZNS_ACTIONS = ["CLAIM", "BUY", "UPDATE", "LIST", "DELIST", "RELEASE"];
|
|
57
59
|
var NAME_RE = /^[a-z0-9]{1,62}$/;
|
|
60
|
+
function isWholeNumber(value) {
|
|
61
|
+
return /^\d+$/.test(value) && !value.startsWith("0") || value === "0";
|
|
62
|
+
}
|
|
63
|
+
function mk(level, action, canonical, message) {
|
|
64
|
+
return { valid: level === "valid", action, canonicalAction: canonical, message, level };
|
|
65
|
+
}
|
|
58
66
|
var ZNS = class {
|
|
59
67
|
/**
|
|
60
68
|
* Creates a new ZNS client.
|
|
@@ -182,6 +190,120 @@ var ZNS = class {
|
|
|
182
190
|
isValidName(name) {
|
|
183
191
|
return NAME_RE.test(name);
|
|
184
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Validate a signing payload string against the ZNS memo format spec.
|
|
195
|
+
*
|
|
196
|
+
* This is the single source of truth for payload format validation —
|
|
197
|
+
* it mirrors the Rust indexer's `parse_memo` and `signing_payload` logic.
|
|
198
|
+
*
|
|
199
|
+
* Does NOT validate the signature (see {@link verifyEd25519} for that).
|
|
200
|
+
* Does NOT validate the name against the blockchain (see {@link isAvailable} for that).
|
|
201
|
+
*
|
|
202
|
+
* @param payload - Raw payload string, e.g. `CLAIM:foo:u1abc`
|
|
203
|
+
* @returns Validation result with level and human-readable message
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* const result = zns.validatePayload("CLAIM:alice:u1qvs2...");
|
|
208
|
+
* if (!result.valid) {
|
|
209
|
+
* console.error(result.message);
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
validatePayload(payload) {
|
|
214
|
+
const raw = String(payload ?? "").trim();
|
|
215
|
+
if (!raw) {
|
|
216
|
+
return {
|
|
217
|
+
valid: false,
|
|
218
|
+
action: "",
|
|
219
|
+
canonicalAction: null,
|
|
220
|
+
message: "Empty payload.",
|
|
221
|
+
level: "invalid"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const colonIdx = raw.indexOf(":");
|
|
225
|
+
if (colonIdx === -1) {
|
|
226
|
+
return {
|
|
227
|
+
valid: false,
|
|
228
|
+
action: raw.toUpperCase(),
|
|
229
|
+
canonicalAction: null,
|
|
230
|
+
message: "Missing colon separator. Expected format: ACTION:field1:field2:...",
|
|
231
|
+
level: "invalid"
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const actionUpper = raw.slice(0, colonIdx).toUpperCase();
|
|
235
|
+
const actionLower = raw.slice(0, colonIdx).toLowerCase();
|
|
236
|
+
const rest = raw.slice(colonIdx + 1);
|
|
237
|
+
const parts = rest.split(":");
|
|
238
|
+
switch (actionLower) {
|
|
239
|
+
case "claim": {
|
|
240
|
+
if (parts.length !== 2)
|
|
241
|
+
return mk("invalid", actionUpper, "claim", `Expected CLAIM:<name>:<ua>.`);
|
|
242
|
+
if (!NAME_RE.test(parts[0]))
|
|
243
|
+
return mk("invalid", actionUpper, "claim", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
244
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
245
|
+
return mk("invalid", actionUpper, "claim", `Invalid unified address: "${parts[1]}".`);
|
|
246
|
+
return mk("valid", actionUpper, "claim", "Valid CLAIM payload.");
|
|
247
|
+
}
|
|
248
|
+
case "buy": {
|
|
249
|
+
if (parts.length !== 2)
|
|
250
|
+
return mk("invalid", actionUpper, "buy", `Expected BUY:<name>:<buyer_ua>.`);
|
|
251
|
+
if (!NAME_RE.test(parts[0]))
|
|
252
|
+
return mk("invalid", actionUpper, "buy", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
253
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
254
|
+
return mk("invalid", actionUpper, "buy", `Invalid unified address: "${parts[1]}".`);
|
|
255
|
+
return mk("valid", actionUpper, "buy", "Valid BUY payload.");
|
|
256
|
+
}
|
|
257
|
+
case "update": {
|
|
258
|
+
if (parts.length !== 3)
|
|
259
|
+
return mk("invalid", actionUpper, "update", `Expected UPDATE:<name>:<ua>:<nonce>.`);
|
|
260
|
+
if (!NAME_RE.test(parts[0]))
|
|
261
|
+
return mk("invalid", actionUpper, "update", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
262
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
263
|
+
return mk("invalid", actionUpper, "update", `Invalid unified address: "${parts[1]}".`);
|
|
264
|
+
if (!isWholeNumber(parts[2]))
|
|
265
|
+
return mk("invalid", actionUpper, "update", `Nonce must be a whole number.`);
|
|
266
|
+
return mk("valid", actionUpper, "update", "Valid UPDATE payload.");
|
|
267
|
+
}
|
|
268
|
+
case "list": {
|
|
269
|
+
if (parts.length !== 4)
|
|
270
|
+
return mk("invalid", actionUpper, "list", `Expected LIST:<name>:<price_zats>:<pay_taddr>:<nonce>.`);
|
|
271
|
+
if (!NAME_RE.test(parts[0]))
|
|
272
|
+
return mk("invalid", actionUpper, "list", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
273
|
+
if (!isWholeNumber(parts[1]) || Number(parts[1]) <= 0)
|
|
274
|
+
return mk("invalid", actionUpper, "list", `Price must be a positive whole number in zats.`);
|
|
275
|
+
if (!isWholeNumber(parts[3]))
|
|
276
|
+
return mk("invalid", actionUpper, "list", `Nonce must be a whole number.`);
|
|
277
|
+
return mk("valid", actionUpper, "list", "Valid LIST payload.");
|
|
278
|
+
}
|
|
279
|
+
case "delist": {
|
|
280
|
+
if (parts.length !== 2)
|
|
281
|
+
return mk("invalid", actionUpper, "delist", `Expected DELIST:<name>:<nonce>.`);
|
|
282
|
+
if (!NAME_RE.test(parts[0]))
|
|
283
|
+
return mk("invalid", actionUpper, "delist", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
284
|
+
if (!isWholeNumber(parts[1]))
|
|
285
|
+
return mk("invalid", actionUpper, "delist", `Nonce must be a whole number.`);
|
|
286
|
+
return mk("valid", actionUpper, "delist", "Valid DELIST payload.");
|
|
287
|
+
}
|
|
288
|
+
case "release": {
|
|
289
|
+
if (parts.length !== 2)
|
|
290
|
+
return mk("invalid", actionUpper, "release", `Expected RELEASE:<name>:<nonce>.`);
|
|
291
|
+
if (!NAME_RE.test(parts[0]))
|
|
292
|
+
return mk("invalid", actionUpper, "release", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
293
|
+
if (!isWholeNumber(parts[1]))
|
|
294
|
+
return mk("invalid", actionUpper, "release", `Nonce must be a whole number.`);
|
|
295
|
+
return mk("valid", actionUpper, "release", "Valid RELEASE payload.");
|
|
296
|
+
}
|
|
297
|
+
default:
|
|
298
|
+
return {
|
|
299
|
+
valid: false,
|
|
300
|
+
action: actionUpper,
|
|
301
|
+
canonicalAction: null,
|
|
302
|
+
message: `Unrecognized action "${actionUpper}". Valid actions: ${ZNS_ACTIONS.join(", ")}.`,
|
|
303
|
+
level: "unrecognized"
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
185
307
|
/**
|
|
186
308
|
* Get the claim cost in zatoshis for a name of given length.
|
|
187
309
|
* @param nameLength The length of the name (1-62)
|
|
@@ -416,5 +538,6 @@ var ZNS = class {
|
|
|
416
538
|
LIST_COMMISSION,
|
|
417
539
|
MAINNET_UIVK,
|
|
418
540
|
TESTNET_UIVK,
|
|
419
|
-
ZNS
|
|
541
|
+
ZNS,
|
|
542
|
+
ZNS_ACTIONS
|
|
420
543
|
});
|
package/dist/zns.d.cts
CHANGED
|
@@ -133,10 +133,29 @@ interface PreparedSetPrice extends PreparedAction {
|
|
|
133
133
|
readonly prices: readonly Zats[];
|
|
134
134
|
readonly nonce: number;
|
|
135
135
|
}
|
|
136
|
+
/** Validation result for a signing payload.
|
|
137
|
+
* Mirrors the Rust indexer's parse_memo format validation.
|
|
138
|
+
* @see https://github.com/zcashme/ZNS/blob/main/src/memo.rs */
|
|
139
|
+
type PayloadValidationLevel = "valid" | "invalid" | "unrecognized";
|
|
140
|
+
interface PayloadValidationResult {
|
|
141
|
+
/** Whether the payload is valid for signing. */
|
|
142
|
+
readonly valid: boolean;
|
|
143
|
+
/** Parsed action name (uppercase), e.g. "CLAIM", "LIST" */
|
|
144
|
+
readonly action: string;
|
|
145
|
+
/** Canonical action used internally (lowercase), e.g. "claim", "list" */
|
|
146
|
+
readonly canonicalAction: string | null;
|
|
147
|
+
/** Human-readable validation message */
|
|
148
|
+
readonly message: string;
|
|
149
|
+
/** Validation level: valid | invalid | unrecognized */
|
|
150
|
+
readonly level: PayloadValidationLevel;
|
|
151
|
+
}
|
|
136
152
|
|
|
137
153
|
declare const DEFAULT_URL = "https://light.zcash.me/zns-testnet";
|
|
138
154
|
declare const TESTNET_UIVK = "uivktest1hzw7wyadutvzfgpna80yftsk5l7jeyu2p5me5quvp28tytxueta00cx4068wnlzcv7tx9n3t3gfhsy83pe4y6jrhxtzaq0hj6xtg5zrk2dn7zen3vns2a5pgs4fxdjlletmqrhfa42";
|
|
139
155
|
declare const MAINNET_UIVK = "uivk1gl26qy0xjja7lqhyg3pf0x4j4j66kqwewrjkdcg28eqq4wgtzjmujpee7x9cs2ec9xhnlgrm8ptlw8z80j2aryw8nqtssser2ys778a0s00uvgkdjnfr58sndhfvc3f4zqjs6ywva6";
|
|
156
|
+
/** Actions accepted by {@link validatePayload}. Exposed for consumers who need
|
|
157
|
+
* to build action selectors or dynamic validation. */
|
|
158
|
+
declare const ZNS_ACTIONS: readonly ["CLAIM", "BUY", "UPDATE", "LIST", "DELIST", "RELEASE"];
|
|
140
159
|
type Network = "testnet" | "mainnet";
|
|
141
160
|
declare class ZNS {
|
|
142
161
|
private url;
|
|
@@ -201,6 +220,27 @@ declare class ZNS {
|
|
|
201
220
|
verifyRegistration(reg: Registration, adminPubkey: string): Promise<boolean>;
|
|
202
221
|
/** Check if a name is valid format (lowercase alphanumeric, 1-62 chars). */
|
|
203
222
|
isValidName(name: string): boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Validate a signing payload string against the ZNS memo format spec.
|
|
225
|
+
*
|
|
226
|
+
* This is the single source of truth for payload format validation —
|
|
227
|
+
* it mirrors the Rust indexer's `parse_memo` and `signing_payload` logic.
|
|
228
|
+
*
|
|
229
|
+
* Does NOT validate the signature (see {@link verifyEd25519} for that).
|
|
230
|
+
* Does NOT validate the name against the blockchain (see {@link isAvailable} for that).
|
|
231
|
+
*
|
|
232
|
+
* @param payload - Raw payload string, e.g. `CLAIM:foo:u1abc`
|
|
233
|
+
* @returns Validation result with level and human-readable message
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const result = zns.validatePayload("CLAIM:alice:u1qvs2...");
|
|
238
|
+
* if (!result.valid) {
|
|
239
|
+
* console.error(result.message);
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
validatePayload(payload: string): PayloadValidationResult;
|
|
204
244
|
/**
|
|
205
245
|
* Get the claim cost in zatoshis for a name of given length.
|
|
206
246
|
* @param nameLength The length of the name (1-62)
|
|
@@ -246,4 +286,4 @@ declare class ZNS {
|
|
|
246
286
|
private rpc;
|
|
247
287
|
}
|
|
248
288
|
|
|
249
|
-
export { BUY_COMMISSION, type CompletedAction, DEFAULT_URL, type Event, type EventAction, type EventsFilter, type EventsResult, LIST_COMMISSION, type LastAction, type Listing, MAINNET_UIVK, type Network, type PendingBuy, type PreparedBuy, type PreparedClaim, type PreparedDelist, type PreparedList, type PreparedRelease, type PreparedSetPrice, type PreparedUpdate, type Pricing, type Registration, type Status, TESTNET_UIVK, ZNS, type Zats };
|
|
289
|
+
export { BUY_COMMISSION, type CompletedAction, DEFAULT_URL, type Event, type EventAction, type EventsFilter, type EventsResult, LIST_COMMISSION, type LastAction, type Listing, MAINNET_UIVK, type Network, type PayloadValidationLevel, type PayloadValidationResult, type PendingBuy, type PreparedBuy, type PreparedClaim, type PreparedDelist, type PreparedList, type PreparedRelease, type PreparedSetPrice, type PreparedUpdate, type Pricing, type Registration, type Status, TESTNET_UIVK, ZNS, ZNS_ACTIONS, type Zats };
|
package/dist/zns.d.ts
CHANGED
|
@@ -133,10 +133,29 @@ interface PreparedSetPrice extends PreparedAction {
|
|
|
133
133
|
readonly prices: readonly Zats[];
|
|
134
134
|
readonly nonce: number;
|
|
135
135
|
}
|
|
136
|
+
/** Validation result for a signing payload.
|
|
137
|
+
* Mirrors the Rust indexer's parse_memo format validation.
|
|
138
|
+
* @see https://github.com/zcashme/ZNS/blob/main/src/memo.rs */
|
|
139
|
+
type PayloadValidationLevel = "valid" | "invalid" | "unrecognized";
|
|
140
|
+
interface PayloadValidationResult {
|
|
141
|
+
/** Whether the payload is valid for signing. */
|
|
142
|
+
readonly valid: boolean;
|
|
143
|
+
/** Parsed action name (uppercase), e.g. "CLAIM", "LIST" */
|
|
144
|
+
readonly action: string;
|
|
145
|
+
/** Canonical action used internally (lowercase), e.g. "claim", "list" */
|
|
146
|
+
readonly canonicalAction: string | null;
|
|
147
|
+
/** Human-readable validation message */
|
|
148
|
+
readonly message: string;
|
|
149
|
+
/** Validation level: valid | invalid | unrecognized */
|
|
150
|
+
readonly level: PayloadValidationLevel;
|
|
151
|
+
}
|
|
136
152
|
|
|
137
153
|
declare const DEFAULT_URL = "https://light.zcash.me/zns-testnet";
|
|
138
154
|
declare const TESTNET_UIVK = "uivktest1hzw7wyadutvzfgpna80yftsk5l7jeyu2p5me5quvp28tytxueta00cx4068wnlzcv7tx9n3t3gfhsy83pe4y6jrhxtzaq0hj6xtg5zrk2dn7zen3vns2a5pgs4fxdjlletmqrhfa42";
|
|
139
155
|
declare const MAINNET_UIVK = "uivk1gl26qy0xjja7lqhyg3pf0x4j4j66kqwewrjkdcg28eqq4wgtzjmujpee7x9cs2ec9xhnlgrm8ptlw8z80j2aryw8nqtssser2ys778a0s00uvgkdjnfr58sndhfvc3f4zqjs6ywva6";
|
|
156
|
+
/** Actions accepted by {@link validatePayload}. Exposed for consumers who need
|
|
157
|
+
* to build action selectors or dynamic validation. */
|
|
158
|
+
declare const ZNS_ACTIONS: readonly ["CLAIM", "BUY", "UPDATE", "LIST", "DELIST", "RELEASE"];
|
|
140
159
|
type Network = "testnet" | "mainnet";
|
|
141
160
|
declare class ZNS {
|
|
142
161
|
private url;
|
|
@@ -201,6 +220,27 @@ declare class ZNS {
|
|
|
201
220
|
verifyRegistration(reg: Registration, adminPubkey: string): Promise<boolean>;
|
|
202
221
|
/** Check if a name is valid format (lowercase alphanumeric, 1-62 chars). */
|
|
203
222
|
isValidName(name: string): boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Validate a signing payload string against the ZNS memo format spec.
|
|
225
|
+
*
|
|
226
|
+
* This is the single source of truth for payload format validation —
|
|
227
|
+
* it mirrors the Rust indexer's `parse_memo` and `signing_payload` logic.
|
|
228
|
+
*
|
|
229
|
+
* Does NOT validate the signature (see {@link verifyEd25519} for that).
|
|
230
|
+
* Does NOT validate the name against the blockchain (see {@link isAvailable} for that).
|
|
231
|
+
*
|
|
232
|
+
* @param payload - Raw payload string, e.g. `CLAIM:foo:u1abc`
|
|
233
|
+
* @returns Validation result with level and human-readable message
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const result = zns.validatePayload("CLAIM:alice:u1qvs2...");
|
|
238
|
+
* if (!result.valid) {
|
|
239
|
+
* console.error(result.message);
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
validatePayload(payload: string): PayloadValidationResult;
|
|
204
244
|
/**
|
|
205
245
|
* Get the claim cost in zatoshis for a name of given length.
|
|
206
246
|
* @param nameLength The length of the name (1-62)
|
|
@@ -246,4 +286,4 @@ declare class ZNS {
|
|
|
246
286
|
private rpc;
|
|
247
287
|
}
|
|
248
288
|
|
|
249
|
-
export { BUY_COMMISSION, type CompletedAction, DEFAULT_URL, type Event, type EventAction, type EventsFilter, type EventsResult, LIST_COMMISSION, type LastAction, type Listing, MAINNET_UIVK, type Network, type PendingBuy, type PreparedBuy, type PreparedClaim, type PreparedDelist, type PreparedList, type PreparedRelease, type PreparedSetPrice, type PreparedUpdate, type Pricing, type Registration, type Status, TESTNET_UIVK, ZNS, type Zats };
|
|
289
|
+
export { BUY_COMMISSION, type CompletedAction, DEFAULT_URL, type Event, type EventAction, type EventsFilter, type EventsResult, LIST_COMMISSION, type LastAction, type Listing, MAINNET_UIVK, type Network, type PayloadValidationLevel, type PayloadValidationResult, type PendingBuy, type PreparedBuy, type PreparedClaim, type PreparedDelist, type PreparedList, type PreparedRelease, type PreparedSetPrice, type PreparedUpdate, type Pricing, type Registration, type Status, TESTNET_UIVK, ZNS, ZNS_ACTIONS, type Zats };
|
package/dist/zns.js
CHANGED
|
@@ -15,7 +15,14 @@ var REGISTRY_ADDRESSES = {
|
|
|
15
15
|
testnet: "utest1f32kn6c4zvn54xr8wfsnxmj9hzpu2mwgtxzpzwcw34906tdccdvzs0z2dx38lly7tpan77x6udt8pjczqm22ymsdhlz9j0tk5yq664nl",
|
|
16
16
|
mainnet: "u1k0evt0ahj5qdt6y9ftsxndl8lrkm4ff6rp00u04cjpmqj6hxl9t8hfsxftmn3ht34e03lljh89czn2h8qn67rwrs8x0hm3lsxsucp9q9"
|
|
17
17
|
};
|
|
18
|
+
var ZNS_ACTIONS = ["CLAIM", "BUY", "UPDATE", "LIST", "DELIST", "RELEASE"];
|
|
18
19
|
var NAME_RE = /^[a-z0-9]{1,62}$/;
|
|
20
|
+
function isWholeNumber(value) {
|
|
21
|
+
return /^\d+$/.test(value) && !value.startsWith("0") || value === "0";
|
|
22
|
+
}
|
|
23
|
+
function mk(level, action, canonical, message) {
|
|
24
|
+
return { valid: level === "valid", action, canonicalAction: canonical, message, level };
|
|
25
|
+
}
|
|
19
26
|
var ZNS = class {
|
|
20
27
|
/**
|
|
21
28
|
* Creates a new ZNS client.
|
|
@@ -143,6 +150,120 @@ var ZNS = class {
|
|
|
143
150
|
isValidName(name) {
|
|
144
151
|
return NAME_RE.test(name);
|
|
145
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Validate a signing payload string against the ZNS memo format spec.
|
|
155
|
+
*
|
|
156
|
+
* This is the single source of truth for payload format validation —
|
|
157
|
+
* it mirrors the Rust indexer's `parse_memo` and `signing_payload` logic.
|
|
158
|
+
*
|
|
159
|
+
* Does NOT validate the signature (see {@link verifyEd25519} for that).
|
|
160
|
+
* Does NOT validate the name against the blockchain (see {@link isAvailable} for that).
|
|
161
|
+
*
|
|
162
|
+
* @param payload - Raw payload string, e.g. `CLAIM:foo:u1abc`
|
|
163
|
+
* @returns Validation result with level and human-readable message
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const result = zns.validatePayload("CLAIM:alice:u1qvs2...");
|
|
168
|
+
* if (!result.valid) {
|
|
169
|
+
* console.error(result.message);
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
validatePayload(payload) {
|
|
174
|
+
const raw = String(payload ?? "").trim();
|
|
175
|
+
if (!raw) {
|
|
176
|
+
return {
|
|
177
|
+
valid: false,
|
|
178
|
+
action: "",
|
|
179
|
+
canonicalAction: null,
|
|
180
|
+
message: "Empty payload.",
|
|
181
|
+
level: "invalid"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const colonIdx = raw.indexOf(":");
|
|
185
|
+
if (colonIdx === -1) {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
action: raw.toUpperCase(),
|
|
189
|
+
canonicalAction: null,
|
|
190
|
+
message: "Missing colon separator. Expected format: ACTION:field1:field2:...",
|
|
191
|
+
level: "invalid"
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const actionUpper = raw.slice(0, colonIdx).toUpperCase();
|
|
195
|
+
const actionLower = raw.slice(0, colonIdx).toLowerCase();
|
|
196
|
+
const rest = raw.slice(colonIdx + 1);
|
|
197
|
+
const parts = rest.split(":");
|
|
198
|
+
switch (actionLower) {
|
|
199
|
+
case "claim": {
|
|
200
|
+
if (parts.length !== 2)
|
|
201
|
+
return mk("invalid", actionUpper, "claim", `Expected CLAIM:<name>:<ua>.`);
|
|
202
|
+
if (!NAME_RE.test(parts[0]))
|
|
203
|
+
return mk("invalid", actionUpper, "claim", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
204
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
205
|
+
return mk("invalid", actionUpper, "claim", `Invalid unified address: "${parts[1]}".`);
|
|
206
|
+
return mk("valid", actionUpper, "claim", "Valid CLAIM payload.");
|
|
207
|
+
}
|
|
208
|
+
case "buy": {
|
|
209
|
+
if (parts.length !== 2)
|
|
210
|
+
return mk("invalid", actionUpper, "buy", `Expected BUY:<name>:<buyer_ua>.`);
|
|
211
|
+
if (!NAME_RE.test(parts[0]))
|
|
212
|
+
return mk("invalid", actionUpper, "buy", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
213
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
214
|
+
return mk("invalid", actionUpper, "buy", `Invalid unified address: "${parts[1]}".`);
|
|
215
|
+
return mk("valid", actionUpper, "buy", "Valid BUY payload.");
|
|
216
|
+
}
|
|
217
|
+
case "update": {
|
|
218
|
+
if (parts.length !== 3)
|
|
219
|
+
return mk("invalid", actionUpper, "update", `Expected UPDATE:<name>:<ua>:<nonce>.`);
|
|
220
|
+
if (!NAME_RE.test(parts[0]))
|
|
221
|
+
return mk("invalid", actionUpper, "update", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
222
|
+
if (!this.isValidUnifiedAddress(parts[1]))
|
|
223
|
+
return mk("invalid", actionUpper, "update", `Invalid unified address: "${parts[1]}".`);
|
|
224
|
+
if (!isWholeNumber(parts[2]))
|
|
225
|
+
return mk("invalid", actionUpper, "update", `Nonce must be a whole number.`);
|
|
226
|
+
return mk("valid", actionUpper, "update", "Valid UPDATE payload.");
|
|
227
|
+
}
|
|
228
|
+
case "list": {
|
|
229
|
+
if (parts.length !== 4)
|
|
230
|
+
return mk("invalid", actionUpper, "list", `Expected LIST:<name>:<price_zats>:<pay_taddr>:<nonce>.`);
|
|
231
|
+
if (!NAME_RE.test(parts[0]))
|
|
232
|
+
return mk("invalid", actionUpper, "list", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
233
|
+
if (!isWholeNumber(parts[1]) || Number(parts[1]) <= 0)
|
|
234
|
+
return mk("invalid", actionUpper, "list", `Price must be a positive whole number in zats.`);
|
|
235
|
+
if (!isWholeNumber(parts[3]))
|
|
236
|
+
return mk("invalid", actionUpper, "list", `Nonce must be a whole number.`);
|
|
237
|
+
return mk("valid", actionUpper, "list", "Valid LIST payload.");
|
|
238
|
+
}
|
|
239
|
+
case "delist": {
|
|
240
|
+
if (parts.length !== 2)
|
|
241
|
+
return mk("invalid", actionUpper, "delist", `Expected DELIST:<name>:<nonce>.`);
|
|
242
|
+
if (!NAME_RE.test(parts[0]))
|
|
243
|
+
return mk("invalid", actionUpper, "delist", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
244
|
+
if (!isWholeNumber(parts[1]))
|
|
245
|
+
return mk("invalid", actionUpper, "delist", `Nonce must be a whole number.`);
|
|
246
|
+
return mk("valid", actionUpper, "delist", "Valid DELIST payload.");
|
|
247
|
+
}
|
|
248
|
+
case "release": {
|
|
249
|
+
if (parts.length !== 2)
|
|
250
|
+
return mk("invalid", actionUpper, "release", `Expected RELEASE:<name>:<nonce>.`);
|
|
251
|
+
if (!NAME_RE.test(parts[0]))
|
|
252
|
+
return mk("invalid", actionUpper, "release", `Invalid name. Use lowercase a-z and 0-9, 1 to 62 chars.`);
|
|
253
|
+
if (!isWholeNumber(parts[1]))
|
|
254
|
+
return mk("invalid", actionUpper, "release", `Nonce must be a whole number.`);
|
|
255
|
+
return mk("valid", actionUpper, "release", "Valid RELEASE payload.");
|
|
256
|
+
}
|
|
257
|
+
default:
|
|
258
|
+
return {
|
|
259
|
+
valid: false,
|
|
260
|
+
action: actionUpper,
|
|
261
|
+
canonicalAction: null,
|
|
262
|
+
message: `Unrecognized action "${actionUpper}". Valid actions: ${ZNS_ACTIONS.join(", ")}.`,
|
|
263
|
+
level: "unrecognized"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
146
267
|
/**
|
|
147
268
|
* Get the claim cost in zatoshis for a name of given length.
|
|
148
269
|
* @param nameLength The length of the name (1-62)
|
|
@@ -376,5 +497,6 @@ export {
|
|
|
376
497
|
LIST_COMMISSION,
|
|
377
498
|
MAINNET_UIVK,
|
|
378
499
|
TESTNET_UIVK,
|
|
379
|
-
ZNS
|
|
500
|
+
ZNS,
|
|
501
|
+
ZNS_ACTIONS
|
|
380
502
|
};
|