@sorandomains/owner 0.10.1 → 0.12.0
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/README.md +68 -3
- package/dist/funding-history.js +3 -3
- package/dist/funding-reader.js +4 -0
- package/dist/funding-service.js +14 -2
- package/dist/funding-types.d.ts +2 -0
- package/dist/funding-types.js +30 -0
- package/dist/index.d.ts +82 -2
- package/dist/index.js +131 -18
- package/dist/multichain.d.ts +99 -0
- package/dist/multichain.js +204 -0
- package/dist/names.d.ts +29 -0
- package/dist/names.js +83 -0
- package/dist/native-code-policy.d.ts +7 -0
- package/dist/native-code-policy.js +7 -6
- package/dist/native-codec.js +1 -1
- package/dist/native-transport.d.ts +2 -0
- package/dist/native-transport.js +18 -1
- package/dist/native-upgrades.d.ts +48 -0
- package/dist/native-upgrades.js +93 -0
- package/dist/subnames-client.d.ts +49 -0
- package/dist/subnames-client.js +63 -0
- package/package.json +11 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export declare const CHAIN_NETWORKS: readonly Readonly<{
|
|
2
|
+
readonly id: "bitcoin";
|
|
3
|
+
readonly name: "Bitcoin";
|
|
4
|
+
readonly coinType: 0;
|
|
5
|
+
readonly encoding: "bitcoin-script";
|
|
6
|
+
readonly caip2: "bip122:000000000019d6689c085ae165831e93";
|
|
7
|
+
} | {
|
|
8
|
+
readonly id: "ethereum";
|
|
9
|
+
readonly name: "Ethereum";
|
|
10
|
+
readonly coinType: 60;
|
|
11
|
+
readonly encoding: "evm";
|
|
12
|
+
readonly caip2: "eip155:1";
|
|
13
|
+
} | {
|
|
14
|
+
readonly id: "solana";
|
|
15
|
+
readonly name: "Solana";
|
|
16
|
+
readonly coinType: 501;
|
|
17
|
+
readonly encoding: "solana";
|
|
18
|
+
readonly caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
19
|
+
} | {
|
|
20
|
+
readonly id: "optimism";
|
|
21
|
+
readonly name: "Optimism";
|
|
22
|
+
readonly coinType: 2147483658;
|
|
23
|
+
readonly encoding: "evm";
|
|
24
|
+
readonly caip2: "eip155:10";
|
|
25
|
+
} | {
|
|
26
|
+
readonly id: "polygon";
|
|
27
|
+
readonly name: "Polygon";
|
|
28
|
+
readonly coinType: 2147483785;
|
|
29
|
+
readonly encoding: "evm";
|
|
30
|
+
readonly caip2: "eip155:137";
|
|
31
|
+
} | {
|
|
32
|
+
readonly id: "base";
|
|
33
|
+
readonly name: "Base";
|
|
34
|
+
readonly coinType: 2147492101;
|
|
35
|
+
readonly encoding: "evm";
|
|
36
|
+
readonly caip2: "eip155:8453";
|
|
37
|
+
} | {
|
|
38
|
+
readonly id: "arbitrum";
|
|
39
|
+
readonly name: "Arbitrum";
|
|
40
|
+
readonly coinType: 2147525809;
|
|
41
|
+
readonly encoding: "evm";
|
|
42
|
+
readonly caip2: "eip155:42161";
|
|
43
|
+
} | {
|
|
44
|
+
readonly id: "bsc";
|
|
45
|
+
readonly name: "BNB Smart Chain";
|
|
46
|
+
readonly coinType: 2147483704;
|
|
47
|
+
readonly encoding: "evm";
|
|
48
|
+
readonly caip2: "eip155:56";
|
|
49
|
+
} | {
|
|
50
|
+
readonly id: "avalanche";
|
|
51
|
+
readonly name: "Avalanche C-Chain";
|
|
52
|
+
readonly coinType: 2147526762;
|
|
53
|
+
readonly encoding: "evm";
|
|
54
|
+
readonly caip2: "eip155:43114";
|
|
55
|
+
} | {
|
|
56
|
+
readonly id: "xrp";
|
|
57
|
+
readonly name: "XRP Ledger";
|
|
58
|
+
readonly coinType: 144;
|
|
59
|
+
readonly encoding: "xrp";
|
|
60
|
+
readonly caip2: "xrpl:0";
|
|
61
|
+
} | {
|
|
62
|
+
readonly id: "tron";
|
|
63
|
+
readonly name: "Tron";
|
|
64
|
+
readonly coinType: 195;
|
|
65
|
+
readonly encoding: "tron";
|
|
66
|
+
readonly caip2: "tron:728126428";
|
|
67
|
+
} | {
|
|
68
|
+
readonly id: "arc";
|
|
69
|
+
readonly name: "Arc";
|
|
70
|
+
readonly coinType: 2147488690;
|
|
71
|
+
readonly encoding: "evm";
|
|
72
|
+
readonly caip2: "eip155:5042";
|
|
73
|
+
} | {
|
|
74
|
+
readonly id: "tempo";
|
|
75
|
+
readonly name: "Tempo";
|
|
76
|
+
readonly coinType: 2147487865;
|
|
77
|
+
readonly encoding: "evm";
|
|
78
|
+
readonly caip2: "eip155:4217";
|
|
79
|
+
} | {
|
|
80
|
+
readonly id: "xrpl-evm";
|
|
81
|
+
readonly name: "XRPL EVM";
|
|
82
|
+
readonly coinType: 2148923648;
|
|
83
|
+
readonly encoding: "evm";
|
|
84
|
+
readonly caip2: "eip155:1440000";
|
|
85
|
+
}>[];
|
|
86
|
+
export type ChainNetwork = typeof CHAIN_NETWORKS[number]["id"];
|
|
87
|
+
export type ChainAddressErrorCode = "UNSUPPORTED_NETWORK" | "INVALID_ADDRESS" | "INVALID_POLICY";
|
|
88
|
+
export declare class ChainAddressError extends Error {
|
|
89
|
+
readonly code: ChainAddressErrorCode;
|
|
90
|
+
constructor(message: string, code: ChainAddressErrorCode);
|
|
91
|
+
}
|
|
92
|
+
export declare function chainNetwork(id: string): typeof CHAIN_NETWORKS[number];
|
|
93
|
+
/** Strictly decode the native Vec<u32> policy; never silently discard unknown networks. */
|
|
94
|
+
export declare function chainPolicyFromNative(raw: unknown): ChainNetwork[];
|
|
95
|
+
export declare function chainPolicyToNative(networks: readonly ChainNetwork[]): number[];
|
|
96
|
+
/** Validate and encode an explicitly selected destination network. Never infer a network. */
|
|
97
|
+
export declare function encodeChainAddress(network: ChainNetwork, address: string): Uint8Array;
|
|
98
|
+
/** Strictly decode native binary records to their canonical human-readable form. */
|
|
99
|
+
export declare function decodeChainAddress(network: ChainNetwork, bytes: Uint8Array): string;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Canonical multichain address codecs. Run sdk/multichain/sync.mjs after editing.
|
|
2
|
+
* Binary records follow ENSIP-9/11: EVM raw 20 bytes; Bitcoin scriptPubKey;
|
|
3
|
+
* Solana raw 32-byte public key. Stellar retains its separate payment/memo API.
|
|
4
|
+
* These are destination-network mainnet addresses even on Soran testnet.
|
|
5
|
+
*/
|
|
6
|
+
import { base58, base58xrp, bech32, bech32m, createBase58check } from "@scure/base";
|
|
7
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
8
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
9
|
+
export const CHAIN_NETWORKS = Object.freeze([
|
|
10
|
+
{ id: "bitcoin", name: "Bitcoin", coinType: 0, encoding: "bitcoin-script", caip2: "bip122:000000000019d6689c085ae165831e93" },
|
|
11
|
+
{ id: "ethereum", name: "Ethereum", coinType: 60, encoding: "evm", caip2: "eip155:1" },
|
|
12
|
+
{ id: "solana", name: "Solana", coinType: 501, encoding: "solana", caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" },
|
|
13
|
+
{ id: "optimism", name: "Optimism", coinType: 2147483658, encoding: "evm", caip2: "eip155:10" },
|
|
14
|
+
{ id: "polygon", name: "Polygon", coinType: 2147483785, encoding: "evm", caip2: "eip155:137" },
|
|
15
|
+
{ id: "base", name: "Base", coinType: 2147492101, encoding: "evm", caip2: "eip155:8453" },
|
|
16
|
+
{ id: "arbitrum", name: "Arbitrum", coinType: 2147525809, encoding: "evm", caip2: "eip155:42161" },
|
|
17
|
+
{ id: "bsc", name: "BNB Smart Chain", coinType: 2147483704, encoding: "evm", caip2: "eip155:56" },
|
|
18
|
+
{ id: "avalanche", name: "Avalanche C-Chain", coinType: 2147526762, encoding: "evm", caip2: "eip155:43114" },
|
|
19
|
+
{ id: "xrp", name: "XRP Ledger", coinType: 144, encoding: "xrp", caip2: "xrpl:0" },
|
|
20
|
+
{ id: "tron", name: "Tron", coinType: 195, encoding: "tron", caip2: "tron:728126428" },
|
|
21
|
+
{ id: "arc", name: "Arc", coinType: 2147488690, encoding: "evm", caip2: "eip155:5042" },
|
|
22
|
+
{ id: "tempo", name: "Tempo", coinType: 2147487865, encoding: "evm", caip2: "eip155:4217" },
|
|
23
|
+
{ id: "xrpl-evm", name: "XRPL EVM", coinType: 2148923648, encoding: "evm", caip2: "eip155:1440000" },
|
|
24
|
+
].map(network => Object.freeze(network)));
|
|
25
|
+
export class ChainAddressError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(message, code) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.name = "ChainAddressError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function chainNetwork(id) {
|
|
34
|
+
const network = CHAIN_NETWORKS.find(network => network.id === id);
|
|
35
|
+
if (!network)
|
|
36
|
+
throw new ChainAddressError(`unsupported address network: ${String(id)}`, "UNSUPPORTED_NETWORK");
|
|
37
|
+
return network;
|
|
38
|
+
}
|
|
39
|
+
/** Strictly decode the native Vec<u32> policy; never silently discard unknown networks. */
|
|
40
|
+
export function chainPolicyFromNative(raw) {
|
|
41
|
+
if (!Array.isArray(raw) || raw.length > CHAIN_NETWORKS.length)
|
|
42
|
+
throw new ChainAddressError("invalid chain policy: expected supported coin types", "INVALID_POLICY");
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
return Array.from(raw, coin => {
|
|
45
|
+
const network = CHAIN_NETWORKS.find(network => network.coinType === coin);
|
|
46
|
+
if (typeof coin !== "number" || !Number.isInteger(coin) || !network || seen.has(coin))
|
|
47
|
+
throw new ChainAddressError("invalid chain policy: unknown or duplicate coin type", "INVALID_POLICY");
|
|
48
|
+
seen.add(coin);
|
|
49
|
+
return network.id;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function chainPolicyToNative(networks) {
|
|
53
|
+
if (!Array.isArray(networks) || networks.length > CHAIN_NETWORKS.length)
|
|
54
|
+
throw new ChainAddressError("invalid chain policy", "INVALID_POLICY");
|
|
55
|
+
const coins = Array.from(networks, network => chainNetwork(network).coinType);
|
|
56
|
+
chainPolicyFromNative(coins);
|
|
57
|
+
return coins;
|
|
58
|
+
}
|
|
59
|
+
const btc58 = createBase58check(sha256);
|
|
60
|
+
const hex = (bytes) => Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join("");
|
|
61
|
+
const fromHex = (value) => Uint8Array.from(value.match(/../g), part => Number.parseInt(part, 16));
|
|
62
|
+
const nonzero = (bytes) => bytes.some(byte => byte !== 0);
|
|
63
|
+
const invalid = (message) => { throw new ChainAddressError(message, "INVALID_ADDRESS"); };
|
|
64
|
+
function evmChecksum(bytes) {
|
|
65
|
+
const lower = hex(bytes);
|
|
66
|
+
const digest = hex(keccak_256(new TextEncoder().encode(lower)));
|
|
67
|
+
return "0x" + [...lower].map((char, index) => Number.parseInt(digest[index], 16) >= 8 ? char.toUpperCase() : char).join("");
|
|
68
|
+
}
|
|
69
|
+
function encodeBitcoin(address) {
|
|
70
|
+
if (/^(bc1|tb1|bcrt1)/i.test(address)) {
|
|
71
|
+
// The checksum encoding is tied to witness version: BIP173 v0, BIP350 v1.
|
|
72
|
+
let decoded;
|
|
73
|
+
let variant;
|
|
74
|
+
try {
|
|
75
|
+
decoded = bech32.decode(address);
|
|
76
|
+
variant = 0;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
decoded = bech32m.decode(address);
|
|
80
|
+
variant = 1;
|
|
81
|
+
}
|
|
82
|
+
if (decoded.prefix !== "bc")
|
|
83
|
+
return invalid("Bitcoin address must use mainnet");
|
|
84
|
+
const version = decoded.words[0];
|
|
85
|
+
if (version !== variant)
|
|
86
|
+
return invalid("unsupported Bitcoin witness version or checksum");
|
|
87
|
+
const program = bech32.fromWords(decoded.words.slice(1));
|
|
88
|
+
if ((version === 0 && program.length !== 20 && program.length !== 32) || (version === 1 && program.length !== 32))
|
|
89
|
+
return invalid("invalid Bitcoin witness program length");
|
|
90
|
+
return Uint8Array.from([version === 0 ? 0 : 0x51, program.length, ...program]);
|
|
91
|
+
}
|
|
92
|
+
const decoded = btc58.decode(address);
|
|
93
|
+
if (decoded.length !== 21)
|
|
94
|
+
return invalid("invalid Bitcoin address length");
|
|
95
|
+
if (decoded[0] === 0)
|
|
96
|
+
return Uint8Array.from([0x76, 0xa9, 0x14, ...decoded.slice(1), 0x88, 0xac]);
|
|
97
|
+
if (decoded[0] === 5)
|
|
98
|
+
return Uint8Array.from([0xa9, 0x14, ...decoded.slice(1), 0x87]);
|
|
99
|
+
return invalid("Bitcoin address must use mainnet");
|
|
100
|
+
}
|
|
101
|
+
function decodeBitcoin(bytes) {
|
|
102
|
+
if (bytes.length === 25 && bytes[0] === 0x76 && bytes[1] === 0xa9 && bytes[2] === 0x14 && bytes[23] === 0x88 && bytes[24] === 0xac)
|
|
103
|
+
return btc58.encode(Uint8Array.from([0, ...bytes.slice(3, 23)]));
|
|
104
|
+
if (bytes.length === 23 && bytes[0] === 0xa9 && bytes[1] === 0x14 && bytes[22] === 0x87)
|
|
105
|
+
return btc58.encode(Uint8Array.from([5, ...bytes.slice(2, 22)]));
|
|
106
|
+
if (bytes[0] === 0 && (bytes[1] === 20 || bytes[1] === 32) && bytes.length === bytes[1] + 2)
|
|
107
|
+
return bech32.encode("bc", [0, ...bech32.toWords(bytes.slice(2))]);
|
|
108
|
+
if (bytes[0] === 0x51 && bytes[1] === 32 && bytes.length === 34)
|
|
109
|
+
return bech32m.encode("bc", [1, ...bech32m.toWords(bytes.slice(2))]);
|
|
110
|
+
return invalid("unsupported or malformed Bitcoin scriptPubKey");
|
|
111
|
+
}
|
|
112
|
+
/** XRP keeps the version prefix and full X-address tag payload. A classic
|
|
113
|
+
* address alone cannot represent an exchange's required destination tag. */
|
|
114
|
+
function validateXrp(bytes) {
|
|
115
|
+
if (bytes.length === 21 && bytes[0] === 0 && nonzero(bytes.slice(1)))
|
|
116
|
+
return;
|
|
117
|
+
if (bytes.length === 31 && bytes[0] === 5 && bytes[1] === 0x44 && nonzero(bytes.slice(2, 22)) &&
|
|
118
|
+
(bytes[22] === 0 || bytes[22] === 1) && !nonzero(bytes.slice(27)) &&
|
|
119
|
+
(bytes[22] === 1 || !nonzero(bytes.slice(23, 27))))
|
|
120
|
+
return;
|
|
121
|
+
return invalid("invalid XRP classic or mainnet X-address payload");
|
|
122
|
+
}
|
|
123
|
+
function encodeXrp(bytes) {
|
|
124
|
+
validateXrp(bytes);
|
|
125
|
+
return base58xrp.encode(Uint8Array.from([...bytes, ...sha256(sha256(bytes)).slice(0, 4)]));
|
|
126
|
+
}
|
|
127
|
+
function decodeXrp(address) {
|
|
128
|
+
const decoded = base58xrp.decode(address);
|
|
129
|
+
if (decoded.length !== 25 && decoded.length !== 35)
|
|
130
|
+
return invalid("invalid XRP address length");
|
|
131
|
+
const bytes = decoded.slice(0, -4);
|
|
132
|
+
const checksum = sha256(sha256(bytes)).slice(0, 4);
|
|
133
|
+
if (!checksum.every((byte, index) => byte === decoded[bytes.length + index]))
|
|
134
|
+
return invalid("invalid XRP address checksum");
|
|
135
|
+
validateXrp(bytes);
|
|
136
|
+
if (encodeXrp(bytes) !== address)
|
|
137
|
+
return invalid("noncanonical XRP address");
|
|
138
|
+
return bytes;
|
|
139
|
+
}
|
|
140
|
+
function validateTron(bytes) {
|
|
141
|
+
if (bytes.length !== 21 || bytes[0] !== 0x41 || !nonzero(bytes.slice(1)))
|
|
142
|
+
return invalid("invalid Tron address payload");
|
|
143
|
+
}
|
|
144
|
+
/** Validate and encode an explicitly selected destination network. Never infer a network. */
|
|
145
|
+
export function encodeChainAddress(network, address) {
|
|
146
|
+
const metadata = chainNetwork(network);
|
|
147
|
+
if (typeof address !== "string" || address.length === 0 || address.length > 128 || /\s/.test(address))
|
|
148
|
+
return invalid("address must be a nonempty string without whitespace");
|
|
149
|
+
try {
|
|
150
|
+
if (metadata.encoding === "bitcoin-script")
|
|
151
|
+
return encodeBitcoin(address);
|
|
152
|
+
if (metadata.encoding === "xrp")
|
|
153
|
+
return decodeXrp(address);
|
|
154
|
+
if (metadata.encoding === "tron") {
|
|
155
|
+
const bytes = btc58.decode(address);
|
|
156
|
+
validateTron(bytes);
|
|
157
|
+
if (!address.startsWith("T") || btc58.encode(bytes) !== address)
|
|
158
|
+
return invalid("Tron address must use canonical T format");
|
|
159
|
+
return bytes;
|
|
160
|
+
}
|
|
161
|
+
if (metadata.encoding === "solana") {
|
|
162
|
+
const bytes = base58.decode(address);
|
|
163
|
+
if (bytes.length !== 32 || !nonzero(bytes) || base58.encode(bytes) !== address)
|
|
164
|
+
return invalid("invalid Solana public key");
|
|
165
|
+
return bytes;
|
|
166
|
+
}
|
|
167
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
168
|
+
return invalid("EVM address must contain 0x and 40 hexadecimal digits");
|
|
169
|
+
const bytes = fromHex(address.slice(2));
|
|
170
|
+
if (!nonzero(bytes))
|
|
171
|
+
return invalid("zero EVM address is not a payment destination");
|
|
172
|
+
const body = address.slice(2);
|
|
173
|
+
if (body !== body.toLowerCase() && body !== body.toUpperCase() && address !== evmChecksum(bytes))
|
|
174
|
+
return invalid("invalid EIP-55 address checksum");
|
|
175
|
+
return bytes;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof ChainAddressError)
|
|
179
|
+
throw error;
|
|
180
|
+
return invalid(`invalid ${metadata.name} address or checksum`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Strictly decode native binary records to their canonical human-readable form. */
|
|
184
|
+
export function decodeChainAddress(network, bytes) {
|
|
185
|
+
const metadata = chainNetwork(network);
|
|
186
|
+
if (!(bytes instanceof Uint8Array))
|
|
187
|
+
return invalid("chain address must be native bytes");
|
|
188
|
+
if (metadata.encoding === "bitcoin-script")
|
|
189
|
+
return decodeBitcoin(bytes);
|
|
190
|
+
if (metadata.encoding === "xrp")
|
|
191
|
+
return encodeXrp(bytes);
|
|
192
|
+
if (metadata.encoding === "tron") {
|
|
193
|
+
validateTron(bytes);
|
|
194
|
+
return btc58.encode(bytes);
|
|
195
|
+
}
|
|
196
|
+
if (metadata.encoding === "solana") {
|
|
197
|
+
if (bytes.length !== 32 || !nonzero(bytes))
|
|
198
|
+
return invalid("invalid Solana public key bytes");
|
|
199
|
+
return base58.encode(bytes);
|
|
200
|
+
}
|
|
201
|
+
if (bytes.length !== 20 || !nonzero(bytes))
|
|
202
|
+
return invalid("invalid EVM address bytes");
|
|
203
|
+
return evmChecksum(bytes);
|
|
204
|
+
}
|
package/dist/names.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ParsedResolvableName = {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
namespace: string;
|
|
5
|
+
parent: string | null;
|
|
6
|
+
parentLabel: string;
|
|
7
|
+
childLabel: string | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function parseNamespace(input: string): string;
|
|
10
|
+
export declare function parseResolvableName(input: string): ParsedResolvableName;
|
|
11
|
+
export declare function resolvableNameNode(input: string): Uint8Array;
|
|
12
|
+
export type SubnamePolicy = "enabled" | "creation-disabled" | "suspended";
|
|
13
|
+
export declare function subnamePolicyToNative(value: SubnamePolicy): string[];
|
|
14
|
+
export declare function subnamePolicyFromNative(raw: unknown): SubnamePolicy;
|
|
15
|
+
export type SubnameRecord = {
|
|
16
|
+
parentNode: Uint8Array;
|
|
17
|
+
parentGeneration: bigint;
|
|
18
|
+
generation: bigint;
|
|
19
|
+
address: string;
|
|
20
|
+
active: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function exactGeneration(value: unknown): bigint;
|
|
23
|
+
/** Address validity is checked by each caller's Stellar SDK, without coercion. */
|
|
24
|
+
export declare function subnameRecordFromNative(raw: unknown, expectedParentNode: Uint8Array): SubnameRecord | null;
|
|
25
|
+
export declare function subnameLabelsFromNative(raw: unknown, limit: number): string[];
|
|
26
|
+
export declare function subnamePageInput(offset?: number, limit?: number): {
|
|
27
|
+
offset: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
};
|
package/dist/names.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Generated by sdk/names/sync.mjs; edit sdk/names/names.ts.
|
|
2
|
+
/** Canonical resolution grammar. Registration and lifecycle actions still use two labels. */
|
|
3
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
4
|
+
const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
5
|
+
export function parseNamespace(input) {
|
|
6
|
+
if (typeof input !== "string" || !/^[A-Za-z0-9-]+$/.test(input))
|
|
7
|
+
throw new Error("invalid namespace");
|
|
8
|
+
const value = input.toLowerCase();
|
|
9
|
+
if (!LABEL.test(value))
|
|
10
|
+
throw new Error("invalid namespace");
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function parseResolvableName(input) {
|
|
14
|
+
if (typeof input !== "string" || !/^[A-Za-z0-9.-]+$/.test(input) || input.length > 191)
|
|
15
|
+
throw new Error("name must contain two or three ASCII labels");
|
|
16
|
+
const name = input.toLowerCase(), parts = name.split(".");
|
|
17
|
+
if ((parts.length !== 2 && parts.length !== 3) || !parts.every(label => LABEL.test(label)))
|
|
18
|
+
throw new Error("expected label.namespace or child.label.namespace");
|
|
19
|
+
const namespace = parts.at(-1), parentLabel = parts.at(-2);
|
|
20
|
+
return { name, label: parts.slice(0, -1).join("."), namespace, parent: parts.length === 3 ? `${parentLabel}.${namespace}` : null,
|
|
21
|
+
parentLabel, childLabel: parts.length === 3 ? parts[0] : null };
|
|
22
|
+
}
|
|
23
|
+
export function resolvableNameNode(input) {
|
|
24
|
+
const parts = parseResolvableName(input).name.split(".").reverse();
|
|
25
|
+
let node = new Uint8Array(32);
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
const joined = new Uint8Array(64);
|
|
28
|
+
joined.set(node);
|
|
29
|
+
joined.set(sha256(new TextEncoder().encode(part)), 32);
|
|
30
|
+
node = new Uint8Array(sha256(joined));
|
|
31
|
+
}
|
|
32
|
+
return node;
|
|
33
|
+
}
|
|
34
|
+
const POLICIES = { enabled: "Enabled", "creation-disabled": "CreationDisabled", suspended: "Suspended" };
|
|
35
|
+
export function subnamePolicyToNative(value) {
|
|
36
|
+
if (!Object.hasOwn(POLICIES, value))
|
|
37
|
+
throw new Error("invalid subname policy");
|
|
38
|
+
return [POLICIES[value]];
|
|
39
|
+
}
|
|
40
|
+
export function subnamePolicyFromNative(raw) {
|
|
41
|
+
if (!Array.isArray(raw) || raw.length !== 1 || typeof raw[0] !== "string")
|
|
42
|
+
throw new Error("invalid subname policy response");
|
|
43
|
+
const policy = Object.keys(POLICIES).find(key => POLICIES[key] === raw[0]);
|
|
44
|
+
if (!policy)
|
|
45
|
+
throw new Error("unsupported subname policy");
|
|
46
|
+
return policy;
|
|
47
|
+
}
|
|
48
|
+
export function exactGeneration(value) {
|
|
49
|
+
if (typeof value !== "bigint" || value < 0n || value > 18446744073709551615n)
|
|
50
|
+
throw new Error("generation must be an exact u64 bigint");
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
/** Address validity is checked by each caller's Stellar SDK, without coercion. */
|
|
54
|
+
export function subnameRecordFromNative(raw, expectedParentNode) {
|
|
55
|
+
if (raw === null)
|
|
56
|
+
return null;
|
|
57
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).sort().join(",") !== "active,address,generation,parent_generation,parent_node")
|
|
58
|
+
throw new Error("invalid subname record fields");
|
|
59
|
+
const r = raw;
|
|
60
|
+
if (!(r.parent_node instanceof Uint8Array) || r.parent_node.length !== 32 || expectedParentNode.length !== 32 || !r.parent_node.every((byte, i) => byte === expectedParentNode[i]) || typeof r.address !== "string" || typeof r.active !== "boolean")
|
|
61
|
+
throw new Error("invalid subname record response");
|
|
62
|
+
return { parentNode: new Uint8Array(r.parent_node), parentGeneration: exactGeneration(r.parent_generation), generation: exactGeneration(r.generation), address: r.address, active: r.active };
|
|
63
|
+
}
|
|
64
|
+
export function subnameLabelsFromNative(raw, limit) {
|
|
65
|
+
if (!Array.isArray(raw) || raw.length > limit)
|
|
66
|
+
throw new Error("invalid subname labels response");
|
|
67
|
+
const values = raw.map(label => {
|
|
68
|
+
if (!(label instanceof Uint8Array) || label.length < 1 || label.length > 63 || label.some(byte => byte > 127))
|
|
69
|
+
throw new Error("invalid subname label bytes");
|
|
70
|
+
const text = new TextDecoder().decode(label);
|
|
71
|
+
if (!LABEL.test(text))
|
|
72
|
+
throw new Error("invalid subname label");
|
|
73
|
+
return text;
|
|
74
|
+
});
|
|
75
|
+
if (new Set(values).size !== values.length)
|
|
76
|
+
throw new Error("duplicate subname label");
|
|
77
|
+
return values;
|
|
78
|
+
}
|
|
79
|
+
export function subnamePageInput(offset = 0, limit = 16) {
|
|
80
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > 4294967295 || !Number.isInteger(limit) || limit < 1 || limit > 16 || offset + limit > 4294967295)
|
|
81
|
+
throw new Error("subname page requires an unsigned offset and a limit from 1 to 16");
|
|
82
|
+
return { offset, limit };
|
|
83
|
+
}
|
|
@@ -10,5 +10,12 @@ type Context = {
|
|
|
10
10
|
};
|
|
11
11
|
/** Select policy from an exact Registry instance proof. Failed RPC/method reads
|
|
12
12
|
* never downgrade governed verification to the historical template rule. */
|
|
13
|
+
export type ExpectedNativeCode = {
|
|
14
|
+
governed: boolean;
|
|
15
|
+
registrar: string;
|
|
16
|
+
resolver: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function expectedNativeCode(context: Context, namespace: string, minimumLedger?: number): Promise<ExpectedNativeCode>;
|
|
19
|
+
/** Registrar-only consumers retain the same governed/legacy policy selection. */
|
|
13
20
|
export declare function expectedRegistrarCode(context: Context, namespace: string, minimumLedger?: number): Promise<string>;
|
|
14
21
|
export {};
|
|
@@ -6,9 +6,7 @@ function ledger(value, minimum) {
|
|
|
6
6
|
fail('missing or stale ledger context');
|
|
7
7
|
return value;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
* never downgrade governed verification to the historical template rule. */
|
|
11
|
-
export async function expectedRegistrarCode(context, namespace, minimumLedger) {
|
|
9
|
+
export async function expectedNativeCode(context, namespace, minimumLedger) {
|
|
12
10
|
const key = new Contract(context.registryId).getFootprint();
|
|
13
11
|
const proof = await context.server.getLedgerEntries(key);
|
|
14
12
|
const observed = ledger(proof?.latestLedger, minimumLedger ?? 1);
|
|
@@ -38,7 +36,7 @@ export async function expectedRegistrarCode(context, namespace, minimumLedger) {
|
|
|
38
36
|
const templates = await read('template_hashes');
|
|
39
37
|
if (!Array.isArray(templates) || templates.length !== 2)
|
|
40
38
|
fail('malformed historical templates');
|
|
41
|
-
return hex(bytes32(templates[0], 'Registry Registrar template'));
|
|
39
|
+
return { governed: false, registrar: hex(bytes32(templates[0], 'Registry Registrar template')), resolver: hex(bytes32(templates[1], 'Registry Resolver template')) };
|
|
42
40
|
}
|
|
43
41
|
if (authorities[0].val.type !== 'scvAddress')
|
|
44
42
|
fail('malformed upgrade authority');
|
|
@@ -48,6 +46,9 @@ export async function expectedRegistrarCode(context, namespace, minimumLedger) {
|
|
|
48
46
|
const code = exactObject(await read('native_code', [sc.bytes(unhex(namespace))]), ['registrar', 'resolver', 'registrar_updates', 'resolver_updates'], 'native code');
|
|
49
47
|
u32(code.registrar_updates, 'Registrar upgrade count');
|
|
50
48
|
u32(code.resolver_updates, 'Resolver upgrade count');
|
|
51
|
-
bytes32(code.resolver, 'Registry Resolver code');
|
|
52
|
-
|
|
49
|
+
return { governed: true, registrar: hex(bytes32(code.registrar, 'Registry Registrar code')), resolver: hex(bytes32(code.resolver, 'Registry Resolver code')) };
|
|
50
|
+
}
|
|
51
|
+
/** Registrar-only consumers retain the same governed/legacy policy selection. */
|
|
52
|
+
export async function expectedRegistrarCode(context, namespace, minimumLedger) {
|
|
53
|
+
return (await expectedNativeCode(context, namespace, minimumLedger)).registrar;
|
|
53
54
|
}
|
package/dist/native-codec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Local successor-ABI helpers. No deployed defaults are changed by this module. */
|
|
2
2
|
import { Address, StrKey, hash, nativeToScVal, xdr } from "@stellar/stellar-sdk";
|
|
3
3
|
import { decodeMuxedAddress, paymentMemoToScVal, validatePaymentDestination } from "./payment.js";
|
|
4
|
-
export const NATIVE_REGISTRAR_ERRORS = { 21: "InvalidClaimConfig", 22: "ClaimsNotConfigured", 23: "ClaimsPaused", 24: "StaleClaimPolicy", 25: "ClaimIntentMismatch", 26: "ClaimIntentExpired", 27: "ReservedName", 28: "NameNotReserved", 29: "PublicIssuanceRequired", 30: "WalletClaimLimit", 31: "InvalidEligibility", 32: "ApprovalAllowanceReached", 33: "ApprovalRateReached", 34: "RequestIdConflict", 35: "CounterOverflow", 36: "InvalidNativeBinding", 37: "UnsupportedClaimant", 38: "DuplicateLabel", 39: "RenewalTooEarly", 40: "RenewalLeaseLimit", 41: "DestinationInitializationFailed", 42: "FeeSettlementFailed" };
|
|
4
|
+
export const NATIVE_REGISTRAR_ERRORS = { 1: "AlreadyInitialized", 2: "NotInitialized", 3: "NameTaken", 4: "NameNotFound", 5: "NotReclaimable", 6: "NotTransferable", 7: "AlreadyPermanent", 8: "BatchTooLarge", 9: "NotNamespaceOwner", 10: "BadLabel", 11: "PermanentName", 12: "FiniteTermPolicy", 13: "PermanentlyLocked", 14: "NameExpired", 15: "NoPendingTransfer", 16: "TransferExpired", 17: "StaleTransfer", 18: "InvalidPolicy", 19: "ExpiryOverflow", 20: "InvalidRegistry", 21: "InvalidClaimConfig", 22: "ClaimsNotConfigured", 23: "ClaimsPaused", 24: "StaleClaimPolicy", 25: "ClaimIntentMismatch", 26: "ClaimIntentExpired", 27: "ReservedName", 28: "NameNotReserved", 29: "PublicIssuanceRequired", 30: "WalletClaimLimit", 31: "InvalidEligibility", 32: "ApprovalAllowanceReached", 33: "ApprovalRateReached", 34: "RequestIdConflict", 35: "CounterOverflow", 36: "InvalidNativeBinding", 37: "UnsupportedClaimant", 38: "DuplicateLabel", 39: "RenewalTooEarly", 40: "RenewalLeaseLimit", 41: "DestinationInitializationFailed", 42: "FeeSettlementFailed", 43: "MigrationInProgress", 44: "MigrationAlreadyStarted", 45: "MigrationNotActive", 46: "MigrationMismatch", 47: "MigrationDuplicate", 48: "MigrationIncomplete", 49: "MigrationUnavailable", 50: "MigrationUnsupported", 51: "PermanentLockDisabled", 52: "InvalidRenewalGrace", 53: "SubnameCreationDisabled", 54: "SubnamesSuspended", 55: "SubnameGenerationMismatch", 56: "SubnameIndexUnavailable" };
|
|
5
5
|
export class NativeClaimError extends Error {
|
|
6
6
|
kind;
|
|
7
7
|
txHash;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Transaction, rpc, xdr } from "@stellar/stellar-sdk";
|
|
2
2
|
import { type NativeAuthorizationPlan } from "./native-auth.js";
|
|
3
|
+
/** RPC clients sometimes reject with a plain `{ code, message }` object, which String() renders as "[object Object]". */
|
|
4
|
+
export declare function describeRejection(error: unknown): string;
|
|
3
5
|
export type NativeSigner = {
|
|
4
6
|
publicKey(): string | Promise<string>;
|
|
5
7
|
signTransaction(encoded: string, opts: {
|
package/dist/native-transport.js
CHANGED
|
@@ -2,6 +2,23 @@ import { expectedRegistrarCode } from "./native-code-policy.js";
|
|
|
2
2
|
import { Contract, Operation, Transaction, TransactionBuilder, rpc, scValToNative, xdr } from "@stellar/stellar-sdk";
|
|
3
3
|
import { NativeClaimError, address, hex, namespaceNode, sc, unhex } from "./native-codec.js";
|
|
4
4
|
import { assertSignedBodyUnchanged, validateEligibilityAuthorization, validateNativeTransaction } from "./native-auth.js";
|
|
5
|
+
/** RPC clients sometimes reject with a plain `{ code, message }` object, which String() renders as "[object Object]". */
|
|
6
|
+
export function describeRejection(error) {
|
|
7
|
+
if (error instanceof Error || typeof error === "string")
|
|
8
|
+
return String(error);
|
|
9
|
+
if (error && typeof error === "object") {
|
|
10
|
+
const message = error.message;
|
|
11
|
+
if (typeof message === "string" && message)
|
|
12
|
+
return message;
|
|
13
|
+
try {
|
|
14
|
+
const json = JSON.stringify(error);
|
|
15
|
+
if (json && json !== "{}")
|
|
16
|
+
return json.length > 300 ? `${json.slice(0, 300)}...` : json;
|
|
17
|
+
}
|
|
18
|
+
catch { /* circular or unserialisable */ }
|
|
19
|
+
}
|
|
20
|
+
return "unknown error";
|
|
21
|
+
}
|
|
5
22
|
/** This published legacy code has owner-only issuance. Missing RPC data is never legacy detection. */
|
|
6
23
|
const LEGACY_REGISTRAR_HASH = "ed06b3374ff4342b4a2316fc546505132cd8fd35be6666571cf088710af9bbc6";
|
|
7
24
|
/** Historical reads must not trust an upgraded Registrar that can fabricate receipts. Resolver changes do not block this Registrar-only check. */
|
|
@@ -119,7 +136,7 @@ export async function sendNative(context, plan, options = {}) {
|
|
|
119
136
|
catch (error) {
|
|
120
137
|
if (error instanceof NativeClaimError && error.txHash)
|
|
121
138
|
throw error;
|
|
122
|
-
throw new NativeClaimError(`submission/confirmation interrupted: ${
|
|
139
|
+
throw new NativeClaimError(`submission/confirmation interrupted: ${describeRejection(error)}; reconcile the original receipt`, "pending", prepared.hash);
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
/** Bind terminal RPC context before classifying success or failure. An incomplete response remains uncertain. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { xdr } from "@stellar/stellar-sdk";
|
|
2
|
+
import type { NativeContext, NativeWriteOptions } from "./native-transport.js";
|
|
3
|
+
export type ContractUpgradeRole = "registrar" | "resolver";
|
|
4
|
+
export type ContractUpgradeOptions = NativeWriteOptions & {
|
|
5
|
+
/** Explicit acknowledgement that installing a superseded (older) release, or the latest known release over code this SDK cannot place, is intended. A reinstall of the installed hash is never allowed. */
|
|
6
|
+
allowDowngrade?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ContractUpgradeStatus = {
|
|
9
|
+
owner: string;
|
|
10
|
+
contractId: string;
|
|
11
|
+
currentHash: string;
|
|
12
|
+
approved: boolean;
|
|
13
|
+
governed: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Reviewed implementation lineage of the governed testnet Registry. Governance
|
|
17
|
+
* approval means "this hash may be installed on any namespace", not "this is a
|
|
18
|
+
* safe transition": superseded releases stay approved, so the chain accepts a
|
|
19
|
+
* downgrade. `latest` is the release owners are offered; `superseded` are the
|
|
20
|
+
* older releases it replaces. A test keeps this equal to the web catalog
|
|
21
|
+
* (web/lib/contractUpdates.ts) and the release record; adding a release means
|
|
22
|
+
* moving the old `latest` into `superseded` here and there.
|
|
23
|
+
*/
|
|
24
|
+
export declare const CONTRACT_RELEASES: {
|
|
25
|
+
readonly registrar: {
|
|
26
|
+
readonly latest: "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1";
|
|
27
|
+
readonly superseded: readonly ["42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420", "5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c", "61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5", "26e03cd1d7fbfa46fe442406fb1b88022ec8cd8bd9123bed570f971fce596a14"];
|
|
28
|
+
};
|
|
29
|
+
readonly resolver: {
|
|
30
|
+
readonly latest: "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806";
|
|
31
|
+
readonly superseded: readonly ["cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48", "351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea", "c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71", "5e7b4fb8d310d67ed6966a155e748d60be771422b3e8c34bbc14485f98525574"];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* How installing `targetHash` over `currentHash` relates to the reviewed lineage:
|
|
36
|
+
* - "same": already installed (never installable again);
|
|
37
|
+
* - "backward": the target is a superseded release, so this is a downgrade;
|
|
38
|
+
* - "forward": the latest known release over a known superseded one;
|
|
39
|
+
* - "unrecognized-current": the latest known release over code this SDK cannot
|
|
40
|
+
* place, which may be a newer release, so installing it could be a downgrade;
|
|
41
|
+
* - "unreviewed-target": a release this SDK has no lineage for (newer than the SDK
|
|
42
|
+
* or custom); direction cannot be judged and the caller's review is trusted.
|
|
43
|
+
*/
|
|
44
|
+
export type ContractUpgradeDirection = "forward" | "same" | "backward" | "unrecognized-current" | "unreviewed-target";
|
|
45
|
+
export declare function contractUpgradeDirection(role: ContractUpgradeRole, currentHash: string, targetHash: string): ContractUpgradeDirection;
|
|
46
|
+
export declare function upgradeHash(hash: string): xdr.ScVal;
|
|
47
|
+
/** Independent of claim versions, claim configuration and fee-token policy. */
|
|
48
|
+
export declare function contractUpgradeStatus(context: NativeContext, namespace: string, role: ContractUpgradeRole, wasmHash: string): Promise<ContractUpgradeStatus>;
|