@x1id/resolve 0.19.0 → 0.19.2
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/adminMarket.d.ts +13 -2
- package/dist/adminMarket.js +6 -1
- package/dist/base58.js +8 -0
- package/dist/gate.js +11 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/register.js +31 -0
- package/dist/subname.js +6 -0
- package/dist/textRecords.js +7 -0
- package/dist/transfer.d.ts +45 -0
- package/dist/transfer.js +56 -0
- package/dist/types.d.ts +4 -0
- package/dist/x402.js +8 -0
- package/package.json +1 -1
package/dist/adminMarket.d.ts
CHANGED
|
@@ -44,9 +44,20 @@ export declare const NAMESPACE_MAX_LEN = 16;
|
|
|
44
44
|
export type NamespaceStatus = "Active" | "Paused" | "Retired";
|
|
45
45
|
export interface CreateNamespaceParams {
|
|
46
46
|
readonly programId: AddressLike;
|
|
47
|
-
/**
|
|
48
|
-
|
|
47
|
+
/** The creator — signs AND pays rent + the creation fee; writable.
|
|
48
|
+
* Permissionless: any wallet, not an admin. (Was mis-named `admin` and
|
|
49
|
+
* built with too few accounts, so the ix failed AccountNotEnoughKeys 3005 —
|
|
50
|
+
* W169.) */
|
|
51
|
+
readonly payer: AddressLike;
|
|
49
52
|
readonly config: AddressLike;
|
|
53
|
+
/** `Config.treasury` — receives the creation fee; writable. */
|
|
54
|
+
readonly treasury: AddressLike;
|
|
55
|
+
/** The creator's chosen payout wallet, recorded on the namespace for future
|
|
56
|
+
* 75% primary/secondary cuts. An arbitrary pubkey; never signs. */
|
|
57
|
+
readonly creatorPayout: AddressLike;
|
|
58
|
+
/** `["reserved_namespace", label]` PDA — the denylist shadow the handler
|
|
59
|
+
* refuses creation against if it exists; read-only, never written. */
|
|
60
|
+
readonly reserved: AddressLike;
|
|
50
61
|
/** `["namespace", label]` PDA — init, writable. */
|
|
51
62
|
readonly namespace: AddressLike;
|
|
52
63
|
/** Canonical ASCII label (a-z 0-9 -), 1..=16 bytes — an instruction ARG. */
|
package/dist/adminMarket.js
CHANGED
|
@@ -111,9 +111,14 @@ export function buildCreateNamespaceIx(p) {
|
|
|
111
111
|
throw new Error(`label must be 1..=${NAMESPACE_MAX_LEN} chars`);
|
|
112
112
|
}
|
|
113
113
|
const data = concat([CREATE_NAMESPACE_DISCRIMINATOR, borshString(p.label)]);
|
|
114
|
+
// Account order matches the program's `CreateNamespace` context exactly:
|
|
115
|
+
// payer, config, treasury, creator_payout, reserved, namespace, system.
|
|
114
116
|
const keys = [
|
|
115
|
-
{ pubkey: toBase58(p.
|
|
117
|
+
{ pubkey: toBase58(p.payer, "payer"), isSigner: true, isWritable: true },
|
|
116
118
|
{ pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: false },
|
|
119
|
+
{ pubkey: toBase58(p.treasury, "treasury"), isSigner: false, isWritable: true },
|
|
120
|
+
{ pubkey: toBase58(p.creatorPayout, "creatorPayout"), isSigner: false, isWritable: false },
|
|
121
|
+
{ pubkey: toBase58(p.reserved, "reserved"), isSigner: false, isWritable: false },
|
|
117
122
|
{ pubkey: toBase58(p.namespace, "namespace"), isSigner: false, isWritable: true },
|
|
118
123
|
{ pubkey: toBase58(p.systemProgram ?? SYSTEM_PROGRAM_ID, "systemProgram"), isSigner: false, isWritable: false },
|
|
119
124
|
];
|
package/dist/base58.js
CHANGED
|
@@ -65,5 +65,13 @@ export function decodeBase58_32(s) {
|
|
|
65
65
|
out[i] = Number(n & 255n);
|
|
66
66
|
n >>= 8n;
|
|
67
67
|
}
|
|
68
|
+
// W167: reject anything that isn't the canonical base58 of EXACTLY these 32
|
|
69
|
+
// bytes. Without this, a short valid-base58 string ("notakey") decodes to a
|
|
70
|
+
// small number and is silently LEFT-PADDED into a bogus 32-byte "address",
|
|
71
|
+
// so builders / sign-in / resolve / MCP prepare_register accept garbage as a
|
|
72
|
+
// pubkey. The round-trip also correctly accepts a real key with leading zero
|
|
73
|
+
// bytes (encodeBase58 re-emits its leading '1's).
|
|
74
|
+
if (encodeBase58(out) !== s)
|
|
75
|
+
return null;
|
|
68
76
|
return out;
|
|
69
77
|
}
|
package/dist/gate.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* attestor-signed `Attestation` account.
|
|
16
16
|
*/
|
|
17
17
|
import { fetchAttestations, isHandleVerified, ATTESTATION_KIND_DNS, ATTESTATION_KIND_SOCIAL, ATTESTATION_KIND_X, ATTESTATION_KIND_DISCORD, ATTESTATION_KIND_GITHUB, ATTESTATION_KIND_TELEGRAM, } from "./attestation.js";
|
|
18
|
+
import { ResolveError } from "./types.js";
|
|
18
19
|
/** Named platform kinds, re-exported so callers gate without magic numbers. */
|
|
19
20
|
export const GateKind = {
|
|
20
21
|
dns: ATTESTATION_KIND_DNS,
|
|
@@ -29,7 +30,16 @@ export const GateKind = {
|
|
|
29
30
|
export function attestationsPassGate(attestations, gate) {
|
|
30
31
|
if (gate === "any-verified")
|
|
31
32
|
return isHandleVerified(attestations);
|
|
32
|
-
|
|
33
|
+
// W171: a malformed gate (null, a string other than "any-verified", or an
|
|
34
|
+
// object with neither `kind` nor `anyOf`) otherwise threw a raw
|
|
35
|
+
// `TypeError: Cannot use 'in' operator…`. Surface it as a typed ResolveError.
|
|
36
|
+
if (gate === null || typeof gate !== "object") {
|
|
37
|
+
throw new ResolveError("invalid-input", 'gate must be "any-verified", { kind } or { anyOf: [...] }', String(gate));
|
|
38
|
+
}
|
|
39
|
+
const kinds = "kind" in gate ? [gate.kind] : "anyOf" in gate ? gate.anyOf : null;
|
|
40
|
+
if (kinds === null) {
|
|
41
|
+
throw new ResolveError("invalid-input", "gate object needs a `kind` or `anyOf`", JSON.stringify(gate));
|
|
42
|
+
}
|
|
33
43
|
return attestations.some((a) => !a.stale && !a.holderMismatch && kinds.includes(a.kind));
|
|
34
44
|
}
|
|
35
45
|
/** Fetch a handle's attestations and test the gate in one call. `handleAccount`,
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export * from "./integrator.js";
|
|
|
42
42
|
export * from "./voucher.js";
|
|
43
43
|
export * from "./agent.js";
|
|
44
44
|
export * from "./register.js";
|
|
45
|
+
export * from "./transfer.js";
|
|
45
46
|
export * from "./adminConfig.js";
|
|
46
47
|
export * from "./lease.js";
|
|
47
48
|
export * from "./accounts.js";
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ export * from "./integrator.js";
|
|
|
42
42
|
export * from "./voucher.js";
|
|
43
43
|
export * from "./agent.js";
|
|
44
44
|
export * from "./register.js";
|
|
45
|
+
export * from "./transfer.js";
|
|
45
46
|
export * from "./adminConfig.js";
|
|
46
47
|
export * from "./lease.js";
|
|
47
48
|
// Was previously imported here for internal use only, never re-exported —
|
package/dist/register.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* field this builder needs (the treasury address every registration pays).
|
|
17
17
|
*/
|
|
18
18
|
import { encodeBase58, decodeBase58_32 } from "./base58.js";
|
|
19
|
+
import { normalizeHandle } from "./parse.js";
|
|
19
20
|
/** Anchor instruction discriminator: `sha256("global:register")[0..8]`. */
|
|
20
21
|
export const REGISTER_DISCRIMINATOR = Uint8Array.from([
|
|
21
22
|
211, 124, 67, 15, 211, 194, 178, 240,
|
|
@@ -73,6 +74,16 @@ export function buildRegisterIx(p) {
|
|
|
73
74
|
if (!Number.isInteger(p.handleType) || p.handleType < 0 || p.handleType > 3) {
|
|
74
75
|
throw new Error("handleType must be 0 (Human), 1 (Merchant), 2 (Org) or 3 (Agent)");
|
|
75
76
|
}
|
|
77
|
+
// W171: the Handle PDA is derived from the CANONICAL name, so a non-canonical
|
|
78
|
+
// name (uppercase, surrounding space, …) builds an instruction the chain
|
|
79
|
+
// rejects with an opaque ConstraintSeeds (2006). normalizeHandle validates the
|
|
80
|
+
// grammar (ASCII, length, no leading/trailing/double hyphen, not all-digit)
|
|
81
|
+
// AND lowercases; require the caller to pass that canonical form so `handle`
|
|
82
|
+
// and the encoded name agree.
|
|
83
|
+
const canonical = normalizeHandle(p.name);
|
|
84
|
+
if (canonical !== p.name) {
|
|
85
|
+
throw new Error(`name must be the canonical handle "${canonical}" (got "${p.name}") — normalizeHandle() it and derive \`handle\` from the same value`);
|
|
86
|
+
}
|
|
76
87
|
const nameBytes = new TextEncoder().encode(p.name);
|
|
77
88
|
if (nameBytes.length < 1 || nameBytes.length > 32) {
|
|
78
89
|
throw new Error("name must be 1..=32 bytes (UTF-8)");
|
|
@@ -123,6 +134,16 @@ export function buildRegisterReservedIx(p) {
|
|
|
123
134
|
if (!Number.isInteger(p.handleType) || p.handleType < 0 || p.handleType > 3) {
|
|
124
135
|
throw new Error("handleType must be 0 (Human), 1 (Merchant), 2 (Org) or 3 (Agent)");
|
|
125
136
|
}
|
|
137
|
+
// W171: the Handle PDA is derived from the CANONICAL name, so a non-canonical
|
|
138
|
+
// name (uppercase, surrounding space, …) builds an instruction the chain
|
|
139
|
+
// rejects with an opaque ConstraintSeeds (2006). normalizeHandle validates the
|
|
140
|
+
// grammar (ASCII, length, no leading/trailing/double hyphen, not all-digit)
|
|
141
|
+
// AND lowercases; require the caller to pass that canonical form so `handle`
|
|
142
|
+
// and the encoded name agree.
|
|
143
|
+
const canonical = normalizeHandle(p.name);
|
|
144
|
+
if (canonical !== p.name) {
|
|
145
|
+
throw new Error(`name must be the canonical handle "${canonical}" (got "${p.name}") — normalizeHandle() it and derive \`handle\` from the same value`);
|
|
146
|
+
}
|
|
126
147
|
const nameBytes = new TextEncoder().encode(p.name);
|
|
127
148
|
if (nameBytes.length < 1 || nameBytes.length > 32) {
|
|
128
149
|
throw new Error("name must be 1..=32 bytes (UTF-8)");
|
|
@@ -194,6 +215,16 @@ export function buildRegisterWithFortiIx(p) {
|
|
|
194
215
|
if (!Number.isInteger(p.handleType) || p.handleType < 0 || p.handleType > 3) {
|
|
195
216
|
throw new Error("handleType must be 0 (Human), 1 (Merchant), 2 (Org) or 3 (Agent)");
|
|
196
217
|
}
|
|
218
|
+
// W171: the Handle PDA is derived from the CANONICAL name, so a non-canonical
|
|
219
|
+
// name (uppercase, surrounding space, …) builds an instruction the chain
|
|
220
|
+
// rejects with an opaque ConstraintSeeds (2006). normalizeHandle validates the
|
|
221
|
+
// grammar (ASCII, length, no leading/trailing/double hyphen, not all-digit)
|
|
222
|
+
// AND lowercases; require the caller to pass that canonical form so `handle`
|
|
223
|
+
// and the encoded name agree.
|
|
224
|
+
const canonical = normalizeHandle(p.name);
|
|
225
|
+
if (canonical !== p.name) {
|
|
226
|
+
throw new Error(`name must be the canonical handle "${canonical}" (got "${p.name}") — normalizeHandle() it and derive \`handle\` from the same value`);
|
|
227
|
+
}
|
|
197
228
|
const nameBytes = new TextEncoder().encode(p.name);
|
|
198
229
|
if (nameBytes.length < 1 || nameBytes.length > 32) {
|
|
199
230
|
throw new Error("name must be 1..=32 bytes (UTF-8)");
|
package/dist/subname.js
CHANGED
|
@@ -173,6 +173,12 @@ export function buildCreateSubnameIx(p) {
|
|
|
173
173
|
if (labelBytes.length === 0 || labelBytes.length > SUBNAME_LABEL_MAX_LEN) {
|
|
174
174
|
throw new Error(`label must be 1..=${SUBNAME_LABEL_MAX_LEN} bytes`);
|
|
175
175
|
}
|
|
176
|
+
// W171: a subname label follows handle-label grammar — a label like
|
|
177
|
+
// "Bad Label" (space, uppercase) otherwise builds and is only rejected on
|
|
178
|
+
// chain. Lowercase a-z / 0-9 with interior single hyphens only.
|
|
179
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(p.label) || p.label.includes("--")) {
|
|
180
|
+
throw new Error("label must be lowercase a-z, 0-9 and interior single hyphens only (no spaces, uppercase, or leading/trailing/double hyphen)");
|
|
181
|
+
}
|
|
176
182
|
const encLabel = encVec(labelBytes);
|
|
177
183
|
const data = new Uint8Array(8 + encLabel.length);
|
|
178
184
|
data.set(CREATE_SUBNAME_DISCRIMINATOR, 0);
|
package/dist/textRecords.js
CHANGED
|
@@ -297,6 +297,13 @@ function checkKey(key) {
|
|
|
297
297
|
if (bytes.length === 0 || bytes.length > TEXT_KEY_MAX_LEN) {
|
|
298
298
|
throw new Error(`key must be 1..=${TEXT_KEY_MAX_LEN} bytes of UTF-8`);
|
|
299
299
|
}
|
|
300
|
+
// W171: reject keys with spaces / control chars / non-ASCII — a key like
|
|
301
|
+
// "BAD KEY!" otherwise builds and only fails loosely downstream. Record keys
|
|
302
|
+
// are dotted identifiers (e.g. "agent.manifest", "com.twitter"): visible
|
|
303
|
+
// ASCII, no whitespace.
|
|
304
|
+
if (!/^[\x21-\x7e]+$/.test(key)) {
|
|
305
|
+
throw new Error('key must be visible ASCII with no spaces (e.g. "agent.manifest")');
|
|
306
|
+
}
|
|
300
307
|
return bytes;
|
|
301
308
|
}
|
|
302
309
|
function checkValue(value) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `transfer` — hand an UNTOKENIZED handle to a new owner (a direct gift /
|
|
3
|
+
* reassignment of `handle.owner`). This is the plain-handle counterpart to a
|
|
4
|
+
* tokenized handle's pNFT transfer: a tokenized handle's ownership IS its NFT,
|
|
5
|
+
* so the program REFUSES `transfer` on one (`HandleIsTokenized`) — move the
|
|
6
|
+
* pNFT instead (see `pnftTransfer.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Signed by the handle's CURRENT owner (`has_one = owner` on-chain). The
|
|
9
|
+
* program also refuses the move if the handle is locked (`require_unlocked`)
|
|
10
|
+
* or a live auction on it still has bids. It is NOT gated by the registry
|
|
11
|
+
* pause, so a treasury gift can be made even while registration is paused.
|
|
12
|
+
*
|
|
13
|
+
* Hand-rolled like the rest of this package — no Anchor client, no web3.js.
|
|
14
|
+
* PDAs are NOT derived here (see delegate.ts's module docs); derive
|
|
15
|
+
* `["handle", name]` and `["auction", handle]` with your runtime's canonical
|
|
16
|
+
* `findProgramAddress` and pass them in.
|
|
17
|
+
*/
|
|
18
|
+
import type { AddressLike, BuiltInstruction } from "./delegate.js";
|
|
19
|
+
/** Anchor instruction discriminator: `sha256("global:transfer")[0..8]`. */
|
|
20
|
+
export declare const TRANSFER_DISCRIMINATOR: Uint8Array;
|
|
21
|
+
export interface TransferParams {
|
|
22
|
+
/** The registry program id. */
|
|
23
|
+
readonly programId: AddressLike;
|
|
24
|
+
/** The handle's CURRENT owner — must equal `handle.owner` on-chain. Signer. */
|
|
25
|
+
readonly owner: AddressLike;
|
|
26
|
+
/** The `["handle", name]` (or scoped `["handle", label, name]`) PDA. Must be
|
|
27
|
+
* UNTOKENIZED; a tokenized handle is moved by transferring its pNFT. */
|
|
28
|
+
readonly handle: AddressLike;
|
|
29
|
+
/** The `["auction", handle]` PDA — read-only; the program reads it only to
|
|
30
|
+
* refuse a transfer while a live auction still has bids. Pass the derived
|
|
31
|
+
* address even if no auction account exists there. */
|
|
32
|
+
readonly auction: AddressLike;
|
|
33
|
+
/** The address to hand the handle to. */
|
|
34
|
+
readonly newOwner: AddressLike;
|
|
35
|
+
/** Scoped (under-TLD) handles only: the account(s) `verify_handle_pda` needs
|
|
36
|
+
* to re-derive `["handle", label, name]`. Omit for a bare `@handle`. */
|
|
37
|
+
readonly remainingAccounts?: readonly AddressLike[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build `transfer(new_owner)`. The data is the 8-byte discriminator followed by
|
|
41
|
+
* the 32-byte new owner; the accounts are `owner` (signer), `handle` (writable)
|
|
42
|
+
* and the read-only `["auction", handle]` PDA, plus any scoped-TLD
|
|
43
|
+
* remaining-accounts. Returns an UNSIGNED instruction — sign with `owner`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildTransferIx(p: TransferParams): BuiltInstruction;
|
package/dist/transfer.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `transfer` — hand an UNTOKENIZED handle to a new owner (a direct gift /
|
|
3
|
+
* reassignment of `handle.owner`). This is the plain-handle counterpart to a
|
|
4
|
+
* tokenized handle's pNFT transfer: a tokenized handle's ownership IS its NFT,
|
|
5
|
+
* so the program REFUSES `transfer` on one (`HandleIsTokenized`) — move the
|
|
6
|
+
* pNFT instead (see `pnftTransfer.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Signed by the handle's CURRENT owner (`has_one = owner` on-chain). The
|
|
9
|
+
* program also refuses the move if the handle is locked (`require_unlocked`)
|
|
10
|
+
* or a live auction on it still has bids. It is NOT gated by the registry
|
|
11
|
+
* pause, so a treasury gift can be made even while registration is paused.
|
|
12
|
+
*
|
|
13
|
+
* Hand-rolled like the rest of this package — no Anchor client, no web3.js.
|
|
14
|
+
* PDAs are NOT derived here (see delegate.ts's module docs); derive
|
|
15
|
+
* `["handle", name]` and `["auction", handle]` with your runtime's canonical
|
|
16
|
+
* `findProgramAddress` and pass them in.
|
|
17
|
+
*/
|
|
18
|
+
import { encodeBase58, decodeBase58_32 } from "./base58.js";
|
|
19
|
+
/** Anchor instruction discriminator: `sha256("global:transfer")[0..8]`. */
|
|
20
|
+
export const TRANSFER_DISCRIMINATOR = Uint8Array.from([
|
|
21
|
+
0xa3, 0x34, 0xc8, 0xe7, 0x8c, 0x03, 0x45, 0xba,
|
|
22
|
+
]);
|
|
23
|
+
function toBytes32(v, what) {
|
|
24
|
+
if (typeof v === "string") {
|
|
25
|
+
const b = decodeBase58_32(v);
|
|
26
|
+
if (!b)
|
|
27
|
+
throw new Error(`${what} is not a valid base58 address`);
|
|
28
|
+
return b;
|
|
29
|
+
}
|
|
30
|
+
if (v.length !== 32)
|
|
31
|
+
throw new Error(`${what} must be exactly 32 bytes`);
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
function toBase58(v, what) {
|
|
35
|
+
return encodeBase58(toBytes32(v, what));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build `transfer(new_owner)`. The data is the 8-byte discriminator followed by
|
|
39
|
+
* the 32-byte new owner; the accounts are `owner` (signer), `handle` (writable)
|
|
40
|
+
* and the read-only `["auction", handle]` PDA, plus any scoped-TLD
|
|
41
|
+
* remaining-accounts. Returns an UNSIGNED instruction — sign with `owner`.
|
|
42
|
+
*/
|
|
43
|
+
export function buildTransferIx(p) {
|
|
44
|
+
const data = new Uint8Array(8 + 32);
|
|
45
|
+
data.set(TRANSFER_DISCRIMINATOR, 0);
|
|
46
|
+
data.set(toBytes32(p.newOwner, "newOwner"), 8);
|
|
47
|
+
const keys = [
|
|
48
|
+
{ pubkey: toBase58(p.owner, "owner"), isSigner: true, isWritable: false },
|
|
49
|
+
{ pubkey: toBase58(p.handle, "handle"), isSigner: false, isWritable: true },
|
|
50
|
+
{ pubkey: toBase58(p.auction, "auction"), isSigner: false, isWritable: false },
|
|
51
|
+
];
|
|
52
|
+
for (const a of p.remainingAccounts ?? []) {
|
|
53
|
+
keys.push({ pubkey: toBase58(a, "remainingAccount"), isSigner: false, isWritable: false });
|
|
54
|
+
}
|
|
55
|
+
return { programId: toBase58(p.programId, "programId"), keys, data };
|
|
56
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface Resolved {
|
|
|
94
94
|
export type Chain = "X1" | "SOL" | "ETH" | "BTC";
|
|
95
95
|
export declare const CHAIN_COIN_TYPE: Readonly<Record<Chain, number>>;
|
|
96
96
|
export type ResolveErrorCode = "unrecognized" | "ambiguous" | "invalid-handle" | "invalid-domain" | "not-found" | "no-record-for-chain" | "rpc-error"
|
|
97
|
+
/** A caller passed a malformed value to an SDK primitive (e.g. a gate that
|
|
98
|
+
* isn't "any-verified"/{kind}/{anyOf}) — a programming error, surfaced as a
|
|
99
|
+
* typed ResolveError rather than a raw TypeError. */
|
|
100
|
+
| "invalid-input"
|
|
97
101
|
/**
|
|
98
102
|
* A reachable namespace exists for this name, but the adapter that would
|
|
99
103
|
* resolve it is not configured on this deployment (e.g. no `ETH_RPC_URL` for
|
package/dist/x402.js
CHANGED
|
@@ -60,6 +60,14 @@ export const X402_VERSION = 2;
|
|
|
60
60
|
* and let the buyer fetch their own — the spec allows either).
|
|
61
61
|
*/
|
|
62
62
|
export function buildX402Requirements(p) {
|
|
63
|
+
// W171: payTo / asset / buyer are REQUIRED base58 pubkeys — a missing one
|
|
64
|
+
// otherwise sails through (payTo: undefined) straight into the requirements.
|
|
65
|
+
const PUBKEY_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
66
|
+
for (const [field, value] of [["payTo", p.payTo], ["asset", p.asset], ["buyer", p.buyer]]) {
|
|
67
|
+
if (!PUBKEY_RE.test((value ?? "").trim())) {
|
|
68
|
+
throw new Error(`${field} must be a base58 address (32-44 chars); got ${value === undefined ? "undefined" : `"${value}"`}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
63
71
|
if (!/^\d+$/.test(p.amount)) {
|
|
64
72
|
throw new Error("amount must be a decimal string of smallest units (e.g. \"1000000\")");
|
|
65
73
|
}
|