@universal-everything/sdk-core 1.1.0 → 1.2.0-dev.0a281f1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -139,6 +139,8 @@ type Profile = {
139
139
  status?: ProfileStatus;
140
140
  defaultControllerId: string | null;
141
141
  controllers: ControllerSummary[];
142
+ /** The owner-level deploy-time controller (lowercase) or `null` = unknown. */
143
+ generatingController: string | null;
142
144
  createdAt: string;
143
145
  };
144
146
  /**
@@ -157,6 +159,12 @@ type ChainStatus = {
157
159
  profileId: string;
158
160
  chainId: number;
159
161
  status: ProfileStatus;
162
+ /**
163
+ * The owner-level controller the LSP23 deploy granted (lowercase), or
164
+ * `null` = unknown. Clients compare it with their own keystore to tell a
165
+ * *created* profile from an *Imported* (secondary-key) one.
166
+ */
167
+ generatingController: string | null;
160
168
  label: string | null;
161
169
  defaultControllerId: string | null;
162
170
  controllers: ControllerSummary[];
@@ -172,8 +180,170 @@ type ChainStatus = {
172
180
  * `getProfileByAddress`. Address is the natural primary key once a UP
173
181
  * may live on multiple chains (handoff G1).
174
182
  */
183
+ /** A subscription currently covering an identity. */
184
+ type CoveredBy = {
185
+ subscriptionId: string;
186
+ /**
187
+ * `self` = owned by the caller's account. `other` (a foreign covering
188
+ * subscription) is reserved for R2's explicit membership — the server only
189
+ * emits `self` today.
190
+ */
191
+ owner: "self" | "other";
192
+ tier: string;
193
+ status: string;
194
+ source: string;
195
+ };
196
+ /**
197
+ * Server-side extend verdict — ADVISORY. `reason` reports the ownership and
198
+ * capacity gates the extend route enforces for this session; target-dependent
199
+ * gates (chain availability, tier chain allow-list, LSP23 package / factory /
200
+ * deployability) come from `extend-targets` and at submit, so `null` means
201
+ * "no ownership or capacity refusal", not "extend will succeed". Whether THIS
202
+ * device can extend (holds the generating key) is the client's call from
203
+ * `generatingController`.
204
+ */
205
+ type ExtendVerdictReason = "not_active" | "held_by_another_login" | "subscription_required" | "cross_chain_cap_reached" | "capacity_unknown";
206
+ type ExtendVerdict = {
207
+ /**
208
+ * Chains the identity is live on (active / pending), across every login of
209
+ * the holder account — NOT candidate targets; those come from
210
+ * `GET /v1/profiles/:id/extend-targets`, whose occupancy check is scoped to
211
+ * the calling login.
212
+ */
213
+ chains: number[];
214
+ reason: ExtendVerdictReason | null;
215
+ };
216
+ /** A store the relayer sells through. The tiers are the same in every store. */
217
+ type BillingStore = "stripe" | "apple" | "google";
218
+ /** One purchasable item of a plan in one store — see {@link RelayerClient.getCatalog}. */
219
+ type CatalogProduct = {
220
+ store: BillingStore;
221
+ /**
222
+ * The id the store knows the item by: the Stripe Price id, the App Store /
223
+ * Play product id. On Apple / Google, fetch the localized title and price
224
+ * from StoreKit / Play Billing with it — those are what the user must see.
225
+ */
226
+ externalProductId: string;
227
+ displayName: string;
228
+ /** Stripe: the charged amount. Apple / Google: a reference price only. */
229
+ amountCents: number | null;
230
+ currency: string | null;
231
+ /** Stripe only; always `false` / `0` for Apple / Google (attach order). */
232
+ isDefault: boolean;
233
+ sortOrder: number;
234
+ /** What the store needs besides the id (Play `basePlanId`); `{}` for Stripe. */
235
+ metadata: Record<string, unknown>;
236
+ productName: string | null;
237
+ productDescription: string | null;
238
+ };
239
+ /** The plan's default Stripe Price — see {@link CatalogPlan.defaultPrice}. */
240
+ type CatalogDefaultPrice = {
241
+ stripePriceId: string;
242
+ displayName: string;
243
+ amountCents: number | null;
244
+ currency: string | null;
245
+ stripeMode: "test" | "live";
246
+ productName: string | null;
247
+ productDescription: string | null;
248
+ };
249
+ /** One plan of `GET /v1/billing/catalog` — what it grants and what buys it. */
250
+ type CatalogPlan = {
251
+ /** `tier:<name>` for subscriptions, `pack:<name>` for one-time packs. */
252
+ key: string;
253
+ label: string;
254
+ description: string | null;
255
+ audience: "user" | "dev";
256
+ cadence: string;
257
+ /** What a store page groups by. `interval` is null for a one-time purchase. */
258
+ kind: "subscription" | "one_time";
259
+ interval: "day" | "month" | "year" | null;
260
+ stackable: boolean;
261
+ /** Only purchasable on top of a live subscription. */
262
+ requiresSubscription: boolean;
263
+ mainnetProfileCap: number;
264
+ testnetProfileCap: number;
265
+ crossChainDeployCap: number;
266
+ crossChainTestnetCap: number;
267
+ crossChainEnabledChains: number[];
268
+ overageEnabled: boolean;
269
+ overageCapCredits: string | null;
270
+ /** Gas budget per period in credits (1 credit = €1), decimal string. */
271
+ credits: string | null;
272
+ gasCreditsPerChain: Record<string, string>;
273
+ /**
274
+ * The default Stripe Price, whatever `store` was requested (null = none
275
+ * attached). Predates `products`; a store page reads `products`.
276
+ */
277
+ defaultPrice: CatalogDefaultPrice | null;
278
+ /** Active items for the requested store. */
279
+ products: CatalogProduct[];
280
+ };
281
+ type Catalog = {
282
+ audience: "user" | "dev";
283
+ store: BillingStore;
284
+ plans: CatalogPlan[];
285
+ };
286
+ /** One entry of `GET /v1/subscriptions` — a subscription of the caller's account. */
287
+ type SubscriptionSummary = {
288
+ subscriptionId: string;
289
+ /** Always `self` today; `other` (a subscription that only covers one of your profiles) arrives with explicit membership. */
290
+ owner: "self" | "other";
291
+ tier: string;
292
+ status: string;
293
+ /** `active` or `trialing`. */
294
+ live: boolean;
295
+ source: "stripe" | "apple" | "google" | "admin";
296
+ /** Bought in the store sandbox (TestFlight / App Review / sandbox tester) — no real money. */
297
+ sandbox: boolean;
298
+ currentPeriodEnd: string | null;
299
+ /** Where Manage goes: `stripe` ⇒ {@link RelayerClient.createPortalSession}; `apple` / `google` ⇒ the store; `admin` ⇒ nothing. */
300
+ manage: {
301
+ kind: "stripe" | "apple" | "google" | "admin";
302
+ };
303
+ };
304
+ /**
305
+ * Result of {@link RelayerClient.submitAppleTransaction} /
306
+ * {@link RelayerClient.claimSubscription}. `applied: false` with a `reason`
307
+ * is bookkeeping, not an error: `stale` (an older period than the relayer
308
+ * already has), `upgraded` (superseded — the upgrade's own transaction
309
+ * follows), `refund_of_past_period`, `refund_unknown_subscription`.
310
+ */
311
+ type StoreTransactionResult = {
312
+ applied: true;
313
+ status: "active" | "past_due" | "expired" | "refunded";
314
+ tier: string;
315
+ } | {
316
+ applied: false;
317
+ reason: string;
318
+ };
319
+ /** `GET /v1/me` — the authenticated login and its account context. */
320
+ type Me = {
321
+ subject: string;
322
+ isAdmin: boolean;
323
+ /** The login's personal account (null only before provisioning). */
324
+ accountId: string | null;
325
+ /** This login's own tier — what the cross-chain cap resolves from. */
326
+ tier: string;
327
+ /**
328
+ * The account's effective tier (best tier across its login subjects) —
329
+ * what gas coverage resolves from. Differs from `tier` only on legacy
330
+ * multi-login accounts.
331
+ */
332
+ accountTier: string;
333
+ };
175
334
  type ProfileWithChains = {
176
335
  address: Hex;
336
+ /**
337
+ * Identity-level generating controller: the value every chain row agrees
338
+ * on; `null` when unknown or when the rows disagree.
339
+ */
340
+ generatingController: string | null;
341
+ /**
342
+ * Present on the `/v1/profiles` mount; omitted on the sig-auth
343
+ * `/v1/relayer/profiles` mirror.
344
+ */
345
+ coveredBy?: CoveredBy[];
346
+ extend: ExtendVerdict;
177
347
  chains: ChainStatus[];
178
348
  };
179
349
  type QuotaSnapshot = {
@@ -313,6 +483,11 @@ type NonceResponse = {
313
483
  nonce: string;
314
484
  issuedAt: string;
315
485
  };
486
+ /** `POST /v1/ipfs/upload-token` — a short-lived bearer for the IPFS upload service. */
487
+ type IpfsUploadTokenResponse = {
488
+ token: string;
489
+ expiresAt: string;
490
+ };
316
491
  type DeployProfileBase = {
317
492
  chainId: number;
318
493
  label?: string | null;
@@ -331,10 +506,17 @@ type DeployProfileBase = {
331
506
  * Deploy a sponsored Universal Profile. Two mutually-exclusive controller
332
507
  * modes:
333
508
  * - **custody**: `controllerWalletId` — a relayer-held wallet row.
334
- * - **external (A1)**: `controllerAddress` + `controlProof` — a user-held key
335
- * (a Privy embedded wallet, or an extension/mobile key) that proves control
336
- * by signing a nonce-bound challenge. Build the proof with
509
+ * - **external (A1)**: `controllerAddress` (+ `controlProof`) — a user-held key
510
+ * (a Privy embedded wallet, or an extension/mobile key). The proof is a
511
+ * signature by that key over a nonce-bound challenge; build it with
337
512
  * `@universal-everything/sdk-core-viem`'s `buildControlProof`.
513
+ *
514
+ * On `/v1` the proof is **optional**: a first-time user has only a social
515
+ * login, and the extension / mobile app holding the new controller cannot sign
516
+ * for a not-yet-deployed profile. When present it is always verified. The next
517
+ * API version makes it mandatory for everyone (relayer
518
+ * `docs/DESIGN_2026-09-10_control-proof-versioning.md`), so callers that CAN
519
+ * sign (a Privy embedded wallet) should always send it.
338
520
  */
339
521
  type DeployProfileInput = (DeployProfileBase & {
340
522
  controllerWalletId: string;
@@ -342,7 +524,7 @@ type DeployProfileInput = (DeployProfileBase & {
342
524
  controlProof?: never;
343
525
  }) | (DeployProfileBase & {
344
526
  controllerAddress: Hex;
345
- controlProof: ControlProof;
527
+ controlProof?: ControlProof;
346
528
  controllerWalletId?: never;
347
529
  });
348
530
  type DeployProfileSingleChainResponse = {
@@ -447,10 +629,7 @@ declare class TierError extends WalletKitError {
447
629
  declare class WalletKitClient {
448
630
  #private;
449
631
  constructor(config: WalletKitClientConfig);
450
- me(): Promise<{
451
- subject: string;
452
- isAdmin: boolean;
453
- }>;
632
+ me(): Promise<Me>;
454
633
  listWallets(): Promise<Wallet[]>;
455
634
  getWallet(walletId: string): Promise<Wallet>;
456
635
  createWallet(input?: {
@@ -475,6 +654,14 @@ declare class WalletKitClient {
475
654
  * bind an A1 control proof for an external-controller {@link deployProfile}.
476
655
  */
477
656
  getNonce(): Promise<NonceResponse>;
657
+ /**
658
+ * Mint a short-lived (~120s) HS256 token for the IPFS upload service
659
+ * (`POST /v1/ipfs/upload-token`, authenticated). Fetch one per upload:
660
+ * the token is meant to expire within the service's 600s window (which the
661
+ * service does not enforce today — see the relayer's routes/ipfs.ts). 503
662
+ * when the relayer has no upload secret configured.
663
+ */
664
+ getIpfsUploadToken(): Promise<IpfsUploadTokenResponse>;
478
665
  importProfile(input: {
479
666
  address: Hex;
480
667
  chainId: number;
@@ -498,6 +685,56 @@ declare class WalletKitClient {
498
685
  * default (`source === null`) when they have no paid subscription.
499
686
  */
500
687
  getSubscription(): Promise<SubscriptionResponse>;
688
+ /**
689
+ * What can be bought, per store — what a store page renders. With `store`
690
+ * the list holds only the plans purchasable there plus the free tier;
691
+ * without it every active plan comes back with its Stripe products (empty
692
+ * = not purchasable yet). The layout is the caller's: group by `kind` /
693
+ * `interval`; Stripe products arrive in `sortOrder`.
694
+ */
695
+ getCatalog(opts?: {
696
+ audience?: "user" | "dev";
697
+ store?: BillingStore;
698
+ }): Promise<Catalog>;
699
+ /**
700
+ * Every subscription of the caller's account, live ones first — what the
701
+ * dashboard's account / subscription dropdown renders.
702
+ */
703
+ listSubscriptions(): Promise<{
704
+ subscriptions: SubscriptionSummary[];
705
+ }>;
706
+ /**
707
+ * Hand a StoreKit 2 purchase to the relayer (DEV-16570): pass the
708
+ * transaction's `jwsRepresentation` after a purchase and for every
709
+ * `Transaction.updates` entry. Purchase with `appAccountToken` = the
710
+ * caller's `accountId` from {@link RelayerClient.me}; a transaction bought
711
+ * for another account rejects with `403 app_account_token_mismatch`, one
712
+ * attached to another account with `409 held_by_another_account` — restore
713
+ * those with {@link RelayerClient.claimSubscription}. `422 unmapped_product`
714
+ * = the product id is not in the catalog.
715
+ */
716
+ submitAppleTransaction(signedTransaction: string): Promise<StoreTransactionResult>;
717
+ /**
718
+ * Restore purchases: move a store subscription onto the caller's account
719
+ * (single owner — the previous account loses it). Pass a signed transaction
720
+ * from `Transaction.currentEntitlements`; only a device signed in with the
721
+ * purchasing Apple ID can produce one. A subscription moves to a different
722
+ * account at most once per calendar month: `409 claim_cooldown` otherwise.
723
+ */
724
+ claimSubscription(input: {
725
+ store: "apple";
726
+ signedTransaction: string;
727
+ }): Promise<StoreTransactionResult>;
728
+ /**
729
+ * Stripe Customer Portal URL for the caller's own Stripe subscription.
730
+ * Rejects with `manage_in_store` (409) for an Apple / Google subscription
731
+ * and `no_subscription` (404) when there is nothing to manage.
732
+ */
733
+ createPortalSession(opts?: {
734
+ returnOrigin?: string;
735
+ }): Promise<{
736
+ url: string;
737
+ }>;
501
738
  /**
502
739
  * Start a Stripe Checkout for a tier and return the created session
503
740
  * `{ id, url }`. In a browser this ALSO navigates to `url` by default
@@ -539,4 +776,4 @@ declare class IdentityError extends Error {
539
776
  constructor(code: IdentityErrorCode, message: string);
540
777
  }
541
778
 
542
- export { type ChainStatus, type CheckoutSession, type ControlProof, type ControllerSummary, type DeployProfileInput, type DeployProfileMultiChainResponse, type DeployProfileResponse, type DeployProfileSingleChainResponse, type DeployQueueStatus, type ExportCompleteResponse, type ExportRequestInput, type ExportRequestResponse, type ExportedKey, type FreeDefaultSnapshot, type Hex, IdentityError, type IdentityErrorCode, type IdentityProvider, type ImportBeginResponse, type ImportCompleteInput, type ImportCompleteResponse, KeyCustodyError, type KeyCustodyErrorCode, type KeyCustodyOpContext, type KeyCustodyProvider, type NonceResponse, type OpContext, type OpenCheckoutInput, type Profile, type ProfileStatus, type ProfileWithChains, type QuotaSnapshot, type RelaySponsoredInput, type RelaySponsoredResponse, type SignLsp25Input, type SignLsp25Response, type SignMessageResponse, type SignTransactionInput, type SignTransactionResponse, type SignTxRequest, type Subject, type SubscriptionResponse, type SubscriptionSnapshot, TIER_ERROR_CODES, TierError, type VerifiedToken, type VerifyOptions, type Wallet, type WalletInfo, WalletKitClient, type WalletKitClientConfig, WalletKitError, createWalletKitClient };
779
+ export { type BillingStore, type Catalog, type CatalogDefaultPrice, type CatalogPlan, type CatalogProduct, type ChainStatus, type CheckoutSession, type ControlProof, type ControllerSummary, type CoveredBy, type DeployProfileInput, type DeployProfileMultiChainResponse, type DeployProfileResponse, type DeployProfileSingleChainResponse, type DeployQueueStatus, type ExportCompleteResponse, type ExportRequestInput, type ExportRequestResponse, type ExportedKey, type ExtendVerdict, type ExtendVerdictReason, type FreeDefaultSnapshot, type Hex, IdentityError, type IdentityErrorCode, type IdentityProvider, type ImportBeginResponse, type ImportCompleteInput, type ImportCompleteResponse, type IpfsUploadTokenResponse, KeyCustodyError, type KeyCustodyErrorCode, type KeyCustodyOpContext, type KeyCustodyProvider, type Me, type NonceResponse, type OpContext, type OpenCheckoutInput, type Profile, type ProfileStatus, type ProfileWithChains, type QuotaSnapshot, type RelaySponsoredInput, type RelaySponsoredResponse, type SignLsp25Input, type SignLsp25Response, type SignMessageResponse, type SignTransactionInput, type SignTransactionResponse, type SignTxRequest, type StoreTransactionResult, type Subject, type SubscriptionResponse, type SubscriptionSnapshot, type SubscriptionSummary, TIER_ERROR_CODES, TierError, type VerifiedToken, type VerifyOptions, type Wallet, type WalletInfo, WalletKitClient, type WalletKitClientConfig, WalletKitError, createWalletKitClient };
package/dist/index.js CHANGED
@@ -121,6 +121,16 @@ var WalletKitClient = class {
121
121
  async getNonce() {
122
122
  return this.#request("POST", "/v1/auth/nonce");
123
123
  }
124
+ /**
125
+ * Mint a short-lived (~120s) HS256 token for the IPFS upload service
126
+ * (`POST /v1/ipfs/upload-token`, authenticated). Fetch one per upload:
127
+ * the token is meant to expire within the service's 600s window (which the
128
+ * service does not enforce today — see the relayer's routes/ipfs.ts). 503
129
+ * when the relayer has no upload secret configured.
130
+ */
131
+ async getIpfsUploadToken() {
132
+ return this.#request("POST", "/v1/ipfs/upload-token");
133
+ }
124
134
  async importProfile(input) {
125
135
  return this.#request("POST", "/v1/profiles/import", input);
126
136
  }
@@ -160,6 +170,60 @@ var WalletKitClient = class {
160
170
  async getSubscription() {
161
171
  return this.#request("GET", "/v1/billing/subscription");
162
172
  }
173
+ /**
174
+ * What can be bought, per store — what a store page renders. With `store`
175
+ * the list holds only the plans purchasable there plus the free tier;
176
+ * without it every active plan comes back with its Stripe products (empty
177
+ * = not purchasable yet). The layout is the caller's: group by `kind` /
178
+ * `interval`; Stripe products arrive in `sortOrder`.
179
+ */
180
+ async getCatalog(opts) {
181
+ const query = new URLSearchParams();
182
+ if (opts?.audience) query.set("audience", opts.audience);
183
+ if (opts?.store) query.set("store", opts.store);
184
+ const qs = query.toString();
185
+ return this.#request("GET", `/v1/billing/catalog${qs ? `?${qs}` : ""}`);
186
+ }
187
+ /**
188
+ * Every subscription of the caller's account, live ones first — what the
189
+ * dashboard's account / subscription dropdown renders.
190
+ */
191
+ async listSubscriptions() {
192
+ return this.#request("GET", "/v1/subscriptions");
193
+ }
194
+ /**
195
+ * Hand a StoreKit 2 purchase to the relayer (DEV-16570): pass the
196
+ * transaction's `jwsRepresentation` after a purchase and for every
197
+ * `Transaction.updates` entry. Purchase with `appAccountToken` = the
198
+ * caller's `accountId` from {@link RelayerClient.me}; a transaction bought
199
+ * for another account rejects with `403 app_account_token_mismatch`, one
200
+ * attached to another account with `409 held_by_another_account` — restore
201
+ * those with {@link RelayerClient.claimSubscription}. `422 unmapped_product`
202
+ * = the product id is not in the catalog.
203
+ */
204
+ async submitAppleTransaction(signedTransaction) {
205
+ return this.#request("POST", "/v1/billing/apple/transactions", {
206
+ signedTransaction
207
+ });
208
+ }
209
+ /**
210
+ * Restore purchases: move a store subscription onto the caller's account
211
+ * (single owner — the previous account loses it). Pass a signed transaction
212
+ * from `Transaction.currentEntitlements`; only a device signed in with the
213
+ * purchasing Apple ID can produce one. A subscription moves to a different
214
+ * account at most once per calendar month: `409 claim_cooldown` otherwise.
215
+ */
216
+ async claimSubscription(input) {
217
+ return this.#request("POST", "/v1/subscriptions/claim", input);
218
+ }
219
+ /**
220
+ * Stripe Customer Portal URL for the caller's own Stripe subscription.
221
+ * Rejects with `manage_in_store` (409) for an Apple / Google subscription
222
+ * and `no_subscription` (404) when there is nothing to manage.
223
+ */
224
+ async createPortalSession(opts) {
225
+ return this.#request("POST", "/v1/billing/portal-session", opts ?? {});
226
+ }
163
227
  /**
164
228
  * Start a Stripe Checkout for a tier and return the created session
165
229
  * `{ id, url }`. In a browser this ALSO navigates to `url` by default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@universal-everything/sdk-core",
3
- "version": "1.1.0",
3
+ "version": "1.2.0-dev.0a281f1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/universal-everything/universal-relayer.git",