@sorandomains/owner 0.12.1 → 0.13.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 CHANGED
@@ -12,7 +12,7 @@ Lookup is the read entry point in `@sorandomains/lookup` 0.10.0 or later.
12
12
 
13
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
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).
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).
16
16
 
17
17
  Ownership, issuer, holder and treasury inputs remain G/C account or contract
18
18
  addresses. Muxed M addresses are payment destinations only: issue the name to its
@@ -119,7 +119,7 @@ Read the [native claim APIs, security boundaries and complete signup flow](https
119
119
 
120
120
  ## Verified testnet deployment
121
121
 
122
- See the [deployment manifest](../../deployments/testnet.json) for confirmed code hashes, transaction receipts and verification scope. Network passphrase: `Test SDF Network ; September 2015`.
122
+ See the [deployment manifest](../../deploy/testnet/deployment.json) for confirmed code hashes, transaction receipts and verification scope. Network passphrase: `Test SDF Network ; September 2015`.
123
123
 
124
124
  | Contract | Address |
125
125
  |---|---|
@@ -1,5 +1,5 @@
1
1
  import { FundingReader, type FundingOptions } from "./funding-reader.js";
2
- import { type SponsorPolicy } from "./funding-types.js";
2
+ import { type SponsorPolicy, type StoragePlan, type UpkeepPolicy, type ExecutionPolicy } from "./funding-types.js";
3
3
  import { type NativeSigner, type NativeWriteOptions } from "./native-transport.js";
4
4
  /** Direct owner operations work even when the sponsorship service is offline. */
5
5
  export declare class SoranFunding extends FundingReader {
@@ -42,4 +42,45 @@ export declare class SoranFunding extends FundingReader {
42
42
  ledger: number;
43
43
  value: unknown;
44
44
  }>;
45
+ activateStorage(namespace: string, plan: StoragePlan, retention: number, options?: NativeWriteOptions): Promise<{
46
+ hash: string;
47
+ ledger: number;
48
+ value: unknown;
49
+ }>;
50
+ private upkeepController;
51
+ configureUpkeep(id: string, policy: UpkeepPolicy, deposit: bigint, options?: NativeWriteOptions): Promise<{
52
+ hash: string;
53
+ ledger: number;
54
+ value: unknown;
55
+ }>;
56
+ depositUpkeep(id: string, amount: bigint, options?: NativeWriteOptions): Promise<{
57
+ hash: string;
58
+ ledger: number;
59
+ value: unknown;
60
+ }>;
61
+ pauseUpkeep(id: string, paused: boolean, options?: NativeWriteOptions): Promise<{
62
+ hash: string;
63
+ ledger: number;
64
+ value: unknown;
65
+ }>;
66
+ withdrawUpkeep(id: string, amount: bigint, recipient: string, options?: NativeWriteOptions): Promise<{
67
+ hash: string;
68
+ ledger: number;
69
+ value: unknown;
70
+ }>;
71
+ configureExecution(id: string, policy: ExecutionPolicy, deposit: bigint, options?: NativeWriteOptions): Promise<{
72
+ hash: string;
73
+ ledger: number;
74
+ value: unknown;
75
+ }>;
76
+ depositExecution(id: string, amount: bigint, options?: NativeWriteOptions): Promise<{
77
+ hash: string;
78
+ ledger: number;
79
+ value: unknown;
80
+ }>;
81
+ withdrawExecution(id: string, amount: bigint, recipient: string, maxPayoutFee: bigint, options?: NativeWriteOptions): Promise<{
82
+ hash: string;
83
+ ledger: number;
84
+ value: unknown;
85
+ }>;
45
86
  }
@@ -1,6 +1,6 @@
1
1
  import { Asset } from "@stellar/stellar-sdk";
2
2
  import { FundingReader } from "./funding-reader.js";
3
- import { FundingError, sponsorPolicyToScVal, } from "./funding-types.js";
3
+ import { FundingError, sponsorPolicyToScVal, storagePlanToScVal, upkeepPolicyToScVal, executionPolicyToScVal, } from "./funding-types.js";
4
4
  import { address, namespaceNode, sc, unhex } from "./native-codec.js";
5
5
  import { sendNative, } from "./native-transport.js";
6
6
  /** Direct owner operations work even when the sponsorship service is offline. */
@@ -94,4 +94,91 @@ export class SoranFunding extends FundingReader {
94
94
  async close(id, options = {}) {
95
95
  return this.write("close_fund", [sc.bytes(unhex(id))], await this.controller(id), options);
96
96
  }
97
+ async activateStorage(namespace, plan, retention, options = {}) {
98
+ await this.upkeepConfiguration();
99
+ if (!Number.isSafeInteger(retention) || retention < 1)
100
+ throw new FundingError("Invalid retention");
101
+ const node = sc.bytes(namespaceNode(namespace));
102
+ const owner = address((await this.read(this.registryId, "owner_of", [node])).value, "identity", "namespace owner");
103
+ return this.write("upkeep_activate", [node, storagePlanToScVal(plan), sc.u32(retention)], owner, options);
104
+ }
105
+ async upkeepController(id) {
106
+ await this.upkeepConfiguration();
107
+ const p = await this.upkeepPosition(id);
108
+ if (!p)
109
+ throw new FundingError("Prepay namespace storage first");
110
+ return p.controller;
111
+ }
112
+ async configureUpkeep(id, policy, deposit, options = {}) {
113
+ if (deposit < 0n)
114
+ throw new FundingError("Invalid deposit");
115
+ const owner = await this.upkeepController(id);
116
+ return this.write("upkeep_configure", [sc.bytes(unhex(id)), upkeepPolicyToScVal(policy), sc.i128(deposit)], owner, options, deposit > 0n
117
+ ? [
118
+ {
119
+ contract: Asset.native().contractId(this.passphrase),
120
+ method: "transfer",
121
+ args: [
122
+ sc.address(owner),
123
+ sc.address(this.fundingId),
124
+ sc.i128(deposit),
125
+ ],
126
+ },
127
+ ]
128
+ : []);
129
+ }
130
+ async depositUpkeep(id, amount, options = {}) {
131
+ if (amount <= 0n)
132
+ throw new FundingError("Deposit must be positive");
133
+ const owner = await this.upkeepController(id);
134
+ return this.write("upkeep_deposit", [sc.bytes(unhex(id)), sc.i128(amount)], owner, options, [
135
+ {
136
+ contract: Asset.native().contractId(this.passphrase),
137
+ method: "transfer",
138
+ args: [
139
+ sc.address(owner),
140
+ sc.address(this.fundingId),
141
+ sc.i128(amount),
142
+ ],
143
+ },
144
+ ]);
145
+ }
146
+ async pauseUpkeep(id, paused, options = {}) {
147
+ return this.write("upkeep_pause", [sc.bytes(unhex(id)), sc.bool(paused)], await this.upkeepController(id), options);
148
+ }
149
+ async withdrawUpkeep(id, amount, recipient, options = {}) {
150
+ if (amount <= 0n || recipient === this.fundingId)
151
+ throw new FundingError("Invalid withdrawal");
152
+ return this.write("upkeep_withdraw", [
153
+ sc.bytes(unhex(id)),
154
+ sc.address(address(recipient, "identity", "recipient")),
155
+ sc.i128(amount),
156
+ ], await this.upkeepController(id), options);
157
+ }
158
+ async configureExecution(id, policy, deposit, options = {}) {
159
+ if (deposit < 0n)
160
+ throw new FundingError("Invalid execution deposit");
161
+ const c = await this.executionConfiguration();
162
+ if (policy.wallet !== c.wallet)
163
+ throw new FundingError("Execution wallet differs from the approved deployment", "authorization");
164
+ const owner = await this.upkeepController(id);
165
+ return this.write("execution_configure", [sc.bytes(unhex(id)), executionPolicyToScVal(policy), sc.i128(deposit)], owner, options, deposit > 0n ? [{ contract: Asset.native().contractId(this.passphrase), method: "transfer", args: [sc.address(owner), sc.address(c.wallet), sc.i128(deposit)] }] : []);
166
+ }
167
+ async depositExecution(id, amount, options = {}) {
168
+ if (amount <= 0n)
169
+ throw new FundingError("Execution deposit must be positive");
170
+ const owner = await this.upkeepController(id), a = await this.executionApproval(id), c = await this.executionConfiguration();
171
+ if (!a || a.policy.wallet !== c.wallet)
172
+ throw new FundingError("Review prepaid execution terms first", "authorization");
173
+ return this.write("execution_deposit", [sc.bytes(unhex(id)), sc.i128(amount)], owner, options, [{ contract: Asset.native().contractId(this.passphrase), method: "transfer", args: [sc.address(owner), sc.address(c.wallet), sc.i128(amount)] }]);
174
+ }
175
+ async withdrawExecution(id, amount, recipient, maxPayoutFee, options = {}) {
176
+ if (amount <= 0n || maxPayoutFee < 100n || maxPayoutFee > 4294967295n)
177
+ throw new FundingError("Invalid prepaid withdrawal or payout fee ceiling");
178
+ address(recipient, "account", "withdrawal recipient");
179
+ const a = await this.executionApproval(id);
180
+ if (!a || recipient === a.policy.wallet || amount + maxPayoutFee > a.depositedTotal)
181
+ throw new FundingError("Invalid execution withdrawal");
182
+ return this.write("execution_withdraw_request", [sc.bytes(unhex(id)), sc.address(recipient), sc.i128(amount), sc.i128(maxPayoutFee)], await this.upkeepController(id), options);
183
+ }
97
184
  }
@@ -1,5 +1,5 @@
1
1
  import { rpc, xdr } from "@stellar/stellar-sdk";
2
- import { type SponsorQuote } from "./funding-types.js";
2
+ import { type SponsorQuote, type StoragePlan, type UpkeepQuote, type ExecutionQuote } from "./funding-types.js";
3
3
  export type FundingOptions = {
4
4
  fundingId: string;
5
5
  registryId: string;
@@ -40,4 +40,32 @@ export declare class FundingReader {
40
40
  policy(id: string): Promise<import("./funding-types.js").SponsorPolicy | null>;
41
41
  actorState(id: string, actor: string): Promise<import("./funding-types.js").SponsorActorState>;
42
42
  validateQuote(quote: SponsorQuote): Promise<void>;
43
+ upkeepConfiguration(): Promise<{
44
+ registry: string;
45
+ nativeAsset: string;
46
+ lookup: string;
47
+ primary: string;
48
+ governance: string;
49
+ paused: boolean;
50
+ revision: bigint;
51
+ ledger: number;
52
+ }>;
53
+ isUpkeepEnrolled(namespace: string): Promise<boolean>;
54
+ currentUpkeep(namespace: string): Promise<{
55
+ id: string;
56
+ position: import("./funding-types.js").UpkeepPosition | null;
57
+ }>;
58
+ upkeepPosition(id: string): Promise<import("./funding-types.js").UpkeepPosition | null>;
59
+ upkeepPolicy(id: string): Promise<import("./funding-types.js").UpkeepPolicy | null>;
60
+ storagePlan(namespace: string): Promise<StoragePlan>;
61
+ upkeepCoverage(id: string, plan: StoragePlan): Promise<number[]>;
62
+ validateUpkeepQuote(q: UpkeepQuote): Promise<void>;
63
+ executionConfiguration(): Promise<{
64
+ version: 2;
65
+ wallet: string;
66
+ }>;
67
+ executionApproval(id: string): Promise<import("./funding-types.js").ExecutionApproval | null>;
68
+ executionWithdrawal(id: string, nonce: bigint): Promise<import("./funding-types.js").ExecutionWithdrawal | null>;
69
+ executionWithdrawalNonce(id: string): Promise<bigint>;
70
+ validateExecutionQuote(q: ExecutionQuote): Promise<void>;
43
71
  }
@@ -1,6 +1,6 @@
1
1
  import { Account, Asset, Contract, Networks, StrKey, TransactionBuilder, hash, rpc, scValToNative, } from "@stellar/stellar-sdk";
2
2
  import { address, hex, namespaceNode, sc, unhex, utf8, } from "./native-codec.js";
3
- import { FundingError, fundingConfigurationFromNative, fundingPositionFromNative, fundingPositionId, sponsorActorFromNative, sponsorPolicyFromNative, sponsorQuoteToScVal, } from "./funding-types.js";
3
+ import { FundingError, fundingConfigurationFromNative, fundingPositionFromNative, fundingPositionId, sponsorActorFromNative, sponsorPolicyFromNative, sponsorQuoteToScVal, upkeepPositionId, upkeepPositionFromNative, upkeepPolicyFromNative, storagePlanToScVal, upkeepQuoteToScVal, executionApprovalFromNative, executionWithdrawalFromNative, executionQuoteToScVal, } from "./funding-types.js";
4
4
  export class FundingReader {
5
5
  fundingId;
6
6
  registryId;
@@ -18,9 +18,16 @@ export class FundingReader {
18
18
  if (this.passphrase !== Networks.TESTNET)
19
19
  throw new FundingError("Funding V1 is currently restricted to testnet");
20
20
  this.networkId = hex(hash(utf8(this.passphrase)));
21
- this.server =
22
- options.server ??
23
- new rpc.Server(options.rpcUrl ?? "https://soroban-testnet.stellar.org");
21
+ if (options.server) {
22
+ // A supplied client retains its caller-reviewed transport configuration.
23
+ this.server = options.server;
24
+ }
25
+ else {
26
+ this.server = new rpc.Server(options.rpcUrl ?? "https://soroban-testnet.stellar.org");
27
+ // SDK 17 ignores the constructor's timeout option. Bound the HTTP client
28
+ // actually used by simulation and ledger requests, as the API RPC factory does.
29
+ this.server.httpClient.defaults.timeout = 15_000;
30
+ }
24
31
  }
25
32
  async read(contract, method, args, minimumLedger = 1) {
26
33
  const source = new Account(StrKey.encodeEd25519PublicKey(new Uint8Array(32)), "0");
@@ -42,6 +49,11 @@ export class FundingReader {
42
49
  result.latestLedger < minimumLedger)
43
50
  throw new FundingError("Cannot verify funding contract state: " +
44
51
  ("error" in result ? result.error : "missing or stale response"), "unavailable");
52
+ const data = result.transactionData.build();
53
+ if (data.ext.type !== "v0" &&
54
+ (data.ext.type !== "resourceExt" ||
55
+ data.ext.value.archivedSorobanEntries.length > 0))
56
+ throw new FundingError("Cannot verify live funding state: archived entries require explicit restoration", "unavailable");
45
57
  return {
46
58
  value: scValToNative(result.result.retval),
47
59
  ledger: result.latestLedger,
@@ -99,4 +111,107 @@ export class FundingReader {
99
111
  await this.configuration();
100
112
  await this.read(this.fundingId, "quote_status", [sponsorQuoteToScVal(quote)], quote.issuedLedger);
101
113
  }
114
+ async upkeepConfiguration() {
115
+ const config = await this.configuration();
116
+ if ((await this.read(this.fundingId, "upkeep_version", [], config.ledger))
117
+ .value !== 1)
118
+ throw new FundingError("Owner-funded storage is not installed on this deployment", "unavailable");
119
+ return config;
120
+ }
121
+ async isUpkeepEnrolled(namespace) {
122
+ await this.upkeepConfiguration();
123
+ const r = await this.read(this.fundingId, "upkeep_enrolled", [sc.bytes(namespaceNode(namespace))]);
124
+ if (typeof r.value !== "boolean")
125
+ throw new FundingError("Namespace storage billing boundary is unavailable", "unavailable");
126
+ return r.value;
127
+ }
128
+ async currentUpkeep(namespace) {
129
+ await this.upkeepConfiguration();
130
+ const node = hex(namespaceNode(namespace));
131
+ const epoch = await this.read(this.registryId, "owner_epoch", [
132
+ sc.bytes(unhex(node)),
133
+ ]);
134
+ if (typeof epoch.value !== "bigint" || epoch.value < 1n)
135
+ throw new FundingError("Namespace ownership epoch unavailable", "unavailable");
136
+ const id = upkeepPositionId(node, epoch.value);
137
+ return { id, position: await this.upkeepPosition(id) };
138
+ }
139
+ async upkeepPosition(id) {
140
+ const r = await this.read(this.fundingId, "upkeep_position", [
141
+ sc.bytes(unhex(id)),
142
+ ]);
143
+ if (r.value === null || r.value === undefined)
144
+ return null;
145
+ const p = upkeepPositionFromNative(r.value);
146
+ if (upkeepPositionId(p.namespace, p.ownerEpoch) !== id)
147
+ throw new FundingError("Upkeep ownership epoch does not match", "unavailable");
148
+ return p;
149
+ }
150
+ async upkeepPolicy(id) {
151
+ const r = await this.read(this.fundingId, "upkeep_policy", [
152
+ sc.bytes(unhex(id)),
153
+ ]);
154
+ return r.value === null || r.value === undefined
155
+ ? null
156
+ : upkeepPolicyFromNative(r.value);
157
+ }
158
+ async storagePlan(namespace) {
159
+ await this.upkeepConfiguration();
160
+ const pair = (await this.read(this.registryId, "native_contracts", [
161
+ sc.bytes(namespaceNode(namespace)),
162
+ ])).value;
163
+ if (!Array.isArray(pair) || pair.length !== 2)
164
+ throw new FundingError("Native namespace pair unavailable", "unavailable");
165
+ const plan = {
166
+ registrar: address(pair[0], "contract", "Registrar"),
167
+ resolver: address(pair[1], "contract", "Resolver"),
168
+ registrarKeys: [],
169
+ resolverKeys: [],
170
+ };
171
+ for (const c of [plan.registrar, plan.resolver])
172
+ if ((await this.read(c, "storage_upkeep_version", [])).value !== 1)
173
+ throw new FundingError("Namespace storage upgrade required", "unavailable");
174
+ return plan;
175
+ }
176
+ async upkeepCoverage(id, plan) {
177
+ const r = await this.read(this.fundingId, "upkeep_coverage", [
178
+ sc.bytes(unhex(id)),
179
+ storagePlanToScVal(plan),
180
+ ]);
181
+ if (!Array.isArray(r.value) ||
182
+ r.value.some((v) => !Number.isSafeInteger(v) || v < 0))
183
+ throw new FundingError("Invalid storage coverage response", "unavailable");
184
+ return r.value;
185
+ }
186
+ async validateUpkeepQuote(q) {
187
+ if (q.funding !== this.fundingId || q.network !== this.networkId)
188
+ throw new FundingError("Upkeep quote belongs to another deployment", "authorization");
189
+ await this.upkeepConfiguration();
190
+ await this.read(this.fundingId, "upkeep_quote_status", [upkeepQuoteToScVal(q)], q.issuedLedger);
191
+ }
192
+ async executionConfiguration() {
193
+ await this.upkeepConfiguration();
194
+ if ((await this.read(this.fundingId, "execution_version", [])).value !== 2)
195
+ throw new FundingError("Prepaid execution contract upgrade required", "unavailable");
196
+ return { version: 2, wallet: address((await this.read(this.fundingId, "execution_wallet", [])).value, "account", "custodial execution wallet") };
197
+ }
198
+ async executionApproval(id) {
199
+ const value = (await this.read(this.fundingId, "execution_approval", [sc.bytes(unhex(id))])).value;
200
+ return value == null ? null : executionApprovalFromNative(value);
201
+ }
202
+ async executionWithdrawal(id, nonce) {
203
+ const value = (await this.read(this.fundingId, "execution_withdrawal", [sc.bytes(unhex(id)), sc.u64(nonce)])).value;
204
+ return value == null ? null : executionWithdrawalFromNative(value);
205
+ }
206
+ async executionWithdrawalNonce(id) {
207
+ const value = (await this.read(this.fundingId, "execution_withdrawal_nonce", [sc.bytes(unhex(id))])).value;
208
+ if (typeof value !== "bigint" || value < 0n)
209
+ throw new FundingError("Invalid withdrawal cursor", "unavailable");
210
+ return value;
211
+ }
212
+ async validateExecutionQuote(q) {
213
+ if (q.funding !== this.fundingId || q.network !== this.networkId)
214
+ throw new FundingError("Execution quote belongs to another deployment", "authorization");
215
+ await this.read(this.fundingId, "execution_quote_status", [executionQuoteToScVal(q)], q.issuedLedger);
216
+ }
102
217
  }
@@ -102,3 +102,91 @@ export declare function fundingPositionId(namespace: string, ownerEpoch: bigint)
102
102
  export declare const encodeSponsorQuote: (v: SponsorQuote) => string;
103
103
  export declare function decodeSponsorQuote(encoded: string): SponsorQuote;
104
104
  export declare const FUNDING_ERRORS: Record<number, string>;
105
+ /** Owner-funded namespace retention. Kept separate from registration coverage. */
106
+ export type StoragePlan = {
107
+ registrar: string;
108
+ resolver: string;
109
+ registrarKeys: xdr.ScVal[];
110
+ resolverKeys: xdr.ScVal[];
111
+ };
112
+ export type UpkeepPolicy = {
113
+ relayer: string;
114
+ renewalCap: bigint;
115
+ dailyCap: bigint;
116
+ totalCap: bigint;
117
+ retention: number;
118
+ renewBefore: number;
119
+ quoteLifetime: number;
120
+ validUntil: bigint;
121
+ };
122
+ export type UpkeepReceipt = {
123
+ quoteHash: string;
124
+ quoteId: string;
125
+ nonce: bigint;
126
+ charge: bigint;
127
+ balance: bigint;
128
+ ledger: number;
129
+ coveredUntil: number;
130
+ };
131
+ export type UpkeepPosition = Omit<FundingPosition, "status"> & {
132
+ status: "paused" | "active" | "closed";
133
+ nonce: bigint;
134
+ lastReceipt: UpkeepReceipt | null;
135
+ };
136
+ export type UpkeepQuote = {
137
+ network: string;
138
+ funding: string;
139
+ configRevision: bigint;
140
+ fundId: string;
141
+ policyRevision: bigint;
142
+ nonce: bigint;
143
+ quoteId: string;
144
+ plan: StoragePlan;
145
+ charge: bigint;
146
+ issuedLedger: number;
147
+ expiresLedger: number;
148
+ };
149
+ export type ExecutionPolicy = {
150
+ wallet: string;
151
+ limits: UpkeepPolicy;
152
+ serviceFeeCap: bigint;
153
+ termsVersion: 2;
154
+ serviceFeeBps: 500;
155
+ payFailedFees: true;
156
+ };
157
+ export type ExecutionApproval = {
158
+ policy: ExecutionPolicy;
159
+ depositedTotal: bigint;
160
+ depositNonce: bigint;
161
+ };
162
+ export type ExecutionQuote = Omit<UpkeepQuote, "charge"> & {
163
+ maxNetworkFee: bigint;
164
+ };
165
+ export type ExecutionReceipt = {
166
+ quoteHash: string;
167
+ quoteId: string;
168
+ nonce: bigint;
169
+ ledger: number;
170
+ coveredUntil: number;
171
+ };
172
+ export type ExecutionWithdrawal = {
173
+ controller: string;
174
+ recipient: string;
175
+ amount: bigint;
176
+ maxNetworkFee: bigint;
177
+ nonce: bigint;
178
+ };
179
+ export declare function executionPolicyToScVal(p: ExecutionPolicy): xdr.ScVal;
180
+ export declare function executionApprovalFromNative(value: unknown): ExecutionApproval;
181
+ export declare const executionReceiptFromNative: (value: unknown) => ExecutionReceipt;
182
+ export declare const executionReceiptToScVal: (value: ExecutionReceipt) => xdr.ScVal;
183
+ export declare const executionWithdrawalFromNative: (value: unknown) => ExecutionWithdrawal;
184
+ export declare const upkeepPositionFromNative: (v: unknown) => UpkeepPosition;
185
+ export declare const upkeepPolicyFromNative: (v: unknown) => UpkeepPolicy;
186
+ export declare const upkeepReceiptToScVal: (v: UpkeepReceipt) => xdr.ScVal;
187
+ export declare const upkeepReceiptFromNative: (v: unknown) => UpkeepReceipt;
188
+ export declare function upkeepPositionId(namespace: string, epoch: bigint): string;
189
+ export declare function upkeepPolicyToScVal(p: UpkeepPolicy): xdr.ScVal;
190
+ export declare function storagePlanToScVal(p: StoragePlan): xdr.ScVal;
191
+ export declare function upkeepQuoteToScVal(q: UpkeepQuote): xdr.ScVal;
192
+ export declare function executionQuoteToScVal(q: ExecutionQuote): xdr.ScVal;
@@ -277,3 +277,164 @@ export const FUNDING_ERRORS = {
277
277
  30: "Name state changed",
278
278
  31: "Upgrade does not match",
279
279
  };
280
+ const UPKEEP_POLICY = model({
281
+ relayer: G,
282
+ renewalCap: MONEY,
283
+ dailyCap: MONEY,
284
+ totalCap: MONEY,
285
+ retention: U32,
286
+ renewBefore: U32,
287
+ quoteLifetime: U32,
288
+ validUntil: U64,
289
+ });
290
+ const EXECUTION_POLICY = model({ wallet: G, limits: UPKEEP_POLICY, serviceFeeCap: MONEY, termsVersion: U32, serviceFeeBps: U32, payFailedFees: BOOL });
291
+ const EXECUTION_APPROVAL = model({ policy: EXECUTION_POLICY, depositedTotal: MONEY, depositNonce: U64 });
292
+ const EXECUTION_RECEIPT = model({ quoteHash: HEX, quoteId: HEX, nonce: U64, ledger: U32, coveredUntil: U32 });
293
+ const EXECUTION_WITHDRAWAL = model({ controller: ID, recipient: G, amount: MONEY, maxNetworkFee: MONEY, nonce: U64 });
294
+ function validateExecutionPolicy(p) {
295
+ upkeepPolicyToScVal(p.limits);
296
+ if (p.termsVersion !== 2 || p.serviceFeeBps !== 500 || p.payFailedFees !== true || p.wallet !== p.limits.relayer ||
297
+ p.serviceFeeCap < 0n || p.serviceFeeCap > p.limits.renewalCap)
298
+ throw new FundingError("Explicit prepaid custody, failed fees and capped 5% approval are required");
299
+ }
300
+ export function executionPolicyToScVal(p) {
301
+ const encoded = EXECUTION_POLICY.encode(p);
302
+ validateExecutionPolicy(p);
303
+ return encoded;
304
+ }
305
+ export function executionApprovalFromNative(value) {
306
+ const a = EXECUTION_APPROVAL.decode(value);
307
+ validateExecutionPolicy(a.policy);
308
+ return a;
309
+ }
310
+ export const executionReceiptFromNative = (value) => EXECUTION_RECEIPT.decode(value);
311
+ export const executionReceiptToScVal = (value) => EXECUTION_RECEIPT.encode(value);
312
+ export const executionWithdrawalFromNative = (value) => EXECUTION_WITHDRAWAL.decode(value);
313
+ const UPKEEP_RECEIPT = model({
314
+ quoteHash: HEX,
315
+ quoteId: HEX,
316
+ nonce: U64,
317
+ charge: MONEY,
318
+ balance: MONEY,
319
+ ledger: U32,
320
+ coveredUntil: U32,
321
+ });
322
+ const UPKEEP_LAST = {
323
+ encode(v) {
324
+ return xdr.ScVal.scvVec(v === null
325
+ ? [sc.symbol("Empty")]
326
+ : [sc.symbol("Recorded"), UPKEEP_RECEIPT.encode(v)]);
327
+ },
328
+ decode(v) {
329
+ if (Array.isArray(v) && v.length === 1 && v[0] === "Empty")
330
+ return null;
331
+ if (Array.isArray(v) && v.length === 2 && v[0] === "Recorded")
332
+ return UPKEEP_RECEIPT.decode(v[1]);
333
+ throw new FundingError("Malformed upkeep receipt");
334
+ },
335
+ };
336
+ const UPKEEP_POSITION = model({
337
+ namespace: HEX,
338
+ ownerEpoch: U64,
339
+ controller: ID,
340
+ balance: MONEY,
341
+ spentTotal: MONEY,
342
+ utcDay: U64,
343
+ spentToday: MONEY,
344
+ policyRevision: U64,
345
+ status: enumeration({ paused: "Paused", active: "Active", closed: "Closed" }),
346
+ nonce: U64,
347
+ lastReceipt: UPKEEP_LAST,
348
+ });
349
+ export const upkeepPositionFromNative = (v) => UPKEEP_POSITION.decode(v);
350
+ export const upkeepPolicyFromNative = (v) => UPKEEP_POLICY.decode(v);
351
+ export const upkeepReceiptToScVal = (v) => UPKEEP_RECEIPT.encode(v);
352
+ export const upkeepReceiptFromNative = (v) => UPKEEP_RECEIPT.decode(v);
353
+ export function upkeepPositionId(namespace, epoch) {
354
+ return hex(hash(xdr.ScVal.scvVec([
355
+ sc.symbol("upkeep_v1"),
356
+ HEX.encode(namespace),
357
+ U64.encode(epoch),
358
+ ]).toXDR()));
359
+ }
360
+ export function upkeepPolicyToScVal(p) {
361
+ const encoded = UPKEEP_POLICY.encode(p);
362
+ if (p.renewalCap <= 0n ||
363
+ p.dailyCap < p.renewalCap ||
364
+ p.totalCap < p.dailyCap ||
365
+ p.retention < 1 ||
366
+ p.renewBefore < 1 ||
367
+ p.renewBefore >= p.retention ||
368
+ p.quoteLifetime < 1 ||
369
+ p.quoteLifetime > 120 ||
370
+ p.validUntil <= 0n)
371
+ throw new FundingError("Invalid upkeep spending limits or retention");
372
+ return encoded;
373
+ }
374
+ export function storagePlanToScVal(p) {
375
+ exactObject(p, ["registrar", "resolver", "registrarKeys", "resolverKeys"], "storage plan");
376
+ const keys = (v) => {
377
+ if (!Array.isArray(v) || v.length > 32)
378
+ throw new FundingError("At most 32 storage keys per contract");
379
+ const seen = new Set();
380
+ for (const key of v) {
381
+ if (!(key instanceof xdr.ScVal))
382
+ throw new FundingError("Storage keys must be canonical ScVal values");
383
+ const raw = key.toXDR("base64");
384
+ if (seen.has(raw))
385
+ throw new FundingError("Duplicate storage key");
386
+ seen.add(raw);
387
+ }
388
+ return xdr.ScVal.scvVec(v);
389
+ };
390
+ const encoded = struct({
391
+ registrar: C.encode(p.registrar),
392
+ resolver: C.encode(p.resolver),
393
+ registrar_keys: keys(p.registrarKeys),
394
+ resolver_keys: keys(p.resolverKeys),
395
+ });
396
+ if (p.registrar === p.resolver || encoded.toXDR().length > 16384)
397
+ throw new FundingError("Invalid storage plan routes or size");
398
+ return encoded;
399
+ }
400
+ export function upkeepQuoteToScVal(q) {
401
+ exactObject(q, [
402
+ "network",
403
+ "funding",
404
+ "configRevision",
405
+ "fundId",
406
+ "policyRevision",
407
+ "nonce",
408
+ "quoteId",
409
+ "plan",
410
+ "charge",
411
+ "issuedLedger",
412
+ "expiresLedger",
413
+ ], "upkeep quote");
414
+ if (q.charge <= 0n ||
415
+ q.expiresLedger <= q.issuedLedger ||
416
+ q.expiresLedger - q.issuedLedger > 120)
417
+ throw new FundingError("Invalid upkeep quote charge or lifetime");
418
+ return struct({
419
+ network: HEX.encode(q.network),
420
+ funding: C.encode(q.funding),
421
+ config_revision: U64.encode(q.configRevision),
422
+ fund_id: HEX.encode(q.fundId),
423
+ policy_revision: U64.encode(q.policyRevision),
424
+ nonce: U64.encode(q.nonce),
425
+ quote_id: HEX.encode(q.quoteId),
426
+ plan: storagePlanToScVal(q.plan),
427
+ charge: MONEY.encode(q.charge),
428
+ issued_ledger: U32.encode(q.issuedLedger),
429
+ expires_ledger: U32.encode(q.expiresLedger),
430
+ });
431
+ }
432
+ export function executionQuoteToScVal(q) {
433
+ exactObject(q, ["network", "funding", "configRevision", "fundId", "policyRevision", "nonce", "quoteId", "plan", "maxNetworkFee", "issuedLedger", "expiresLedger"], "prepaid execution quote");
434
+ if (q.maxNetworkFee <= 0n || q.expiresLedger <= q.issuedLedger || q.expiresLedger - q.issuedLedger > 120)
435
+ throw new FundingError("Invalid prepaid execution budget or lifetime");
436
+ return struct({ network: HEX.encode(q.network), funding: C.encode(q.funding), config_revision: U64.encode(q.configRevision),
437
+ fund_id: HEX.encode(q.fundId), policy_revision: U64.encode(q.policyRevision), nonce: U64.encode(q.nonce),
438
+ quote_id: HEX.encode(q.quoteId), plan: storagePlanToScVal(q.plan), max_network_fee: MONEY.encode(q.maxNetworkFee),
439
+ issued_ledger: U32.encode(q.issuedLedger), expires_ledger: U32.encode(q.expiresLedger) });
440
+ }
@@ -1,6 +1,6 @@
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
- import { decodeMuxedAddress, paymentMemoToScVal, validatePaymentDestination } from "./payment.js";
3
+ import { isMuxedPaymentAddress, decodeMuxedAddress, paymentMemoToScVal, validatePaymentDestination } from "./payment.js";
4
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;
@@ -86,7 +86,7 @@ export function struct(fields) {
86
86
  }
87
87
  export function paymentDestinationToScVal(raw) {
88
88
  const payment = validatePaymentDestination(raw);
89
- if (StrKey.isValidMed25519PublicKey(payment.address)) {
89
+ if (isMuxedPaymentAddress(payment.address)) {
90
90
  const value = decodeMuxedAddress(payment.address);
91
91
  return xdr.ScVal.scvVec([sc.symbol("Muxed"), struct({ account: sc.address(value.account), id: sc.u64(BigInt(value.id)) })]);
92
92
  }
@@ -54,6 +54,8 @@ export type NativeWriteOptions = {
54
54
  /** Persist the public recovery reference before wallet signing/broadcast. Rejecting stops submission. */
55
55
  onPrepared?: (prepared: NativePrepared) => void | Promise<void>;
56
56
  };
57
+ /** Protocol 29 can fold restoration into an otherwise successful simulation. */
58
+ export declare function refuseNativeRestoration(tx: Transaction): void;
57
59
  export declare function prepareNative(context: NativeContext, plan: NativeAuthorizationPlan, options?: NativeWriteOptions): Promise<{
58
60
  transaction: Transaction;
59
61
  prepared: NativePrepared;
@@ -66,6 +66,15 @@ export async function requireNative(context, namespace) {
66
66
  throw new NativeClaimError(`namespace has no supported native claim interface (${capability.reason})`, "unsupported");
67
67
  return capability;
68
68
  }
69
+ /** Protocol 29 can fold restoration into an otherwise successful simulation. */
70
+ export function refuseNativeRestoration(tx) {
71
+ const envelope = tx.toEnvelope();
72
+ if (envelope.type !== "envelopeTypeTx" || envelope.value.tx.ext.type !== "sorobanData")
73
+ return;
74
+ const ext = envelope.value.tx.ext.value.ext;
75
+ if (ext.type !== "v0" && (ext.type !== "resourceExt" || ext.value.archivedSorobanEntries.length > 0))
76
+ throw new NativeClaimError("This operation would restore archived storage. Review restoration separately before continuing.", "unavailable");
77
+ }
69
78
  export async function prepareNative(context, plan, options = {}) {
70
79
  const source = address(await context.signer.publicKey(), "account", "transaction signer");
71
80
  if (source !== plan.source)
@@ -97,6 +106,7 @@ export async function prepareNative(context, plan, options = {}) {
97
106
  const auth = (op.auth ?? []).map(entry => entry.credentials.type === "sorobanCredentialsSourceAccount" ? entry : signed);
98
107
  transaction = TransactionBuilder.cloneFrom(transaction, { networkPassphrase: context.passphrase }).clearOperations().addOperation(Operation.invokeHostFunction({ func: op.func, auth })).build();
99
108
  transaction = rpc.assembleTransaction(transaction, await simulate(transaction)).build();
109
+ refuseNativeRestoration(transaction);
100
110
  validateNativeTransaction(transaction, plan);
101
111
  }
102
112
  const op = transaction.operations[0];
@@ -23,12 +23,12 @@ export type ContractUpgradeStatus = {
23
23
  */
24
24
  export declare const CONTRACT_RELEASES: {
25
25
  readonly registrar: {
26
- readonly latest: "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1";
27
- readonly superseded: readonly ["42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420", "5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c", "61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5", "26e03cd1d7fbfa46fe442406fb1b88022ec8cd8bd9123bed570f971fce596a14"];
26
+ readonly latest: "13195f1ed62cee15a30fab0d69da09f8537f2c5b62dfb9bbe70d9f3a5a90e498";
27
+ readonly superseded: readonly ["a7b618ac95f5f0b76d4489a9e88d1ce4691611b0d7c53d2d2f8b74b7526e284a", "8e84a35643c6e9c54d161c44102d229e18b50ad16394afea6d73031e21188ede", "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1", "42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420", "5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c", "61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5", "26e03cd1d7fbfa46fe442406fb1b88022ec8cd8bd9123bed570f971fce596a14"];
28
28
  };
29
29
  readonly resolver: {
30
- readonly latest: "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806";
31
- readonly superseded: readonly ["cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48", "351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea", "c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71", "5e7b4fb8d310d67ed6966a155e748d60be771422b3e8c34bbc14485f98525574"];
30
+ readonly latest: "a77bb9ef08d383691092c71ce6291e2f5ab600320c5afd5686713cc54cbfd95c";
31
+ readonly superseded: readonly ["4ab266de682a38208464e74d52692829ac344ece14f48c4249c3455e6b62a27b", "743559d763d81bf2debe8b70650a2a6dc50d6f5da4cbc0aeccedd3f5559442a7", "218cb1f7f90bd21f81ad4fe8b86f66478c3804b7355257cdc7e37d55d7edd5fb", "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806", "cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48", "351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea", "c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71", "5e7b4fb8d310d67ed6966a155e748d60be771422b3e8c34bbc14485f98525574"];
32
32
  };
33
33
  };
34
34
  /**
@@ -11,8 +11,11 @@ import { expectedNativeCode } from "./native-code-policy.js";
11
11
  */
12
12
  export const CONTRACT_RELEASES = {
13
13
  registrar: {
14
- latest: "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1",
14
+ latest: "13195f1ed62cee15a30fab0d69da09f8537f2c5b62dfb9bbe70d9f3a5a90e498",
15
15
  superseded: [
16
+ "a7b618ac95f5f0b76d4489a9e88d1ce4691611b0d7c53d2d2f8b74b7526e284a",
17
+ "8e84a35643c6e9c54d161c44102d229e18b50ad16394afea6d73031e21188ede",
18
+ "4d6d18625f4d890028acb794b24871c5c93f96dc15d94cf3d52cbf9713b07fb1",
16
19
  "42dc5f8b40932617d3f2667e1d26dad3e5e9a62c63de1fde7c016cb67a86c420",
17
20
  "5c8ae72ea7370ccf19af52b9bbc0007362be94464651ccdf68a2bb1f478b6f2c",
18
21
  "61ded153e6311075c1087c5639a5410222e23f7cade239ed2c946150af94b1c5",
@@ -20,8 +23,12 @@ export const CONTRACT_RELEASES = {
20
23
  ],
21
24
  },
22
25
  resolver: {
23
- latest: "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806",
26
+ latest: "a77bb9ef08d383691092c71ce6291e2f5ab600320c5afd5686713cc54cbfd95c",
24
27
  superseded: [
28
+ "4ab266de682a38208464e74d52692829ac344ece14f48c4249c3455e6b62a27b",
29
+ "743559d763d81bf2debe8b70650a2a6dc50d6f5da4cbc0aeccedd3f5559442a7",
30
+ "218cb1f7f90bd21f81ad4fe8b86f66478c3804b7355257cdc7e37d55d7edd5fb",
31
+ "941bf87f26cb817f3c6a52a326de855cca8a1dc4477e62a38ec4ffa5c7f43806",
25
32
  "cb927ffa19d6b1f3f0b0187048d885e200adb6de3fddd11ee3cfeb59df9c5c48",
26
33
  "351bf4e056b179b578fe2a4ea46f937bfafccc6538c7357ea70021b7038d91ea",
27
34
  "c6dfcd7bbf35943a13406888678334d0938d0fea54939906b00c2dc37b4a9a71",
package/dist/payment.d.ts CHANGED
@@ -18,6 +18,8 @@ export type PaymentDestination = {
18
18
  address: string;
19
19
  memo: PaymentMemo;
20
20
  };
21
+ /** M and W are destinations, never standalone signing identities. */
22
+ export declare function isMuxedPaymentAddress(address: string): boolean;
21
23
  /** Reject invalid data without normalizing meaningful memo bytes. */
22
24
  export declare function validatePaymentDestination(value: unknown): PaymentDestination;
23
25
  export declare function encodePaymentRecord(value: PaymentDestination): string;
@@ -31,7 +33,7 @@ export declare function decodeMuxedAddress(address: string): {
31
33
  account: string;
32
34
  id: string;
33
35
  };
34
- /** Encode only canonical G + exact decimal u64; numbers are never accepted. */
36
+ /** Encode a G or C base and exact decimal u64; numbers are never accepted. */
35
37
  export declare function encodeMuxedAddress(account: string, id: string): string;
36
38
  /** Strict additive on-chain destination enum; v1 payment structs stay separate. */
37
39
  export declare function destinationFromNative(raw: unknown): PaymentDestination;
package/dist/payment.js CHANGED
@@ -4,6 +4,10 @@ import { StrKey, nativeToScVal, xdr } from "@stellar/stellar-sdk";
4
4
  export const PAYMENT_RECORD_KEY = "payment";
5
5
  const MAX_U64 = 18446744073709551615n;
6
6
  const utf8 = (value) => new TextEncoder().encode(value);
7
+ /** M and W are destinations, never standalone signing identities. */
8
+ export function isMuxedPaymentAddress(address) {
9
+ return StrKey.isValidMed25519PublicKey(address) || StrKey.isValidMuxedContract(address);
10
+ }
7
11
  /** Reject invalid data without normalizing meaningful memo bytes. */
8
12
  export function validatePaymentDestination(value) {
9
13
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -11,15 +15,15 @@ export function validatePaymentDestination(value) {
11
15
  const p = value;
12
16
  if (Object.keys(p).sort().join(",") !== "address,memo")
13
17
  throw new Error("unexpected payment fields");
14
- if (typeof p.address !== "string" || (!StrKey.isValidEd25519PublicKey(p.address) && !StrKey.isValidContract(p.address) && !StrKey.isValidMed25519PublicKey(p.address)))
15
- throw new Error("payment address must be a valid G, C or M address");
18
+ if (typeof p.address !== "string" || (!StrKey.isValidEd25519PublicKey(p.address) && !StrKey.isValidContract(p.address) && !isMuxedPaymentAddress(p.address)))
19
+ throw new Error("payment address must be a valid G, C, M or W address");
16
20
  if (!p.memo || typeof p.memo !== "object" || Array.isArray(p.memo))
17
21
  throw new Error("invalid memo");
18
22
  const m = p.memo;
19
23
  if (m.type === "none") {
20
24
  if (Object.keys(m).join(",") !== "type")
21
25
  throw new Error("none memo cannot have a value");
22
- if (StrKey.isValidMed25519PublicKey(p.address))
26
+ if (isMuxedPaymentAddress(p.address))
23
27
  decodeMuxedAddress(p.address);
24
28
  return { address: p.address, memo: { type: "none" } };
25
29
  }
@@ -47,7 +51,7 @@ export function validatePaymentDestination(value) {
47
51
  }
48
52
  export function encodePaymentRecord(value) {
49
53
  const p = validatePaymentDestination(value);
50
- if (StrKey.isValidMed25519PublicKey(p.address)) {
54
+ if (isMuxedPaymentAddress(p.address)) {
51
55
  const muxed = decodeMuxedAddress(p.address);
52
56
  return `2|${muxed.account}|muxed|${muxed.id}`;
53
57
  }
@@ -108,30 +112,32 @@ export function paymentFromNative(raw) {
108
112
  }
109
113
  /** Decode a full muxed destination without treating its operation ID as a memo. */
110
114
  export function decodeMuxedAddress(address) {
111
- if (typeof address !== "string" || !StrKey.isValidMed25519PublicKey(address))
112
- throw new Error("invalid muxed M address");
113
- const raw = StrKey.decodeMed25519PublicKey(address);
114
- if (raw.length !== 40 || StrKey.encodeMed25519PublicKey(raw) !== address)
115
+ if (typeof address !== "string" || !isMuxedPaymentAddress(address))
116
+ throw new Error("invalid muxed M or W address");
117
+ const contract = StrKey.isValidMuxedContract(address);
118
+ const raw = contract ? StrKey.decodeMuxedContract(address) : StrKey.decodeMed25519PublicKey(address);
119
+ if (raw.length !== 40 || (contract ? StrKey.encodeMuxedContract(raw) : StrKey.encodeMed25519PublicKey(raw)) !== address)
115
120
  throw new Error("noncanonical muxed address");
116
121
  let id = 0n;
117
122
  for (const byte of raw.subarray(32))
118
123
  id = (id << 8n) | BigInt(byte);
119
- return { account: StrKey.encodeEd25519PublicKey(raw.subarray(0, 32)), id: id.toString() };
124
+ return { account: contract ? StrKey.encodeContract(raw.subarray(0, 32)) : StrKey.encodeEd25519PublicKey(raw.subarray(0, 32)), id: id.toString() };
120
125
  }
121
- /** Encode only canonical G + exact decimal u64; numbers are never accepted. */
126
+ /** Encode a G or C base and exact decimal u64; numbers are never accepted. */
122
127
  export function encodeMuxedAddress(account, id) {
123
- if (typeof account !== "string" || !StrKey.isValidEd25519PublicKey(account))
124
- throw new Error("muxed base must be a G account");
128
+ if (typeof account !== "string" || (!StrKey.isValidEd25519PublicKey(account) && !StrKey.isValidContract(account)))
129
+ throw new Error("muxed base must be a G account or C contract");
125
130
  if (typeof id !== "string" || !/^(0|[1-9][0-9]{0,19})$/.test(id) || BigInt(id) > MAX_U64)
126
131
  throw new Error("muxed ID must be canonical decimal u64");
127
132
  const raw = new Uint8Array(40);
128
- raw.set(StrKey.decodeEd25519PublicKey(account));
133
+ const contract = StrKey.isValidContract(account);
134
+ raw.set(contract ? StrKey.decodeContract(account) : StrKey.decodeEd25519PublicKey(account));
129
135
  let value = BigInt(id);
130
136
  for (let i = 39; i >= 32; i--) {
131
137
  raw[i] = Number(value & 255n);
132
138
  value >>= 8n;
133
139
  }
134
- return StrKey.encodeMed25519PublicKey(raw);
140
+ return contract ? StrKey.encodeMuxedContract(raw) : StrKey.encodeMed25519PublicKey(raw);
135
141
  }
136
142
  /** Strict additive on-chain destination enum; v1 payment structs stay separate. */
137
143
  export function destinationFromNative(raw) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorandomains/owner",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Run your Soran namespace on Stellar: configure claims, issue names and manage namespace policies.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -16,7 +16,7 @@
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/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",
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 test/funding-upkeep.test.mts test/funding-prepaid.test.mts && node --import tsx --test test/multichain.test.mts && node --import tsx --test test/subnames.test.mts",
20
20
  "test:hardening": "node --import tsx --test test/hardening.test.mts"
21
21
  },
22
22
  "license": "MIT",
@@ -38,10 +38,10 @@
38
38
  "issuance"
39
39
  ],
40
40
  "peerDependencies": {
41
- "@stellar/stellar-sdk": ">=17 <18"
41
+ "@stellar/stellar-sdk": ">=17.4.0 <18"
42
42
  },
43
43
  "devDependencies": {
44
- "@stellar/stellar-sdk": "^17.0.1",
44
+ "@stellar/stellar-sdk": "17.4.0",
45
45
  "@types/node": "^26.6.3",
46
46
  "esbuild": "^0.28.2",
47
47
  "tsx": "^4.19.2",