@sorandomains/owner 0.9.0 → 0.10.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
@@ -91,7 +91,7 @@ Read the [native claim APIs, security boundaries and complete signup flow](https
91
91
 
92
92
  ## Verified testnet deployment
93
93
 
94
- See the [deployment manifest](../../deployments/testnet.json) for confirmed code hashes, transaction receipts and verification scope. Network passphrase: `Test SDF Network ; September 2015`.
94
+ See the [deployment manifest](../../deploy/testnet/deployment.json) for confirmed code hashes, transaction receipts and verification scope. Network passphrase: `Test SDF Network ; September 2015`.
95
95
 
96
96
  | Contract | Address |
97
97
  |---|---|
@@ -121,22 +121,3 @@ The older `maxNativeFeeStroops` option remains supported: when the new option is
121
121
  On a governed Registry, native verification reads the exact per-namespace Registrar code pin and its upgrade history. Approved upgrades do not have to match the current factory default. Missing or malformed provenance still prevents signing and receipt confirmation; RPC failure never downgrades verification to a legacy rule.
122
122
 
123
123
  Historical claim recovery is read-only and binds the original intent to the frozen source Registrar and sealed migration commitments. It never rewrites the intent to a successor Registry or treats an unavailable receipt as permission to submit again. Deployment migration and package publication are separate; check the release status for the active addresses.
124
-
125
-
126
- ## Namespace sponsorship
127
-
128
- This package supports on-chain funding and sponsored actions on Stellar testnet.
129
- `SoranFunding` manages an owner's deposit, spending limits, pause controls and withdrawals.
130
- `SoranSponsorship` builds and checks an exact sponsored action, while `FundingServiceClient`
131
- requests quotes and recovers transaction outcomes from a compatible service.
132
-
133
- The Soran service uses fixed quotes based on live Stellar fee estimates. A successful action
134
- charges the agreed amount; Soran retains any difference from the actual network fee.
135
- There is no separate refund transaction or later debit. Failed actions do not debit the
136
- namespace's funding position. A separate username price remains payable by the claimant.
137
-
138
- User authorization remains required. A wallet must support Soroban authorization-entry
139
- signing; a transaction-only wallet cannot silently switch to a user-paid transaction.
140
- Always preserve the canonical quote before submission and recover its status after a timeout.
141
- Funding balances and permissions are verified on chain, and terminal outcomes are checked
142
- against trusted Stellar RPC/history providers.
@@ -0,0 +1,22 @@
1
+ import { xdr } from "@stellar/stellar-sdk";
2
+ /** The wallet defines its signature format and retains custody of its keys.
3
+ * Soran supplies the exact reviewed invocation, network, nonce and expiry.
4
+ * A separate G-account TxSigner submits the resulting transaction.
5
+ */
6
+ export type ContractWallet = {
7
+ address: string;
8
+ signAuthorization(request: {
9
+ address: string;
10
+ networkPassphrase: string;
11
+ authorizationEntryXdr: string;
12
+ signaturePayload: Uint8Array;
13
+ }): Promise<xdr.ScVal>;
14
+ };
15
+ export type ContractAuthorization = {
16
+ account: string;
17
+ latestLedger: number;
18
+ maxExpirationLedger: number;
19
+ };
20
+ export declare function validateContractAuthorization(entry: xdr.SorobanAuthorizationEntry, expected: ContractAuthorization, signed: boolean): void;
21
+ /** Invocation scope must be validated by the caller before calling this helper. */
22
+ export declare function signContractAuthorization(unsigned: xdr.SorobanAuthorizationEntry, expected: ContractAuthorization, wallet: ContractWallet, networkPassphrase: string): Promise<xdr.SorobanAuthorizationEntry>;
@@ -0,0 +1,55 @@
1
+ import { Address, buildAuthorizationEntryPreimage, hash, xdr } from "@stellar/stellar-sdk";
2
+ import { NativeClaimError, address, u32 } from "./native-codec.js";
3
+ export function validateContractAuthorization(entry, expected, signed) {
4
+ address(expected.account, "contract", "contract wallet");
5
+ u32(expected.latestLedger, "latest ledger");
6
+ u32(expected.maxExpirationLedger, "maximum authorization ledger");
7
+ if (expected.latestLedger < 1 || expected.maxExpirationLedger <= expected.latestLedger)
8
+ throw new NativeClaimError("contract authorization ledger window is invalid", "authorization");
9
+ if (entry.credentials.type !== "sorobanCredentialsAddress" ||
10
+ Address.fromScAddress(entry.credentials.address.address).toString() !== expected.account)
11
+ throw new NativeClaimError("authorization does not belong to the selected contract wallet", "authorization");
12
+ const c = entry.credentials.address;
13
+ if (signed) {
14
+ if (c.signatureExpirationLedger <= expected.latestLedger || c.signatureExpirationLedger > expected.maxExpirationLedger ||
15
+ c.signature.toXDR().length > 16384)
16
+ throw new NativeClaimError("contract authorization is expired or outside the reviewed limits", "authorization");
17
+ // A contract can legitimately accept any ScVal, including Void. Only an
18
+ // enforcing simulation and the host's __check_auth establish validity.
19
+ }
20
+ else if (c.signature.type !== "scvVoid" && !(c.signature.type === "scvVec" && !c.signature.vec?.length)) {
21
+ throw new NativeClaimError("refusing to sign an already signed contract authorization", "authorization");
22
+ }
23
+ }
24
+ /** Invocation scope must be validated by the caller before calling this helper. */
25
+ export async function signContractAuthorization(unsigned, expected, wallet, networkPassphrase) {
26
+ validateContractAuthorization(unsigned, expected, false);
27
+ if (wallet.address !== expected.account || !networkPassphrase)
28
+ throw new NativeClaimError("contract wallet or network differs from the reviewed authorization", "authorization");
29
+ // Round-trip before yielding to a wallet. Its callback receives no mutable
30
+ // invocation objects and can supply only the contract-defined signature.
31
+ const entry = xdr.SorobanAuthorizationEntry.fromXDR(unsigned.toXDR());
32
+ const c = entry.credentials;
33
+ if (c.type !== "sorobanCredentialsAddress")
34
+ throw new NativeClaimError("missing contract credential");
35
+ const preimage = buildAuthorizationEntryPreimage(entry, expected.maxExpirationLedger, networkPassphrase);
36
+ const credentials = new xdr.SorobanAddressCredentials({
37
+ ...c.address, signatureExpirationLedger: expected.maxExpirationLedger, signature: xdr.ScVal.scvVoid(),
38
+ });
39
+ const signingEntry = new xdr.SorobanAuthorizationEntry({
40
+ credentials: xdr.SorobanCredentials.sorobanCredentialsAddress(credentials), rootInvocation: entry.rootInvocation,
41
+ });
42
+ const signature = await wallet.signAuthorization({
43
+ address: expected.account, networkPassphrase, authorizationEntryXdr: signingEntry.toXDR("base64"),
44
+ signaturePayload: Uint8Array.from(hash(preimage.toXDR())),
45
+ });
46
+ if (wallet.address !== expected.account)
47
+ throw new NativeClaimError("contract wallet changed while authorizing", "authorization");
48
+ const signed = new xdr.SorobanAuthorizationEntry({
49
+ credentials: xdr.SorobanCredentials.sorobanCredentialsAddress(new xdr.SorobanAddressCredentials({
50
+ ...credentials, signature: xdr.ScVal.fromXDR(signature.toXDR()),
51
+ })), rootInvocation: entry.rootInvocation,
52
+ });
53
+ validateContractAuthorization(signed, expected, true);
54
+ return signed;
55
+ }
@@ -1,7 +1,7 @@
1
1
  import { type SponsorHistory } from "./funding-history.js";
2
2
  import { type ClaimIntent } from "./native-types.js";
3
3
  import { type SponsorQuote, type SponsoredAction, type SponsoredIdentityIntent } from "./funding-types.js";
4
- import { SoranSponsorship } from "./funding-user.js";
4
+ import { SoranSponsorship, type SponsorRequest } from "./funding-user.js";
5
5
  /** HTTP transports offers and status. The contract/RPC checks still authorize spending. */
6
6
  export declare class FundingServiceClient {
7
7
  private client;
@@ -18,6 +18,17 @@ export declare class FundingServiceClient {
18
18
  };
19
19
  transactionXdr: any;
20
20
  }>;
21
+ /** Re-estimate with the previous, valid wallet authorization. This never
22
+ * submits; review the new charge and call authorize again before submit. */
23
+ refreshQuote(request: SponsorRequest, authorizedTransactionXdr: string, maxSponsorCharge: bigint): Promise<{
24
+ request: {
25
+ quote: SponsorQuote;
26
+ intent: ClaimIntent | SponsoredIdentityIntent;
27
+ proof: readonly string[];
28
+ };
29
+ transactionXdr: any;
30
+ }>;
31
+ private reviewOffer;
21
32
  submit(quote: SponsorQuote, authorizedTransactionXdr: string): Promise<{
22
33
  id: string;
23
34
  status: "quoted" | "dispatching" | "pending" | "unknown" | "success" | "failed" | "expired";
@@ -58,6 +58,18 @@ export class FundingServiceClient {
58
58
  intentXdr: encoded.toXDR("base64"),
59
59
  proof,
60
60
  });
61
+ return this.reviewOffer(offer, action, intent, maxSponsorCharge, proof);
62
+ }
63
+ /** Re-estimate with the previous, valid wallet authorization. This never
64
+ * submits; review the new charge and call authorize again before submit. */
65
+ async refreshQuote(request, authorizedTransactionXdr, maxSponsorCharge) {
66
+ this.id(request.quote.quoteId);
67
+ const offer = await this.call("/v1/sponsorship/requote", {
68
+ quoteId: request.quote.quoteId, transactionXdr: authorizedTransactionXdr,
69
+ });
70
+ return this.reviewOffer(offer, request.quote.action, request.intent, maxSponsorCharge, request.proof ?? []);
71
+ }
72
+ async reviewOffer(offer, action, intent, maxSponsorCharge, proof) {
61
73
  const quote = decodeSponsorQuote(offer.quoteXdr);
62
74
  if (offer.id !== quote.quoteId ||
63
75
  quote.action !== action ||
@@ -1,5 +1,6 @@
1
1
  import { Transaction, authorizeEntry, xdr } from "@stellar/stellar-sdk";
2
2
  import { FundingReader } from "./funding-reader.js";
3
+ import { type ContractWallet } from "./contract-wallet.js";
3
4
  import { type SponsorQuote, type SponsoredIdentityIntent } from "./funding-types.js";
4
5
  import { type ClaimIntent } from "./native-types.js";
5
6
  import { type NativeInvocation } from "./native-auth.js";
@@ -33,6 +34,7 @@ export declare class SoranSponsorship extends FundingReader {
33
34
  plan: SponsorPlan;
34
35
  ledger: number;
35
36
  }>;
36
- /** Sign only the caller's G authorization. Wallet adapters retain custody of keys. */
37
- authorize(request: SponsorRequest, encoded: string, signer: Parameters<typeof authorizeEntry>[1], maxSponsorCharge: bigint, maxNetworkFee: bigint, eligibilityXdr?: string): Promise<string>;
37
+ /** Sign only the caller's authorization. C wallets supply their own signature
38
+ * format; the service enforces __check_auth before the relayer signs. */
39
+ authorize(request: SponsorRequest, encoded: string, signer: Parameters<typeof authorizeEntry>[1] | ContractWallet, maxSponsorCharge: bigint, maxNetworkFee: bigint, eligibilityXdr?: string): Promise<string>;
38
40
  }
@@ -1,5 +1,6 @@
1
1
  import { Address, Contract, Operation, Transaction, TransactionBuilder, authorizeEntry, rpc, nativeToScVal, xdr, } from "@stellar/stellar-sdk";
2
2
  import { FundingReader } from "./funding-reader.js";
3
+ import { signContractAuthorization } from "./contract-wallet.js";
3
4
  import { FundingError, sponsorIntentHash, sponsorQuoteToScVal, sponsoredIdentityToScVal, } from "./funding-types.js";
4
5
  import { claimIntentToScVal, claimLabelToScVal, claimQuoteFromNative, } from "./native-types.js";
5
6
  import { address, paymentDestinationToScVal, sc, unhex, } from "./native-codec.js";
@@ -186,6 +187,11 @@ export class SoranSponsorship extends FundingReader {
186
187
  const i = request.intent;
187
188
  if (i.context.registry !== this.registryId)
188
189
  throw new FundingError("Claim Registry differs from deployment");
190
+ if (i.claimant.startsWith("C")) {
191
+ const capability = await this.read(request.quote.registrar, "contract_claim_version", [], request.quote.issuedLedger);
192
+ if (capability.value !== 1)
193
+ throw new FundingError("This namespace needs a Registrar upgrade before contract wallets can claim", "unavailable");
194
+ }
189
195
  const r = await this.read(request.quote.registrar, "claim_quote", [claimLabelToScVal(i.label), sc.address(i.claimant)], request.quote.issuedLedger);
190
196
  const admission = claimQuoteFromNative(r.value).config?.settings
191
197
  .admission;
@@ -211,17 +217,23 @@ export class SoranSponsorship extends FundingReader {
211
217
  validateSponsorTransaction(transaction, plan, sim.latestLedger);
212
218
  return { transaction, plan, ledger: sim.latestLedger };
213
219
  }
214
- /** Sign only the caller's G authorization. Wallet adapters retain custody of keys. */
220
+ /** Sign only the caller's authorization. C wallets supply their own signature
221
+ * format; the service enforces __check_auth before the relayer signs. */
215
222
  async authorize(request, encoded, signer, maxSponsorCharge, maxNetworkFee, eligibilityXdr) {
216
223
  if (request.quote.charge > maxSponsorCharge)
217
224
  throw new FundingError("Sponsor charge exceeds the reviewed amount");
218
- address(request.quote.actor, "account", "this adapter's signing wallet");
225
+ address(request.quote.actor, "identity", "signing wallet");
226
+ const contractWallet = typeof signer === "object" && "signAuthorization" in signer ? signer : undefined;
227
+ if (request.quote.actor.startsWith("C") !== !!contractWallet)
228
+ throw new FundingError("Use the selected wallet's G signer or C authorization adapter", "authorization");
219
229
  const plan = await this.plan(request, maxNetworkFee), parsed = TransactionBuilder.fromXDR(encoded, this.passphrase);
220
230
  if (!(parsed instanceof Transaction))
221
231
  throw new FundingError("Fee-bump envelopes are not supported by this transport");
222
232
  const latest = await this.server.getLatestLedger();
223
233
  const entry = validateSponsorTransaction(parsed, plan, latest.sequence);
224
- const signed = await authorizeEntry(entry, signer, request.quote.expiresLedger, this.passphrase);
234
+ const signed = contractWallet ? await signContractAuthorization(entry, {
235
+ account: request.quote.actor, latestLedger: latest.sequence, maxExpirationLedger: request.quote.expiresLedger,
236
+ }, contractWallet, this.passphrase) : await authorizeEntry(entry, signer, request.quote.expiresLedger, this.passphrase);
225
237
  const op = parsed.operations[0];
226
238
  if (op.type !== "invokeHostFunction")
227
239
  throw new FundingError("Unexpected operation");
package/dist/index.d.ts CHANGED
@@ -158,6 +158,10 @@ export declare class SoranOwner {
158
158
  claimPolicy(namespace: string): Promise<ClaimConfig | null>;
159
159
  nativeClaimFeeToken(namespace: string): Promise<string>;
160
160
  private nativeOwnerWrite;
161
+ /** Current implementation after validating the Registry's native binding. */
162
+ registrarCodeHash(namespace: string): Promise<string>;
163
+ /** Owner-authorized, Registry-approved update; never uses the legacy tainting path. */
164
+ upgradeRegistrar(namespace: string, wasmHash: string, options?: NativeWriteOptions): Promise<Submitted>;
161
165
  configureClaims(namespace: string, settings: ClaimSettings, options?: NativeWriteOptions): Promise<Submitted & {
162
166
  config: ClaimConfig;
163
167
  }>;
@@ -287,3 +291,4 @@ export { SoranFunding } from "./funding-owner.js";
287
291
  export { SoranSponsorship, sponsorPlan, validateSponsorTransaction, type SponsorRequest, type SponsorPlan } from "./funding-user.js";
288
292
  export { FundingServiceClient } from "./funding-service.js";
289
293
  export { TestnetSponsorHistory, type SponsorHistory, type FundingReceipt } from "./funding-history.js";
294
+ export type { ContractWallet } from "./contract-wallet.js";
package/dist/index.js CHANGED
@@ -401,6 +401,32 @@ export class SoranOwner {
401
401
  return sendNative(context, { source, contract: cap.registrar, method, args, sourceInvocation: { contract: cap.registrar, method, args }, maxFeeStroops: context.maxFeeStroops }, options);
402
402
  });
403
403
  }
404
+ /** Current implementation after validating the Registry's native binding. */
405
+ async registrarCodeHash(namespace) {
406
+ const cap = await requireNative(this.nativeContext(), normalizeLabel(namespace));
407
+ const instance = await this.server.getContractInstance(cap.registrar);
408
+ if (instance.executable.type !== "contractExecutableWasm")
409
+ throw new NativeClaimError("Registrar is not a Wasm contract", "unavailable");
410
+ return nativeHex(instance.executable.wasmHash.value);
411
+ }
412
+ /** Owner-authorized, Registry-approved update; never uses the legacy tainting path. */
413
+ async upgradeRegistrar(namespace, wasmHash, options = {}) {
414
+ if (!/^[a-f0-9]{64}$/.test(wasmHash))
415
+ throw new NativeClaimError("Registrar implementation hash must be lowercase hex32");
416
+ const bytes = Uint8Array.from(wasmHash.match(/../g), value => parseInt(value, 16));
417
+ const cap = await requireNative(this.nativeContext(), normalizeLabel(namespace));
418
+ const [governed, approved, current] = await Promise.all([
419
+ this.read(cap.registrar, "upgrade_policy_version", []),
420
+ this.read(this.registryId, "implementation_approved", [nativeSc.u32(0), nativeSc.bytes(bytes)]),
421
+ this.registrarCodeHash(namespace),
422
+ ]);
423
+ if (governed !== 1 || approved !== true)
424
+ throw new NativeClaimError("This Registrar upgrade is not approved by the Registry", "authorization");
425
+ if (current === wasmHash)
426
+ throw new NativeClaimError("This Registrar implementation is already installed");
427
+ const result = await this.nativeOwnerWrite(namespace, "upgrade", [nativeSc.bytes(bytes)], options);
428
+ return { hash: result.hash, ledger: result.ledger };
429
+ }
404
430
  async configureClaims(namespace, settings, options = {}) {
405
431
  const encoded = claimSettingsToScVal(settings);
406
432
  const cap = await requireNative(this.nativeContext(), normalizeLabel(namespace));
@@ -1,15 +1,15 @@
1
1
  import { hash, xdr } from "@stellar/stellar-sdk";
2
2
  import { NativeClaimError, address, hex, hex32, sc, unhex } from "./native-codec.js";
3
3
  export function claimAllowlistLeaf(context, account) {
4
- return hex(hash(xdr.ScVal.scvVec([sc.symbol("allow_v1"), sc.bytes(unhex(context.network)), sc.address(address(context.registry, "contract", "Registry")), sc.bytes(unhex(context.namespace)), sc.address(address(context.registrar, "contract", "Registrar")), sc.address(address(account, "account", "allowlisted claimant"))]).toXDR()));
4
+ return hex(hash(xdr.ScVal.scvVec([sc.symbol("allow_v1"), sc.bytes(unhex(context.network)), sc.address(address(context.registry, "contract", "Registry")), sc.bytes(unhex(context.namespace)), sc.address(address(context.registrar, "contract", "Registrar")), sc.address(address(account, "identity", "allowlisted claimant"))]).toXDR()));
5
5
  }
6
6
  export function claimAllowlistParent(left, right) { hex32(left, "left leaf"); hex32(right, "right leaf"); const [first, second] = left < right ? [left, right] : [right, left]; const bytes = new Uint8Array(65); bytes[0] = 1; bytes.set(unhex(first), 1); bytes.set(unhex(second), 33); return hex(hash(bytes)); }
7
7
  /** A complete list is required to generate a replacement root. Keep/distribute the returned proof bundle. */
8
8
  export function buildClaimAllowlist(context, accounts) {
9
9
  if (!Array.isArray(accounts) || accounts.length === 0 || accounts.length > 65536)
10
- throw new NativeClaimError("allowlist requires 1–65536 distinct G accounts");
10
+ throw new NativeClaimError("allowlist requires 1–65536 distinct G or C accounts");
11
11
  const unique = new Set();
12
- const entries = accounts.map(account => { address(account, "account", "allowlisted claimant"); if (unique.has(account))
12
+ const entries = accounts.map(account => { address(account, "identity", "allowlisted claimant"); if (unique.has(account))
13
13
  throw new NativeClaimError("duplicate allowlist account"); unique.add(account); return { account, leaf: claimAllowlistLeaf(context, account), proof: [] }; }).sort((a, b) => a.leaf.localeCompare(b.leaf));
14
14
  let layer = entries.map((entry, index) => ({ hash: entry.leaf, indices: [index] }));
15
15
  while (layer.length > 1) {
@@ -1,4 +1,5 @@
1
1
  import { Transaction, authorizeEntry, xdr } from "@stellar/stellar-sdk";
2
+ import { type ContractAuthorization } from "./contract-wallet.js";
2
3
  export type NativeInvocation = {
3
4
  contract: string;
4
5
  method: string;
@@ -8,6 +9,8 @@ export type NativeInvocation = {
8
9
  /** Derive this plan locally from a validated intent, never from a remote envelope. */
9
10
  export type NativeAuthorizationPlan = {
10
11
  source: string;
12
+ /** When present, this contract wallet authorizes the holder invocation. */
13
+ actor?: ContractAuthorization;
11
14
  contract: string;
12
15
  method: string;
13
16
  args: xdr.ScVal[];
@@ -27,5 +30,5 @@ export type EligibilitySigner = Parameters<typeof authorizeEntry>[1];
27
30
  /** Signs only the expected admission entry. This helper never signs a transaction. */
28
31
  export declare function signEligibilityAuthorization(unsignedEntry: xdr.SorobanAuthorizationEntry, expected: NonNullable<NativeAuthorizationPlan["eligibility"]>, signer: EligibilitySigner, expirationLedger: number, networkPassphrase: string): Promise<xdr.SorobanAuthorizationEntry>;
29
32
  /** Exact local intent and native role scope; does not relax ordinary SDK authorization. */
30
- export declare function validateNativeTransaction(tx: Transaction, plan: NativeAuthorizationPlan, requireEligibilitySigned?: boolean): void;
33
+ export declare function validateNativeTransaction(tx: Transaction, plan: NativeAuthorizationPlan, requireEligibilitySigned?: boolean, requireActorSigned?: boolean): void;
31
34
  export declare function assertSignedBodyUnchanged(prepared: Transaction, signedXdr: string, passphrase: string): Transaction;
@@ -1,5 +1,6 @@
1
1
  import { Address, Transaction, TransactionBuilder, authorizeEntry, xdr } from "@stellar/stellar-sdk";
2
2
  import { NativeClaimError, address, hex, u32 } from "./native-codec.js";
3
+ import { validateContractAuthorization } from "./contract-wallet.js";
3
4
  export function authorizedInvocation(value) {
4
5
  return new xdr.SorobanAuthorizedInvocation({
5
6
  function: xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(new xdr.InvokeContractArgs({
@@ -44,7 +45,7 @@ export async function signEligibilityAuthorization(unsignedEntry, expected, sign
44
45
  return signed;
45
46
  }
46
47
  /** Exact local intent and native role scope; does not relax ordinary SDK authorization. */
47
- export function validateNativeTransaction(tx, plan, requireEligibilitySigned = true) {
48
+ export function validateNativeTransaction(tx, plan, requireEligibilitySigned = true, requireActorSigned = true) {
48
49
  address(plan.source, "account", "claim transaction source");
49
50
  if (tx.source !== plan.source)
50
51
  throw new NativeClaimError(`native transaction source ${tx.source} differs from reviewed source ${plan.source}`, "authorization");
@@ -64,12 +65,21 @@ export function validateNativeTransaction(tx, plan, requireEligibilitySigned = t
64
65
  if (entries.length !== (plan.eligibility ? 2 : 1))
65
66
  throw new NativeClaimError("unexpected native authorization count", "authorization");
66
67
  const sources = entries.filter(entry => entry.credentials.type === "sorobanCredentialsSourceAccount");
67
- if (sources.length !== 1 || !sameInvocation(sources[0].rootInvocation, plan.sourceInvocation))
68
+ if (plan.actor) {
69
+ const actors = entries.filter(entry => entry.credentials.type === "sorobanCredentialsAddress" &&
70
+ Address.fromScAddress(entry.credentials.address.address).toString() === plan.actor.account);
71
+ if (sources.length || actors.length !== 1 || !sameInvocation(actors[0].rootInvocation, plan.sourceInvocation))
72
+ throw new NativeClaimError("contract wallet authorization differs from exact reviewed intent", "authorization");
73
+ validateContractAuthorization(actors[0], plan.actor, requireActorSigned);
74
+ }
75
+ else if (sources.length !== 1 || !sameInvocation(sources[0].rootInvocation, plan.sourceInvocation)) {
68
76
  throw new NativeClaimError("holder or owner authorization differs from exact reviewed intent", "authorization");
77
+ }
69
78
  if (plan.eligibility) {
70
- if (plan.eligibility.account === plan.source)
71
- throw new NativeClaimError("eligibility account must be separate from claimant", "authorization");
72
- const separate = entries.filter(entry => entry.credentials.type !== "sorobanCredentialsSourceAccount");
79
+ if (plan.eligibility.account === plan.source || plan.eligibility.account === plan.actor?.account)
80
+ throw new NativeClaimError("eligibility account must be separate from claimant and transaction source", "authorization");
81
+ const separate = entries.filter(entry => entry.credentials.type === "sorobanCredentialsAddress" &&
82
+ Address.fromScAddress(entry.credentials.address.address).toString() === plan.eligibility.account);
73
83
  if (separate.length !== 1)
74
84
  throw new NativeClaimError("unexpected second authorizer", "authorization");
75
85
  validateEligibilityAuthorization(separate[0], plan.eligibility, requireEligibilitySigned);
@@ -33,16 +33,16 @@ function model(fields) { return { encode: v => { const value = exactObject(v, Ob
33
33
  const SETTINGS = model({ mode: enumeration({ manual: "Manual", public: "Public" }), enabled: BOOL, admission: ADMISSION, feeToken: CONTRACT, feeAmount: AMOUNT, feeRecipient: ACCOUNT, walletLimit: U64, approvalAllowance: U64, approvalRateLimit: U64, approvalWindowSecs: U64, approvalTtlSecs: U64 });
34
34
  const CONFIG = model({ settings: SETTINGS, ownerEpoch: U64, policyVersion: U64, grantEpoch: U64 });
35
35
  const CONTEXT = model({ network: HEX, registry: CONTRACT, registrar: CONTRACT, namespace: HEX, requestId: HEX, validAfter: U64, deadline: U64 });
36
- const CLAIM = model({ context: CONTEXT, label: LABEL, claimant: ACCOUNT, destination: DESTINATION, resolver: CONTRACT, expectedGeneration: optional(U64), expectedExpiry: optional(U64), termSecs: U64, ownerEpoch: U64, policyVersion: U64, grantEpoch: U64, feeToken: CONTRACT, feeAmount: AMOUNT, feeRecipient: ACCOUNT });
37
- const RESERVED = model({ context: CONTEXT, label: LABEL, holder: ACCOUNT, destination: DESTINATION, resolver: CONTRACT, expectedGeneration: optional(U64), expectedExpiry: optional(U64), termSecs: U64, ownerEpoch: U64, policyVersion: U64 });
38
- const TRANSFER = model({ context: CONTEXT, label: LABEL, from: IDENTITY, to: ACCOUNT, expectedGeneration: U64, expectedExpiry: U64, proposalExpires: U64, destination: DESTINATION, resolver: CONTRACT });
39
- const RENEW = model({ context: CONTEXT, label: LABEL, holder: ACCOUNT, expectedGeneration: U64, expectedExpiry: U64, termSecs: U64, minNewExpiry: U64, maxNewExpiry: U64 });
36
+ const CLAIM = model({ context: CONTEXT, label: LABEL, claimant: IDENTITY, destination: DESTINATION, resolver: CONTRACT, expectedGeneration: optional(U64), expectedExpiry: optional(U64), termSecs: U64, ownerEpoch: U64, policyVersion: U64, grantEpoch: U64, feeToken: CONTRACT, feeAmount: AMOUNT, feeRecipient: ACCOUNT });
37
+ const RESERVED = model({ context: CONTEXT, label: LABEL, holder: IDENTITY, destination: DESTINATION, resolver: CONTRACT, expectedGeneration: optional(U64), expectedExpiry: optional(U64), termSecs: U64, ownerEpoch: U64, policyVersion: U64 });
38
+ const TRANSFER = model({ context: CONTEXT, label: LABEL, from: IDENTITY, to: IDENTITY, expectedGeneration: U64, expectedExpiry: U64, proposalExpires: U64, destination: DESTINATION, resolver: CONTRACT });
39
+ const RENEW = model({ context: CONTEXT, label: LABEL, holder: IDENTITY, expectedGeneration: U64, expectedExpiry: U64, termSecs: U64, minNewExpiry: U64, maxNewExpiry: U64 });
40
40
  const USAGE = model({ publicClaims: U64, reservedIssues: U64 }), APPROVAL_USAGE = model({ total: U64, windowUsed: U64, windowEnds: U64 });
41
41
  const CONFIG_STATE = { encode: v => v === null ? xdr.ScVal.scvVec([sc.symbol("Unconfigured")]) : xdr.ScVal.scvVec([sc.symbol("Configured"), CONFIG.encode(v)]), decode: v => { if (!Array.isArray(v))
42
42
  throw new NativeClaimError("invalid claim config state"); if (v.length === 1 && v[0] === "Unconfigured")
43
43
  return null; if (v.length === 2 && v[0] === "Configured")
44
44
  return CONFIG.decode(v[1]); throw new NativeClaimError("invalid claim config state"); } };
45
- const QUOTE = model({ network: HEX, registry: CONTRACT, registrar: CONTRACT, namespace: HEX, resolver: CONTRACT, label: LABEL, node: HEX, claimant: ACCOUNT, config: CONFIG_STATE, ownerEpoch: U64, policyVersion: U64, available: BOOL, reserved: BOOL, generation: optional(U64), expiresAt: optional(U64), termSecs: U64, now: U64, usage: USAGE, approvalUsage: APPROVAL_USAGE });
45
+ const QUOTE = model({ network: HEX, registry: CONTRACT, registrar: CONTRACT, namespace: HEX, resolver: CONTRACT, label: LABEL, node: HEX, claimant: IDENTITY, config: CONFIG_STATE, ownerEpoch: U64, policyVersion: U64, available: BOOL, reserved: BOOL, generation: optional(U64), expiresAt: optional(U64), termSecs: U64, now: U64, usage: USAGE, approvalUsage: APPROVAL_USAGE });
46
46
  const RECEIPT = model({ operation: enumeration({ claim: "Claim", reserved: "Reserved", transfer: "Transfer", renew: "Renew" }), intentHash: HEX, node: HEX, holder: IDENTITY, generation: U64, expiresAt: U64, feeAmount: AMOUNT, feeToken: CONTRACT, feeRecipient: optional(ACCOUNT), ledger: U32, timestamp: U64 });
47
47
  const PREVIEW = model({ node: HEX, holder: IDENTITY, generation: U64, expiresAt: U64, active: BOOL, destination: DESTINATION });
48
48
  export function claimSettingsToScVal(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorandomains/owner",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Run your Soran namespace on Stellar: configure claims, issue names and manage namespace policies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",