@sorandomains/owner 0.11.0 → 0.12.1
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 +50 -4
- package/dist/funding-history.js +3 -3
- package/dist/funding-reader.d.ts +1 -1
- 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 +84 -4
- package/dist/index.js +106 -3
- 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-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 +36 -1
- package/dist/native-upgrades.js +39 -0
- package/dist/subnames-client.d.ts +49 -0
- package/dist/subnames-client.js +63 -0
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
> [release status](https://docs.soran.domains/reference/release-status) for package and service availability.
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
Version 0.
|
|
8
|
+
Version 0.12.1 targets Stellar SDK17 (`>=17 <18`). ASCII names
|
|
9
9
|
and labels are validated before lowercase normalization; Unicode lookalikes are
|
|
10
10
|
rejected. Writes continue to target the owning Registry/Registrar/Resolver. Universal
|
|
11
|
-
Lookup is the read entry point in `@sorandomains/lookup` 0.10.0.
|
|
11
|
+
Lookup is the read entry point in `@sorandomains/lookup` 0.10.0 or later.
|
|
12
|
+
|
|
13
|
+
Version 0.12.1 rejects simulated transactions whose source, operation, contract, method, arguments or authorization differ from the requested legacy account-signed write. Unexpected authorizers and nested calls are rejected before signing.
|
|
14
|
+
|
|
15
|
+
Releases older than 0.11.0 (and any built against Lookup 0.6.0 to 0.9.0) default to the retired 5 and 6 September 2026 testnet stacks; upgrade rather than pinning a retired Registry. See [Older versions and retired stacks](../../README.md#older-versions-and-retired-stacks).
|
|
12
16
|
|
|
13
17
|
Ownership, issuer, holder and treasury inputs remain G/C account or contract
|
|
14
18
|
addresses. Muxed M addresses are payment destinations only: issue the name to its
|
|
@@ -36,6 +40,30 @@ const batch = await owner.issueBatch("acme", rows);
|
|
|
36
40
|
for (const o of batch.outcomes) if (!o.issued) console.warn(o.label, o.reason);
|
|
37
41
|
```
|
|
38
42
|
|
|
43
|
+
## Network address policy (unreleased native extension)
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
await owner.setChainPolicy("solo", ["bitcoin", "ethereum", "base", "xrp"]);
|
|
47
|
+
const enabled = await owner.chainPolicy("solo");
|
|
48
|
+
await owner.setChainPolicy("solo", []); // disable all generic network records
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The current namespace owner sets one live policy for every existing and future
|
|
52
|
+
holder. The default policy is empty. Disabled records are hidden and cannot be
|
|
53
|
+
updated, but remain stored for re-enablement or removal by their holder. Duplicate
|
|
54
|
+
or unknown IDs are rejected. Each write freshly verifies the native route/version
|
|
55
|
+
and current namespace owner before signing the exact policy transaction. The owner
|
|
56
|
+
chooses available networks; holders choose their own destinations.
|
|
57
|
+
|
|
58
|
+
All methods require `multichain_version() == 1`; the existing published deployment
|
|
59
|
+
presets are not upgraded by installing this SDK. The source change does not deploy
|
|
60
|
+
contracts or publish packages. See [network formats and integration rules](../multichain/README.md)
|
|
61
|
+
for all 14 supported mainnets, binary formats, and shared codec exports. XRP Ledger
|
|
62
|
+
`xrp` preserves full mainnet X-addresses and their destination tags; `xrpl-evm` is a
|
|
63
|
+
separate EVM network. Never strip an X-address tag. For Stellar use the dedicated
|
|
64
|
+
payment API.
|
|
65
|
+
|
|
66
|
+
|
|
39
67
|
## What's in the box
|
|
40
68
|
|
|
41
69
|
| Operation | What it does |
|
|
@@ -91,7 +119,7 @@ Read the [native claim APIs, security boundaries and complete signup flow](https
|
|
|
91
119
|
|
|
92
120
|
## Verified testnet deployment
|
|
93
121
|
|
|
94
|
-
See the [deployment manifest](../../
|
|
122
|
+
See the [deployment manifest](../../deployments/testnet.json) for confirmed code hashes, transaction receipts and verification scope. Network passphrase: `Test SDF Network ; September 2015`.
|
|
95
123
|
|
|
96
124
|
| Contract | Address |
|
|
97
125
|
|---|---|
|
|
@@ -129,7 +157,8 @@ The current namespace owner can update each governed child contract separately:
|
|
|
129
157
|
```ts
|
|
130
158
|
const status = await client.contractUpgradeStatus("acme", "resolver", approvedResolverHash);
|
|
131
159
|
// status: { owner, contractId, currentHash, approved, governed }
|
|
132
|
-
|
|
160
|
+
const direction = contractUpgradeDirection("resolver", status.currentHash, approvedResolverHash);
|
|
161
|
+
if (status.approved && status.governed && (direction === "forward" || direction === "unreviewed-target")) {
|
|
133
162
|
await client.upgradeResolver("acme", approvedResolverHash, {
|
|
134
163
|
onPrepared: ({ hash, feeStroops }) => savePendingUpdate({ hash, feeStroops }),
|
|
135
164
|
});
|
|
@@ -137,8 +166,25 @@ if (status.approved && status.governed && status.currentHash !== approvedResolve
|
|
|
137
166
|
await client.upgradeRegistrar("acme", approvedRegistrarHash);
|
|
138
167
|
```
|
|
139
168
|
|
|
169
|
+
Registry approval says a hash may be installed on any namespace; it does not say the move is safe. Governance keeps superseded releases approved, so the chain itself accepts a downgrade (and a reinstall of the installed hash). The SDK therefore refuses, before preparing or signing anything: a reinstall of the installed hash, always; a **superseded** release (`CONTRACT_RELEASES[role].superseded`, a downgrade that drops later fixes); and the latest known release over installed code the SDK cannot place (it may be newer). `contractUpgradeDirection(role, currentHash, targetHash)` returns `"forward"`, `"same"`, `"backward"`, `"unrecognized-current"` or `"unreviewed-target"` (a release newer than this SDK, whose direction it cannot judge: only a reviewed hash from a trusted release record should reach it). A deliberate rollback passes `{ allowDowngrade: true }` to `upgradeRegistrar`/`upgradeResolver` after reviewing the older release for that namespace; it never overrides a reinstall. Keep an application-side allow-list of reviewed `from -> to` pairs, as the Soran console does; `CONTRACT_RELEASES` is a tested mirror of that catalog and lags any release newer than the SDK.
|
|
170
|
+
|
|
140
171
|
Use exact lowercase 32-byte hex hashes from a reviewed release. Registry governance must approve the implementation for its Registrar or Resolver role first. Approval permits an update; it does not upgrade existing namespace contracts. Each owner-signed transaction changes code at the existing contract address, executes immediately, and pays its own simulated network fee. Storage compatibility is a property of the selected release, not a guarantee for every approved implementation.
|
|
141
172
|
|
|
142
173
|
`contractUpgradeStatus` is read-only and checks both actual executables against the Registry's per-namespace code pins, clean attestations, Registrar anchors, and Resolver provenance/authority. It does not depend on claim configuration, claim-interface version or fee-token settings. A verified unsupported policy or revoked target is reported through `governed`/`approved`; missing or inconsistent evidence throws. This review is a snapshot: the write rechecks authorization and simulation before signing, and the contracts enforce current approval and ownership on chain.
|
|
143
174
|
|
|
144
175
|
Both update methods require the current owner's account as the transaction source and use the native transaction's exact operation, authorization-tree and fee checks. They never fall back to the legacy tainting upgrade path or restore archived storage automatically. Active migrations and frozen exports must be resolved before updating. If submission is interrupted, reconcile the error's original `txHash` before retrying. Signature quorum is validated by the network; contract-wallet owner authorization is not supported by these account-source helpers.
|
|
176
|
+
|
|
177
|
+
### Namespace-wide child-name policy
|
|
178
|
+
|
|
179
|
+
On a Registrar with `subname_version() == 1`, the current namespace owner can
|
|
180
|
+
call `setSubnamePolicy(namespace, policy)` with `enabled`, `creation-disabled`
|
|
181
|
+
or `suspended`. The default is `creation-disabled`. Changes apply to all parent
|
|
182
|
+
holders, including usernames claimed before the change. `creation-disabled`
|
|
183
|
+
blocks new children while existing ones still work; `suspended` blocks child
|
|
184
|
+
resolution and record edits, preserves records, and permits their removal.
|
|
185
|
+
|
|
186
|
+
`subnamePolicy`, `subnameRecord` and `subnames(parent, { offset, limit })` provide
|
|
187
|
+
strict on-chain reads. Raw child records and pages include inactive/stale rows;
|
|
188
|
+
they do not establish payment readiness. The parent holder, not a separate
|
|
189
|
+
child wallet, controls `mail.fred.solo`, and its expiry and ownership generation
|
|
190
|
+
are tied to `fred.solo`. Child destinations may differ from the parent holder.
|
package/dist/funding-history.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Address, FeeBumpTransaction, Networks, Transaction, TransactionBuilder, xdr, hash, } from "@stellar/stellar-sdk";
|
|
2
2
|
import { hex } from "./native-codec.js";
|
|
3
|
-
import { FundingError, sponsorQuoteToScVal, } from "./funding-types.js";
|
|
3
|
+
import { FundingError, readBoundedText, sponsorQuoteToScVal, } from "./funding-types.js";
|
|
4
4
|
/** Input must come from the caller's trusted chain provider, never a pasted/API receipt. */
|
|
5
5
|
export function verifySponsorHistory(value, quote, expectedHash, latestLedger, passphrase, provider = "sdf-testnet-horizon") {
|
|
6
6
|
const r = value;
|
|
@@ -116,8 +116,8 @@ export class TestnetSponsorHistory {
|
|
|
116
116
|
return null;
|
|
117
117
|
if (!response.ok)
|
|
118
118
|
throw new FundingError("Sponsorship history unavailable; preserve the quote reference", "unavailable");
|
|
119
|
-
const body = await response
|
|
120
|
-
if (body
|
|
119
|
+
const body = await readBoundedText(response, 600000);
|
|
120
|
+
if (body === null)
|
|
121
121
|
throw new FundingError("Sponsorship history response is too large", "unavailable");
|
|
122
122
|
return verifySponsorHistory(JSON.parse(body), quote, hash, latestLedger, passphrase);
|
|
123
123
|
}
|
package/dist/funding-reader.d.ts
CHANGED
|
@@ -23,7 +23,6 @@ export declare class FundingReader {
|
|
|
23
23
|
ledger: number;
|
|
24
24
|
}>;
|
|
25
25
|
configuration(): Promise<{
|
|
26
|
-
ledger: number;
|
|
27
26
|
registry: string;
|
|
28
27
|
nativeAsset: string;
|
|
29
28
|
lookup: string;
|
|
@@ -31,6 +30,7 @@ export declare class FundingReader {
|
|
|
31
30
|
governance: string;
|
|
32
31
|
paused: boolean;
|
|
33
32
|
revision: bigint;
|
|
33
|
+
ledger: number;
|
|
34
34
|
}>;
|
|
35
35
|
currentPosition(namespace: string): Promise<{
|
|
36
36
|
id: string;
|
package/dist/funding-reader.js
CHANGED
|
@@ -32,6 +32,10 @@ export class FundingReader {
|
|
|
32
32
|
.setTimeout(30)
|
|
33
33
|
.build();
|
|
34
34
|
const result = await this.server.simulateTransaction(tx, undefined, undefined, false);
|
|
35
|
+
// A simulation that needs a restore preamble still reports success, with the
|
|
36
|
+
// value computed as if the archived entry were live; it is not a live read.
|
|
37
|
+
if (rpc.Api.isSimulationRestore(result))
|
|
38
|
+
throw new FundingError("Cannot verify funding contract state: an archived entry needs restoring", "unavailable");
|
|
35
39
|
if (!rpc.Api.isSimulationSuccess(result) ||
|
|
36
40
|
!result.result ||
|
|
37
41
|
!Number.isSafeInteger(result.latestLedger) ||
|
package/dist/funding-service.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TestnetSponsorHistory, sponsorRpcReceipt, } from "./funding-history.js";
|
|
2
2
|
import { claimIntentToScVal } from "./native-types.js";
|
|
3
|
-
import { FundingError, decodeSponsorQuote, sponsoredIdentityToScVal, sponsorIntentHash, } from "./funding-types.js";
|
|
3
|
+
import { FundingError, decodeSponsorQuote, readBoundedText, sponsoredIdentityToScVal, sponsorIntentHash, } from "./funding-types.js";
|
|
4
|
+
/** An offer carries at most a 100000 character transaction plus the quote XDR; nothing legitimate approaches this. */
|
|
5
|
+
const MAX_SERVICE_BODY_BYTES = 262_144;
|
|
4
6
|
/** HTTP transports offers and status. The contract/RPC checks still authorize spending. */
|
|
5
7
|
export class FundingServiceClient {
|
|
6
8
|
client;
|
|
@@ -39,8 +41,18 @@ export class FundingServiceClient {
|
|
|
39
41
|
throw new FundingError("Service response unavailable. Keep your quote reference and recover its status before submitting again.", "unavailable");
|
|
40
42
|
}
|
|
41
43
|
let value;
|
|
44
|
+
let text;
|
|
42
45
|
try {
|
|
43
|
-
|
|
46
|
+
text = await readBoundedText(r, MAX_SERVICE_BODY_BYTES);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
text = "";
|
|
50
|
+
}
|
|
51
|
+
// Too large is handled like an unreadable body: a submit's outcome is unknown.
|
|
52
|
+
if (text === null)
|
|
53
|
+
throw new FundingError("Service response was too large. Keep your quote reference and recover its status before submitting again.", path === "/v1/sponsorship/submit" ? "pending" : "unavailable");
|
|
54
|
+
try {
|
|
55
|
+
value = JSON.parse(text);
|
|
44
56
|
}
|
|
45
57
|
catch {
|
|
46
58
|
// A proxy/gateway error page (e.g. a 502 HTML body) is not JSON. A
|
package/dist/funding-types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { xdr } from "@stellar/stellar-sdk";
|
|
2
2
|
import { type PaymentDestination } from "./payment.js";
|
|
3
3
|
import { type ClaimIntent } from "./native-types.js";
|
|
4
|
+
/** Read a response body as text, refusing more than `limit` bytes, declared or streamed, so a hostile or broken endpoint cannot make the client buffer it all. Returns null over the limit. */
|
|
5
|
+
export declare function readBoundedText(response: Response, limit: number): Promise<string | null>;
|
|
4
6
|
export declare class FundingError extends Error {
|
|
5
7
|
readonly kind: "invalid" | "unavailable" | "authorization" | "pending" | "failed";
|
|
6
8
|
readonly transactionHash: string | null;
|
package/dist/funding-types.js
CHANGED
|
@@ -2,6 +2,36 @@ import { hash, nativeToScVal, scValToNative, xdr } from "@stellar/stellar-sdk";
|
|
|
2
2
|
import { address, amount, bool, bytes32, exactObject, hex, hex32, label, paymentDestinationToScVal, sc, struct, u32, u64, unhex, } from "./native-codec.js";
|
|
3
3
|
import { destinationFromNative } from "./payment.js";
|
|
4
4
|
import { claimIntentToScVal } from "./native-types.js";
|
|
5
|
+
/** Read a response body as text, refusing more than `limit` bytes, declared or streamed, so a hostile or broken endpoint cannot make the client buffer it all. Returns null over the limit. */
|
|
6
|
+
export async function readBoundedText(response, limit) {
|
|
7
|
+
if (Number(response.headers.get("content-length") ?? 0) > limit) {
|
|
8
|
+
await response.body?.cancel().catch(() => undefined);
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const reader = response.body?.getReader();
|
|
12
|
+
if (!reader)
|
|
13
|
+
return "";
|
|
14
|
+
const chunks = [];
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const { done, value } = await reader.read();
|
|
18
|
+
if (done)
|
|
19
|
+
break;
|
|
20
|
+
total += value.byteLength;
|
|
21
|
+
if (total > limit) {
|
|
22
|
+
await reader.cancel().catch(() => undefined);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
chunks.push(value);
|
|
26
|
+
}
|
|
27
|
+
const bytes = new Uint8Array(total);
|
|
28
|
+
let offset = 0;
|
|
29
|
+
for (const chunk of chunks) {
|
|
30
|
+
bytes.set(chunk, offset);
|
|
31
|
+
offset += chunk.byteLength;
|
|
32
|
+
}
|
|
33
|
+
return new TextDecoder().decode(bytes);
|
|
34
|
+
}
|
|
5
35
|
export class FundingError extends Error {
|
|
6
36
|
kind;
|
|
7
37
|
transactionHash;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
|
+
import { type SubnamePolicy, type SubnameRecord } from "./names.js";
|
|
2
|
+
export { type SubnamePolicy, type SubnameRecord } from "./names.js";
|
|
1
3
|
import { type HistoricalRecoveryOptions } from "./native-history.js";
|
|
2
4
|
import type { ClaimIntent } from "./native-types.js";
|
|
3
5
|
export type { HistoricalRecoveryOptions } from "./native-history.js";
|
|
6
|
+
/**
|
|
7
|
+
* @sorandomains/owner — the write-side SDK for Soran namespace owners.
|
|
8
|
+
*
|
|
9
|
+
* Everything a namespace owner does after winning a namespace, programmatically:
|
|
10
|
+
*
|
|
11
|
+
* import { SoranOwner, keypairSigner } from "@sorandomains/owner";
|
|
12
|
+
*
|
|
13
|
+
* const owner = new SoranOwner({ signer: keypairSigner(process.env.OWNER_SECRET!) });
|
|
14
|
+
* await owner.issue("acme", "alice", "GDHN…"); // alice.acme now resolves
|
|
15
|
+
* await owner.issueBatch("acme", csvRows); // up to 23 per transaction
|
|
16
|
+
*
|
|
17
|
+
* TRUST MODEL. This SDK talks to the contracts directly over any Soroban RPC
|
|
18
|
+
* node — no Soran account, no hosted API, no third party in the path. Every
|
|
19
|
+
* operation here is authorized on chain: the Registrar and Registry check
|
|
20
|
+
* `require_auth` against the namespace owner's address, so the only credential
|
|
21
|
+
* that matters is the signer you supply. Soran's servers cannot perform any of
|
|
22
|
+
* these operations for you or against you.
|
|
23
|
+
*
|
|
24
|
+
* WHO CAN CALL WHAT. All operations below are namespace-OWNER powers except
|
|
25
|
+
* `acceptNamespaceTransfer`, which the proposed NEW owner signs. Name-holder
|
|
26
|
+
* powers (transferring an individual name, pointing it at a new address,
|
|
27
|
+
* electing a primary name) are the holder's alone — they live in the contracts'
|
|
28
|
+
* holder-authorized entry points and live in `@sorandomains/holder`; this
|
|
29
|
+
* package deliberately cannot exercise them.
|
|
30
|
+
*
|
|
31
|
+
* SIGNING. Supply a `TxSigner`: `keypairSigner(secret)` for backends, or wrap a
|
|
32
|
+
* browser wallet (Freighter / Stellar Wallets Kit expose exactly this
|
|
33
|
+
* `signTransaction(xdr, { networkPassphrase })` shape). The SDK builds and
|
|
34
|
+
* simulates each transaction, hands you the envelope to sign, and submits it.
|
|
35
|
+
* Operations on one SoranOwner instance are serialized so concurrent calls
|
|
36
|
+
* cannot race the account sequence number.
|
|
37
|
+
*
|
|
38
|
+
* READS. For resolution, reverse lookup, and assurance checks use the read-only
|
|
39
|
+
* companion package `@sorandomains/lookup` — wallets and apps should depend on
|
|
40
|
+
* that one only. The few reads here (policy, pending transfers, records) exist
|
|
41
|
+
* to support write flows.
|
|
42
|
+
*/
|
|
43
|
+
import { type ChainNetwork } from "./multichain.js";
|
|
44
|
+
export { CHAIN_NETWORKS, ChainAddressError, chainNetwork, chainPolicyFromNative, chainPolicyToNative, encodeChainAddress, decodeChainAddress, type ChainNetwork, type ChainAddressErrorCode } from "./multichain.js";
|
|
4
45
|
import { type NativeWriteOptions, type NativeCapability } from "./native-transport.js";
|
|
5
|
-
import { type ContractUpgradeRole, type ContractUpgradeStatus } from "./native-upgrades.js";
|
|
6
|
-
export type
|
|
46
|
+
import { type ContractUpgradeOptions, type ContractUpgradeRole, type ContractUpgradeStatus } from "./native-upgrades.js";
|
|
47
|
+
export { CONTRACT_RELEASES, contractUpgradeDirection, type ContractUpgradeDirection, type ContractUpgradeOptions, type ContractUpgradeRole, type ContractUpgradeStatus } from "./native-upgrades.js";
|
|
7
48
|
import { type ClaimSettings, type ClaimConfig, type ClaimUsage, type ApprovalUsage } from "./native-types.js";
|
|
8
49
|
export * from "./native-types.js";
|
|
9
50
|
export * from "./native-allowlist.js";
|
|
@@ -166,9 +207,9 @@ export declare class SoranOwner {
|
|
|
166
207
|
contractUpgradeStatus(namespace: string, role: ContractUpgradeRole, wasmHash: string): Promise<ContractUpgradeStatus>;
|
|
167
208
|
private upgradeContract;
|
|
168
209
|
/** Current-owner, Registry-approved update; never uses the legacy tainting path. */
|
|
169
|
-
upgradeRegistrar(namespace: string, wasmHash: string, options?:
|
|
210
|
+
upgradeRegistrar(namespace: string, wasmHash: string, options?: ContractUpgradeOptions): Promise<Submitted>;
|
|
170
211
|
/** Current-owner, Registry-approved Resolver update with exact native authorization. */
|
|
171
|
-
upgradeResolver(namespace: string, wasmHash: string, options?:
|
|
212
|
+
upgradeResolver(namespace: string, wasmHash: string, options?: ContractUpgradeOptions): Promise<Submitted>;
|
|
172
213
|
configureClaims(namespace: string, settings: ClaimSettings, options?: NativeWriteOptions): Promise<Submitted & {
|
|
173
214
|
config: ClaimConfig;
|
|
174
215
|
}>;
|
|
@@ -253,6 +294,12 @@ export declare class SoranOwner {
|
|
|
253
294
|
* Refused (ResolverFrozen) once the namespace is permanent.
|
|
254
295
|
*/
|
|
255
296
|
setResolver(namespace: string, resolver: string | null): Promise<Submitted>;
|
|
297
|
+
/** Networks currently enabled for all holders, including names already claimed. */
|
|
298
|
+
chainPolicy(namespace: string): Promise<ChainNetwork[]>;
|
|
299
|
+
/** Set the namespace-wide network allowlist. Empty disables all multichain records.
|
|
300
|
+
* Disabled destinations remain stored and become visible if re-enabled. */
|
|
301
|
+
setChainPolicy(namespace: string, networks: readonly ChainNetwork[]): Promise<Submitted>;
|
|
302
|
+
private multichainResolverOf;
|
|
256
303
|
/** The namespace's immutable issuance policy. */
|
|
257
304
|
policy(namespace: string): Promise<NamespacePolicy>;
|
|
258
305
|
/** Has the namespace passed the one-way door? */
|
|
@@ -261,6 +308,30 @@ export declare class SoranOwner {
|
|
|
261
308
|
nameState(namespace: string, label: string): Promise<NameState | null>;
|
|
262
309
|
/** The pending namespace transfer on the Registry, or null. */
|
|
263
310
|
pendingNamespaceTransfer(namespace: string): Promise<unknown | null>;
|
|
311
|
+
private subnameReader;
|
|
312
|
+
/** Live namespace policy. Creation-disabled keeps existing children usable; suspended hides them. */
|
|
313
|
+
subnamePolicy(namespace: string): Promise<SubnamePolicy>;
|
|
314
|
+
/** Raw child state, including tombstones and stale parent generations; this is not payment resolution. */
|
|
315
|
+
subnameRecord(name: string): Promise<SubnameRecord | null>;
|
|
316
|
+
/** Bounded on-chain historical labels, including removed children. Re-read metadata before treating a child as active. */
|
|
317
|
+
subnames(parent: string, options?: {
|
|
318
|
+
offset?: number;
|
|
319
|
+
limit?: number;
|
|
320
|
+
}): Promise<{
|
|
321
|
+
parent: string;
|
|
322
|
+
policy: SubnamePolicy;
|
|
323
|
+
records: {
|
|
324
|
+
parentNode: Uint8Array;
|
|
325
|
+
parentGeneration: bigint;
|
|
326
|
+
generation: bigint;
|
|
327
|
+
address: string;
|
|
328
|
+
active: boolean;
|
|
329
|
+
name: string;
|
|
330
|
+
}[];
|
|
331
|
+
offset: number;
|
|
332
|
+
nextOffset: number | null;
|
|
333
|
+
}>;
|
|
334
|
+
setSubnamePolicy(namespace: string, policy: SubnamePolicy): Promise<Submitted>;
|
|
264
335
|
private readRecord;
|
|
265
336
|
private readRecords;
|
|
266
337
|
/** Simulation-only contract read — no signature, no fee, no state change. */
|
|
@@ -281,6 +352,15 @@ export declare class SoranOwner {
|
|
|
281
352
|
* auth demands an address the envelope signature cannot satisfy.
|
|
282
353
|
*/
|
|
283
354
|
private assertSatisfiableAuth;
|
|
355
|
+
/**
|
|
356
|
+
* (SR-01 / SW-05) Default-deny for every legacy write: this source account, one invoke-contract operation on the
|
|
357
|
+
* intended contract, function and arguments, and exactly one source-account authorization whose root is that same
|
|
358
|
+
* call with NO sub-invocations. Every owner writer is a single call (issue, issue_batch, reclaim, renew,
|
|
359
|
+
* set_treasury, make_permanent, namespace transfers, set_resolver, set_chain_policy, set_subname_policy). The
|
|
360
|
+
* Registrar an owner talks to can be upgraded by whoever owned the namespace before them, so the callee is not a
|
|
361
|
+
* safe assumption: a recorded `token.transfer(owner -> attacker)` child call must never be signed.
|
|
362
|
+
*/
|
|
363
|
+
private assertExactIntent;
|
|
284
364
|
private attempt;
|
|
285
365
|
private restore;
|
|
286
366
|
private confirm;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { subnamePolicyToNative } from "./names.js";
|
|
2
|
+
import { createSubnameReader, SUBNAME_REGISTRAR_ERRORS } from "./subnames-client.js";
|
|
1
3
|
import { recoverFrozenClaim, historicalReadLedger } from "./native-history.js";
|
|
2
4
|
/**
|
|
3
5
|
* @sorandomains/owner — the write-side SDK for Soran namespace owners.
|
|
@@ -36,6 +38,8 @@ import { recoverFrozenClaim, historicalReadLedger } from "./native-history.js";
|
|
|
36
38
|
* that one only. The few reads here (policy, pending transfers, records) exist
|
|
37
39
|
* to support write flows.
|
|
38
40
|
*/
|
|
41
|
+
import { chainPolicyFromNative, chainPolicyToNative } from "./multichain.js";
|
|
42
|
+
export { CHAIN_NETWORKS, ChainAddressError, chainNetwork, chainPolicyFromNative, chainPolicyToNative, encodeChainAddress, decodeChainAddress } from "./multichain.js";
|
|
39
43
|
import { Account, Address, BASE_FEE, Contract, Keypair, Networks, Operation, StrKey, SorobanDataBuilder, TransactionBuilder, hash, nativeToScVal, rpc, scValToNative, xdr, } from "@stellar/stellar-sdk";
|
|
40
44
|
/** Contract events from a transaction's meta, across meta versions (v3 puts
|
|
41
45
|
* them on sorobanMeta; v4 nests them per-operation and in tx events). */
|
|
@@ -87,7 +91,8 @@ function decodeIssuedEvents(events, registrarId) {
|
|
|
87
91
|
}
|
|
88
92
|
import { NativeClaimError, address as nativeAddress, bool as nativeBool, bytes32 as nativeBytes32, hex as nativeHex, sc as nativeSc } from "./native-codec.js";
|
|
89
93
|
import { requireNative, nativeCapability, sendNative } from "./native-transport.js";
|
|
90
|
-
import { contractUpgradeStatus as readContractUpgradeStatus, upgradeHash } from "./native-upgrades.js";
|
|
94
|
+
import { contractUpgradeStatus as readContractUpgradeStatus, contractUpgradeDirection, upgradeHash } from "./native-upgrades.js";
|
|
95
|
+
export { CONTRACT_RELEASES, contractUpgradeDirection } from "./native-upgrades.js";
|
|
91
96
|
import { claimSettingsToScVal, claimConfigFromNative, claimLabelToScVal, claimUsageFromNative, approvalUsageFromNative } from "./native-types.js";
|
|
92
97
|
export * from "./native-types.js";
|
|
93
98
|
export * from "./native-allowlist.js";
|
|
@@ -184,6 +189,15 @@ const REGISTRAR_ERRORS = {
|
|
|
184
189
|
49: "MigrationUnavailable",
|
|
185
190
|
50: "MigrationUnsupported",
|
|
186
191
|
51: "PermanentLockDisabled",
|
|
192
|
+
52: "InvalidRenewalGrace",
|
|
193
|
+
53: "SubnameCreationDisabled", 54: "SubnamesSuspended",
|
|
194
|
+
55: "SubnameGenerationMismatch", 56: "SubnameIndexUnavailable",
|
|
195
|
+
};
|
|
196
|
+
/** Error names shared by native multichain Resolver reads and writes. */
|
|
197
|
+
const CHAIN_RESOLVER_ERRORS = {
|
|
198
|
+
2: "NotInitialized", 7: "InvalidAuthority", 33: "UnsupportedCoinType",
|
|
199
|
+
34: "ChainDisabled", 35: "InvalidChainAddress", 36: "InvalidChainPolicy",
|
|
200
|
+
37: "ChainContextMismatch", 38: "ChainUnavailable",
|
|
187
201
|
};
|
|
188
202
|
/** Registry contract error codes, by number. */
|
|
189
203
|
const REGISTRY_ERRORS = {
|
|
@@ -228,6 +242,7 @@ const REGISTRY_ERRORS = {
|
|
|
228
242
|
39: "UpgradePolicyMismatch",
|
|
229
243
|
40: "UpgradeNotApproved",
|
|
230
244
|
41: "PermanentLockDisabled",
|
|
245
|
+
42: "BatchTooLarge",
|
|
231
246
|
};
|
|
232
247
|
/** Errors for a submit the network REJECTED as a stale sequence: nothing was included, so one retry with a fresh sequence is safe. */
|
|
233
248
|
const BAD_SEQ_REJECTED = new WeakSet();
|
|
@@ -443,8 +458,17 @@ export class SoranOwner {
|
|
|
443
458
|
throw new NativeClaimError("the current namespace owner must authorize this change", "authorization");
|
|
444
459
|
if (!status.governed || !status.approved)
|
|
445
460
|
throw new NativeClaimError("This contract upgrade is not approved by the Registry", "authorization");
|
|
446
|
-
|
|
461
|
+
const direction = contractUpgradeDirection(role, status.currentHash, wasmHash);
|
|
462
|
+
if (direction === "same")
|
|
447
463
|
throw new NativeClaimError("This contract implementation is already installed");
|
|
464
|
+
// Registry approval says a hash may be installed, not that the move is safe:
|
|
465
|
+
// older releases stay approved, and a downgrade drops their fixes.
|
|
466
|
+
if (options.allowDowngrade !== true) {
|
|
467
|
+
if (direction === "backward")
|
|
468
|
+
throw new NativeClaimError("Refusing to install a superseded implementation (a downgrade that drops later fixes). Install the latest reviewed release, or pass allowDowngrade: true only after reviewing the older release for this namespace.", "authorization");
|
|
469
|
+
if (direction === "unrecognized-current")
|
|
470
|
+
throw new NativeClaimError("This namespace runs an implementation this SDK does not recognise, so installing the latest known release may be a downgrade. Update the SDK, or pass allowDowngrade: true after verifying the installed release is older.", "authorization");
|
|
471
|
+
}
|
|
448
472
|
const result = await sendNative(context, {
|
|
449
473
|
source, contract: status.contractId, method: "upgrade", args,
|
|
450
474
|
sourceInvocation: { contract: status.contractId, method: "upgrade", args },
|
|
@@ -715,6 +739,44 @@ export class SoranOwner {
|
|
|
715
739
|
const r = await this.invoke(this.registryId, "set_resolver", [nodeArg(namehash(namespace)), resolver === null ? xdr.ScVal.scvVoid() : addrArg(resolver)], REGISTRY_ERRORS);
|
|
716
740
|
return { hash: r.hash, ledger: r.ledger };
|
|
717
741
|
}
|
|
742
|
+
/** Networks currently enabled for all holders, including names already claimed. */
|
|
743
|
+
async chainPolicy(namespace) {
|
|
744
|
+
namespace = normalizeLabel(namespace);
|
|
745
|
+
const resolver = await this.multichainResolverOf(namespace);
|
|
746
|
+
return chainPolicyFromNative(await this.read(resolver, "chain_policy", []));
|
|
747
|
+
}
|
|
748
|
+
/** Set the namespace-wide network allowlist. Empty disables all multichain records.
|
|
749
|
+
* Disabled destinations remain stored and become visible if re-enabled. */
|
|
750
|
+
async setChainPolicy(namespace, networks) {
|
|
751
|
+
namespace = normalizeLabel(namespace);
|
|
752
|
+
const coins = chainPolicyToNative(networks);
|
|
753
|
+
const resolver = await this.multichainResolverOf(namespace);
|
|
754
|
+
await this.assertOwner(namespace);
|
|
755
|
+
const result = await this.invoke(resolver, "set_chain_policy", [
|
|
756
|
+
xdr.ScVal.scvVec(coins.map(coin => nativeToScVal(coin, { type: "u32" }))),
|
|
757
|
+
], CHAIN_RESOLVER_ERRORS);
|
|
758
|
+
return { hash: result.hash, ledger: result.ledger };
|
|
759
|
+
}
|
|
760
|
+
async multichainResolverOf(namespace) {
|
|
761
|
+
const nsNode = namehash(namespace);
|
|
762
|
+
const args = [nodeArg(nsNode)];
|
|
763
|
+
const [resolver, registrar] = await Promise.all([
|
|
764
|
+
this.read(this.registryId, "resolver_of", args), this.read(this.registryId, "registrar_of", args),
|
|
765
|
+
]);
|
|
766
|
+
if (typeof resolver !== "string" || !StrKey.isValidContract(resolver) || typeof registrar !== "string" || !StrKey.isValidContract(registrar))
|
|
767
|
+
throw new OwnerError("namespace has no native multichain route", this.registryId, "resolver_of", null, "UnsupportedImplementation");
|
|
768
|
+
const [anchor, authority, version, anchors] = await Promise.all([
|
|
769
|
+
this.read(resolver, "registry", []), this.read(resolver, "authority", []),
|
|
770
|
+
this.read(resolver, "multichain_version", []), this.read(registrar, "anchors", []),
|
|
771
|
+
]);
|
|
772
|
+
if (anchor !== this.registryId || authority !== registrar || !Array.isArray(anchors) || anchors.length !== 2 ||
|
|
773
|
+
anchors[0] !== this.registryId || !(anchors[1] instanceof Uint8Array) || anchors[1].length !== 32 ||
|
|
774
|
+
!nsNode.every((byte, index) => byte === anchors[1][index]))
|
|
775
|
+
throw new OwnerError("multichain Resolver route does not match this Registry and namespace", resolver, "registry", 37, "ChainContextMismatch");
|
|
776
|
+
if (version !== 1)
|
|
777
|
+
throw new OwnerError("unsupported multichain Resolver version", resolver, "multichain_version", null, "UnsupportedImplementation");
|
|
778
|
+
return resolver;
|
|
779
|
+
}
|
|
718
780
|
// ---- reads that support write flows --------------------------------------
|
|
719
781
|
/** The namespace's immutable issuance policy. */
|
|
720
782
|
async policy(namespace) {
|
|
@@ -745,6 +807,20 @@ export class SoranOwner {
|
|
|
745
807
|
return this.read(this.registryId, "pending_transfer", [nodeArg(namehash(namespace))]);
|
|
746
808
|
}
|
|
747
809
|
// ---- internals -----------------------------------------------------------
|
|
810
|
+
subnameReader() { return createSubnameReader({ registryId: this.registryId, error: message => new OwnerError(message), read: (id, fn, args) => this.read(id, fn, args) }); }
|
|
811
|
+
/** Live namespace policy. Creation-disabled keeps existing children usable; suspended hides them. */
|
|
812
|
+
async subnamePolicy(namespace) { return this.subnameReader().policy(namespace); }
|
|
813
|
+
/** Raw child state, including tombstones and stale parent generations; this is not payment resolution. */
|
|
814
|
+
async subnameRecord(name) { return this.subnameReader().record(name); }
|
|
815
|
+
/** Bounded on-chain historical labels, including removed children. Re-read metadata before treating a child as active. */
|
|
816
|
+
async subnames(parent, options = {}) { return this.subnameReader().list(parent, options); }
|
|
817
|
+
async setSubnamePolicy(namespace, policy) {
|
|
818
|
+
const native = subnamePolicyToNative(policy);
|
|
819
|
+
const ctx = await this.subnameReader().context(namespace);
|
|
820
|
+
await this.assertOwner(ctx.namespace);
|
|
821
|
+
const result = await this.invoke(ctx.registrar, "set_subname_policy", [xdr.ScVal.scvVec([nativeToScVal(native[0], { type: "symbol" })])], { ...REGISTRAR_ERRORS, ...SUBNAME_REGISTRAR_ERRORS });
|
|
822
|
+
return { hash: result.hash, ledger: result.ledger };
|
|
823
|
+
}
|
|
748
824
|
async readRecord(registrarId, label) {
|
|
749
825
|
const rec = (await this.read(registrarId, "record_of", [labelArg(label)]));
|
|
750
826
|
if (!rec)
|
|
@@ -777,7 +853,8 @@ export class SoranOwner {
|
|
|
777
853
|
.build();
|
|
778
854
|
const sim = await this.server.simulateTransaction(tx);
|
|
779
855
|
if (rpc.Api.isSimulationError(sim)) {
|
|
780
|
-
|
|
856
|
+
const errors = fn.startsWith("subname_") ? SUBNAME_REGISTRAR_ERRORS : ["chain_policy", "chain_address", "multichain_version"].includes(fn) ? CHAIN_RESOLVER_ERRORS : {};
|
|
857
|
+
throw typedError(contractId, fn, sim.error, errors);
|
|
781
858
|
}
|
|
782
859
|
// An archived entry is NOT absence: reads cannot restore (no signer),
|
|
783
860
|
// so surface the state honestly instead of reporting "does not exist".
|
|
@@ -853,6 +930,31 @@ export class SoranOwner {
|
|
|
853
930
|
: `${fn}: this operation must be authorized by ${required}, but the signer is ${pub} — use that account's signer`, contractId, fn);
|
|
854
931
|
}
|
|
855
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* (SR-01 / SW-05) Default-deny for every legacy write: this source account, one invoke-contract operation on the
|
|
935
|
+
* intended contract, function and arguments, and exactly one source-account authorization whose root is that same
|
|
936
|
+
* call with NO sub-invocations. Every owner writer is a single call (issue, issue_batch, reclaim, renew,
|
|
937
|
+
* set_treasury, make_permanent, namespace transfers, set_resolver, set_chain_policy, set_subname_policy). The
|
|
938
|
+
* Registrar an owner talks to can be upgraded by whoever owned the namespace before them, so the callee is not a
|
|
939
|
+
* safe assumption: a recorded `token.transfer(owner -> attacker)` child call must never be signed.
|
|
940
|
+
*/
|
|
941
|
+
assertExactIntent(prepared, pub, contractId, fn, args) {
|
|
942
|
+
if (prepared.source !== pub || prepared.operations.length !== 1)
|
|
943
|
+
throw new OwnerError(`${fn}: unexpected transaction source or operation count`, contractId, fn);
|
|
944
|
+
const op = prepared.operations[0];
|
|
945
|
+
if (op.type !== "invokeHostFunction" || (op.source !== undefined && op.source !== pub) || op.func.type !== "hostFunctionTypeInvokeContract")
|
|
946
|
+
throw new OwnerError(`${fn}: unexpected operation`, contractId, fn);
|
|
947
|
+
const call = op.func.invokeContract;
|
|
948
|
+
if (Address.fromScAddress(call.contractAddress).toString() !== contractId || call.functionName.toString() !== fn ||
|
|
949
|
+
call.args.length !== args.length || call.args.some((value, i) => value.toXDR("base64") !== args[i].toXDR("base64")))
|
|
950
|
+
throw new OwnerError(`${fn}: operation differs from selected intent`, contractId, fn);
|
|
951
|
+
if (!op.auth || op.auth.length !== 1 || op.auth[0].credentials.type !== "sorobanCredentialsSourceAccount")
|
|
952
|
+
throw new OwnerError(`${fn}: requires exactly the source owner authorization`, contractId, fn);
|
|
953
|
+
const root = op.auth[0].rootInvocation;
|
|
954
|
+
if (root.function.type !== "sorobanAuthorizedFunctionTypeContractFn" || root.subInvocations.length ||
|
|
955
|
+
root.function.contractFn.toXDR("base64") !== call.toXDR("base64"))
|
|
956
|
+
throw new OwnerError(`${fn}: authorization differs from selected intent (unexpected nested authorization)`, contractId, fn);
|
|
957
|
+
}
|
|
856
958
|
async attempt(contractId, fn, args, errNames) {
|
|
857
959
|
const pub = await this.signer.publicKey();
|
|
858
960
|
const contract = new Contract(contractId);
|
|
@@ -883,6 +985,7 @@ export class SoranOwner {
|
|
|
883
985
|
const prepared = rpc.assembleTransaction(tx, sim).build();
|
|
884
986
|
this.assertNetworkFee(prepared.fee, fn);
|
|
885
987
|
this.assertSatisfiableAuth(prepared, pub, contractId, fn);
|
|
988
|
+
this.assertExactIntent(prepared, pub, contractId, fn, args);
|
|
886
989
|
// The hash is fixed before signatures — compute it now so every failure
|
|
887
990
|
// past this point can carry it (the "re-check before retrying" contract).
|
|
888
991
|
const txHash = toHex(prepared.hash()); // (SDK17) hash() is Uint8Array
|
|
@@ -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
|
+
}
|
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. */
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { xdr } from "@stellar/stellar-sdk";
|
|
2
|
-
import type { NativeContext } from "./native-transport.js";
|
|
2
|
+
import type { NativeContext, NativeWriteOptions } from "./native-transport.js";
|
|
3
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
|
+
};
|
|
4
8
|
export type ContractUpgradeStatus = {
|
|
5
9
|
owner: string;
|
|
6
10
|
contractId: string;
|
|
@@ -8,6 +12,37 @@ export type ContractUpgradeStatus = {
|
|
|
8
12
|
approved: boolean;
|
|
9
13
|
governed: boolean;
|
|
10
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;
|
|
11
46
|
export declare function upgradeHash(hash: string): xdr.ScVal;
|
|
12
47
|
/** Independent of claim versions, claim configuration and fee-token policy. */
|
|
13
48
|
export declare function contractUpgradeStatus(context: NativeContext, namespace: string, role: ContractUpgradeRole, wasmHash: string): Promise<ContractUpgradeStatus>;
|
package/dist/native-upgrades.js
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
import { NativeClaimError, address, bool, bytes32, exactObject, hex, namespaceNode, sc, u32 } from "./native-codec.js";
|
|
2
2
|
import { expectedNativeCode } from "./native-code-policy.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reviewed implementation lineage of the governed testnet Registry. Governance
|
|
5
|
+
* approval means "this hash may be installed on any namespace", not "this is a
|
|
6
|
+
* safe transition": superseded releases stay approved, so the chain accepts a
|
|
7
|
+
* downgrade. `latest` is the release owners are offered; `superseded` are the
|
|
8
|
+
* older releases it replaces. A test keeps this equal to the web catalog
|
|
9
|
+
* (web/lib/contractUpdates.ts) and the release record; adding a release means
|
|
10
|
+
* moving the old `latest` into `superseded` here and there.
|
|
11
|
+
*/
|
|
12
|
+
export const CONTRACT_RELEASES = {
|
|
13
|
+
registrar: {
|
|
14
|
+
latest: "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1",
|
|
15
|
+
superseded: [
|
|
16
|
+
"42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420",
|
|
17
|
+
"5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c",
|
|
18
|
+
"61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5",
|
|
19
|
+
"26e03cd1d7fbfa46fe442406fb1b88022ec8cd8bd9123bed570f971fce596a14",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
resolver: {
|
|
23
|
+
latest: "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806",
|
|
24
|
+
superseded: [
|
|
25
|
+
"cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48",
|
|
26
|
+
"351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea",
|
|
27
|
+
"c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71",
|
|
28
|
+
"5e7b4fb8d310d67ed6966a155e748d60be771422b3e8c34bbc14485f98525574",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export function contractUpgradeDirection(role, currentHash, targetHash) {
|
|
33
|
+
const { latest, superseded } = CONTRACT_RELEASES[role];
|
|
34
|
+
if (currentHash === targetHash)
|
|
35
|
+
return "same";
|
|
36
|
+
if (superseded.includes(targetHash))
|
|
37
|
+
return "backward";
|
|
38
|
+
if (targetHash !== latest)
|
|
39
|
+
return "unreviewed-target";
|
|
40
|
+
return superseded.includes(currentHash) ? "forward" : "unrecognized-current";
|
|
41
|
+
}
|
|
3
42
|
export function upgradeHash(hash) {
|
|
4
43
|
if (typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
|
|
5
44
|
throw new NativeClaimError("Contract implementation hash must be lowercase hex32");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type xdr } from "@stellar/stellar-sdk";
|
|
2
|
+
import { type SubnameRecord } from "./names.js";
|
|
3
|
+
export declare const SUBNAME_REGISTRAR_ERRORS: Record<number, string>;
|
|
4
|
+
export declare const subnameLabelArg: (value: string) => xdr.ScVal;
|
|
5
|
+
export declare function subnameParent(input: string): import("./names.js").ParsedResolvableName;
|
|
6
|
+
export declare function subnameParts(input: string): import("./names.js").ParsedResolvableName;
|
|
7
|
+
export declare function createSubnameReader(config: {
|
|
8
|
+
registryId: string;
|
|
9
|
+
error?: (message: string) => Error;
|
|
10
|
+
read: (id: string, fn: string, args: xdr.ScVal[]) => Promise<unknown>;
|
|
11
|
+
}): {
|
|
12
|
+
context: (input: string) => Promise<{
|
|
13
|
+
namespace: string;
|
|
14
|
+
registrar: string;
|
|
15
|
+
owner: string;
|
|
16
|
+
policy: import("./names.js").SubnamePolicy;
|
|
17
|
+
}>;
|
|
18
|
+
creationContext: (input: string) => Promise<{
|
|
19
|
+
namespace: string;
|
|
20
|
+
registrar: string;
|
|
21
|
+
owner: string;
|
|
22
|
+
policy: import("./names.js").SubnamePolicy;
|
|
23
|
+
}>;
|
|
24
|
+
recordAt: (ctx: Awaited<ReturnType<(input: string) => Promise<{
|
|
25
|
+
namespace: string;
|
|
26
|
+
registrar: string;
|
|
27
|
+
owner: string;
|
|
28
|
+
policy: import("./names.js").SubnamePolicy;
|
|
29
|
+
}>>>, parent: string, label: string) => Promise<SubnameRecord | null>;
|
|
30
|
+
record: (name: string) => Promise<SubnameRecord | null>;
|
|
31
|
+
policy: (namespace: string) => Promise<import("./names.js").SubnamePolicy>;
|
|
32
|
+
list: (parent: string, options?: {
|
|
33
|
+
offset?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
parent: string;
|
|
37
|
+
policy: import("./names.js").SubnamePolicy;
|
|
38
|
+
records: {
|
|
39
|
+
parentNode: Uint8Array;
|
|
40
|
+
parentGeneration: bigint;
|
|
41
|
+
generation: bigint;
|
|
42
|
+
address: string;
|
|
43
|
+
active: boolean;
|
|
44
|
+
name: string;
|
|
45
|
+
}[];
|
|
46
|
+
offset: number;
|
|
47
|
+
nextOffset: number | null;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by sdk/names/sync.mjs; edit sdk/names/subnames-client.ts.
|
|
2
|
+
import { hash, nativeToScVal, StrKey } from "@stellar/stellar-sdk";
|
|
3
|
+
import { parseNamespace, parseResolvableName, resolvableNameNode, subnamePolicyFromNative, subnameRecordFromNative, subnameLabelsFromNative, subnamePageInput } from "./names.js";
|
|
4
|
+
export const SUBNAME_REGISTRAR_ERRORS = { 53: "SubnameCreationDisabled", 54: "SubnamesSuspended", 55: "SubnameGenerationMismatch", 56: "SubnameIndexUnavailable" };
|
|
5
|
+
const account = (value) => typeof value === "string" && (StrKey.isValidEd25519PublicKey(value) || StrKey.isValidContract(value));
|
|
6
|
+
const contract = (value) => typeof value === "string" && StrKey.isValidContract(value);
|
|
7
|
+
export const subnameLabelArg = (value) => nativeToScVal(new TextEncoder().encode(value), { type: "bytes" });
|
|
8
|
+
export function subnameParent(input) { const parsed = parseResolvableName(input); if (parsed.childLabel)
|
|
9
|
+
throw new Error("parent must be label.namespace"); return parsed; }
|
|
10
|
+
export function subnameParts(input) { const parsed = parseResolvableName(input); if (!parsed.childLabel)
|
|
11
|
+
throw new Error("subname must be child.label.namespace"); return parsed; }
|
|
12
|
+
export function createSubnameReader(config) {
|
|
13
|
+
const read = config.read;
|
|
14
|
+
const failure = (message) => config.error?.(message) ?? new Error(message);
|
|
15
|
+
async function context(input) {
|
|
16
|
+
const namespace = parseNamespace(input);
|
|
17
|
+
if (!contract(config.registryId))
|
|
18
|
+
throw failure("invalid Registry");
|
|
19
|
+
const joined = new Uint8Array(64);
|
|
20
|
+
joined.set(hash(new TextEncoder().encode(namespace)), 32);
|
|
21
|
+
const node = hash(joined);
|
|
22
|
+
const arg = nativeToScVal(node, { type: "bytes" });
|
|
23
|
+
const [registrar, owner] = await Promise.all([read(config.registryId, "registrar_of", [arg]), read(config.registryId, "owner_of", [arg])]);
|
|
24
|
+
if (!contract(registrar) || !account(owner))
|
|
25
|
+
throw failure("namespace has no current Registrar or owner");
|
|
26
|
+
const anchors = await read(registrar, "anchors", []);
|
|
27
|
+
if (!Array.isArray(anchors) || anchors.length !== 2 || anchors[0] !== config.registryId || !(anchors[1] instanceof Uint8Array) || anchors[1].length !== 32 || !node.every((b, i) => b === anchors[1][i]))
|
|
28
|
+
throw failure("subname Registrar context mismatch");
|
|
29
|
+
if (await read(registrar, "subname_version", []) !== 1)
|
|
30
|
+
throw failure("subnames unsupported");
|
|
31
|
+
const policy = subnamePolicyFromNative(await read(registrar, "subname_policy", []));
|
|
32
|
+
return { namespace, registrar, owner, policy };
|
|
33
|
+
}
|
|
34
|
+
async function creationContext(input) {
|
|
35
|
+
const ctx = await context(input), joined = new Uint8Array(64);
|
|
36
|
+
joined.set(hash(new TextEncoder().encode(ctx.namespace)), 32);
|
|
37
|
+
const resolver = await read(config.registryId, "resolver_of", [nativeToScVal(hash(joined), { type: "bytes" })]);
|
|
38
|
+
if (!contract(resolver))
|
|
39
|
+
throw failure("subnames unsupported: no Resolver");
|
|
40
|
+
const [anchor, authority, version] = await Promise.all([read(resolver, "registry", []), read(resolver, "authority", []), read(resolver, "subname_version", [])]);
|
|
41
|
+
if (anchor !== config.registryId || authority !== ctx.registrar)
|
|
42
|
+
throw failure("subname Resolver context mismatch");
|
|
43
|
+
if (version !== 1)
|
|
44
|
+
throw failure("subnames unsupported by Resolver");
|
|
45
|
+
return ctx;
|
|
46
|
+
}
|
|
47
|
+
async function recordAt(ctx, parent, label) {
|
|
48
|
+
const record = subnameRecordFromNative(await read(ctx.registrar, "subname_record", [subnameLabelArg(parent), subnameLabelArg(label)]), resolvableNameNode(`${parent}.${ctx.namespace}`));
|
|
49
|
+
if (record && !account(record.address))
|
|
50
|
+
throw failure("invalid subname destination");
|
|
51
|
+
return record;
|
|
52
|
+
}
|
|
53
|
+
async function record(name) { const parsed = subnameParts(name), ctx = await context(parsed.namespace); return recordAt(ctx, parsed.parentLabel, parsed.childLabel); }
|
|
54
|
+
async function policy(namespace) { return (await context(namespace)).policy; }
|
|
55
|
+
async function list(parent, options = {}) {
|
|
56
|
+
const parsed = subnameParent(parent), { offset, limit } = subnamePageInput(options.offset, options.limit), ctx = await context(parsed.namespace);
|
|
57
|
+
const labels = subnameLabelsFromNative(await read(ctx.registrar, "subname_labels", [subnameLabelArg(parsed.label), nativeToScVal(offset, { type: "u32" }), nativeToScVal(limit, { type: "u32" })]), limit);
|
|
58
|
+
const records = await Promise.all(labels.map(async (label) => { const record = await recordAt(ctx, parsed.label, label); if (!record)
|
|
59
|
+
throw failure("subname index references missing record"); return { name: `${label}.${parsed.name}`, ...record }; }));
|
|
60
|
+
return { parent: parsed.name, policy: ctx.policy, records, offset, nextOffset: labels.length === limit ? offset + labels.length : null };
|
|
61
|
+
}
|
|
62
|
+
return { context, creationContext, recordAt, record, policy, list };
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorandomains/owner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Run your Soran namespace on Stellar: configure claims, issue names and manage namespace policies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=22.12.0"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"prepublishOnly": "tsc",
|
|
18
18
|
"check:browser": "esbuild dist/index.js --bundle --platform=browser --external:@stellar/stellar-sdk --outfile=browser-check.js --legal-comments=none --log-level=error && node -e \"const fs=require('fs');const s=fs.readFileSync('browser-check.js','utf8');fs.unlinkSync('browser-check.js');if(/\\bBuffer\\b/.test(s)){console.error('FAIL: bare Buffer reference in browser bundle');process.exit(1)}console.log('browser bundle clean')\"",
|
|
19
|
-
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts test/native-*.test.mts && node --import tsx --test test/funding.test.mts test/funding-history.test.mts"
|
|
19
|
+
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts test/hardening.test.mts test/error-codes.test.mts test/nested-authorization.test.mts test/native-*.test.mts && node --import tsx --test test/funding.test.mts test/funding-history.test.mts test/funding-bounds.test.mts && node --import tsx --test test/multichain.test.mts && node --import tsx --test test/subnames.test.mts",
|
|
20
|
+
"test:hardening": "node --import tsx --test test/hardening.test.mts"
|
|
20
21
|
},
|
|
21
22
|
"license": "MIT",
|
|
22
23
|
"repository": {
|
|
@@ -41,15 +42,22 @@
|
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@stellar/stellar-sdk": "^17.0.1",
|
|
44
|
-
"@types/node": "^
|
|
45
|
-
"esbuild": "^0.
|
|
45
|
+
"@types/node": "^26.6.3",
|
|
46
|
+
"esbuild": "^0.28.2",
|
|
46
47
|
"tsx": "^4.19.2",
|
|
47
|
-
"typescript": "^
|
|
48
|
+
"typescript": "^7.0.2"
|
|
48
49
|
},
|
|
49
50
|
"exports": {
|
|
50
51
|
".": {
|
|
51
52
|
"types": "./dist/index.d.ts",
|
|
52
53
|
"default": "./dist/index.js"
|
|
53
54
|
}
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@noble/hashes": "2.4.0",
|
|
58
|
+
"@scure/base": "2.4.0"
|
|
59
|
+
},
|
|
60
|
+
"overrides": {
|
|
61
|
+
"axios": "1.20.0"
|
|
54
62
|
}
|
|
55
63
|
}
|