@explorins/pers-sdk-react-native 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3216,6 +3216,12 @@ const EntityTypes = {
3216
3216
  SIGNING_ACCOUNT: 'signing_account',
3217
3217
  API_KEY: 'api_key',
3218
3218
  WEBHOOK: 'webhook',
3219
+ // Service domains
3220
+ ANALYTICS: 'analytics',
3221
+ CHAIN: 'chain',
3222
+ FILE: 'file',
3223
+ JWKS: 'jwks',
3224
+ PROJECT: 'project',
3219
3225
  };
3220
3226
  /**
3221
3227
  * Error-specific domains (extends EntityTypes)
@@ -3726,6 +3732,9 @@ const CommonErrorCodes = {
3726
3732
  TOKEN_NOT_ACTIVE: 'TOKEN_NOT_ACTIVE',
3727
3733
  TOKEN_ID_REQUIRED: 'TOKEN_ID_REQUIRED',
3728
3734
  TOKEN_TYPE_NOT_FOUND: 'TOKEN_TYPE_NOT_FOUND',
3735
+ TOKEN_UNIT_NOT_FOUND: 'TOKEN_UNIT_NOT_FOUND',
3736
+ NFT_EXPIRED: 'NFT_EXPIRED',
3737
+ NFT_BUSINESS_RESTRICTION: 'NFT_BUSINESS_RESTRICTION',
3729
3738
  // Wallet domain
3730
3739
  WALLET_MISSING_SIGNING: 'WALLET_MISSING_SIGNING',
3731
3740
  WALLET_NOT_FOUND: 'WALLET_NOT_FOUND',
@@ -3765,6 +3774,9 @@ const CommonErrorCodes = {
3765
3774
  CAMPAIGN_DAILY_LIMIT_REACHED: 'CAMPAIGN_DAILY_LIMIT_REACHED',
3766
3775
  CAMPAIGN_GLOBAL_LIMIT_REACHED: 'CAMPAIGN_GLOBAL_LIMIT_REACHED',
3767
3776
  CAMPAIGN_GLOBAL_DAILY_LIMIT_REACHED: 'CAMPAIGN_GLOBAL_DAILY_LIMIT_REACHED',
3777
+ CAMPAIGN_NOT_FOUND: 'CAMPAIGN_NOT_FOUND',
3778
+ GEOGRAPHIC_RESTRICTION: 'GEOGRAPHIC_RESTRICTION',
3779
+ LOCATION_VERIFICATION_REQUIRED: 'LOCATION_VERIFICATION_REQUIRED',
3768
3780
  // Trigger domain
3769
3781
  TRIGGER_NOT_FOUND: 'TRIGGER_NOT_FOUND',
3770
3782
  // Trigger Source domain
@@ -3780,6 +3792,12 @@ const CommonErrorCodes = {
3780
3792
  EXTERNAL_SERVICE_ERROR: 'EXTERNAL_SERVICE_ERROR',
3781
3793
  AUTHENTICATION_REQUIRED: 'AUTHENTICATION_REQUIRED',
3782
3794
  AUTHORIZATION_FAILED: 'AUTHORIZATION_FAILED',
3795
+ CONFIGURATION_MISSING: 'CONFIGURATION_MISSING',
3796
+ // Redemption domain
3797
+ USER_STATUS_RESTRICTED: 'USER_STATUS_RESTRICTED',
3798
+ REDEMPTION_ALREADY_PROCESSING: 'REDEMPTION_ALREADY_PROCESSING',
3799
+ MAX_REDEMPTION: 'MAX_REDEMPTION',
3800
+ USER_LIMIT: 'USER_LIMIT',
3783
3801
  // Authentication errors
3784
3802
  INVALID_TOKEN: 'INVALID_TOKEN',
3785
3803
  TOKEN_EXPIRED: 'TOKEN_EXPIRED',
@@ -3787,6 +3805,8 @@ const CommonErrorCodes = {
3787
3805
  REFRESH_TOKEN_EXPIRED: 'REFRESH_TOKEN_EXPIRED',
3788
3806
  REFRESH_TOKEN_REVOKED: 'REFRESH_TOKEN_REVOKED',
3789
3807
  FIREBASE_AUTH_ERROR: 'FIREBASE_AUTH_ERROR',
3808
+ MULTIPLE_CONTEXT_SELECTION_REQUIRED: 'MULTIPLE_CONTEXT_SELECTION_REQUIRED',
3809
+ ADMIN_TENANT_REQUIRED: 'ADMIN_TENANT_REQUIRED',
3790
3810
  // Generic fallbacks
3791
3811
  INTERNAL_ERROR: 'INTERNAL_ERROR',
3792
3812
  VALIDATION_ERROR: 'VALIDATION_ERROR',
@@ -3883,12 +3903,12 @@ class ErrorUtils {
3883
3903
  return false;
3884
3904
  const err = error;
3885
3905
  // Check for explicit token expired indicators
3886
- if (err?.code === 'TOKEN_EXPIRED' || err?.errorCode === 'TOKEN_EXPIRED') {
3906
+ if (err?.code === CommonErrorCodes.TOKEN_EXPIRED || err?.errorCode === CommonErrorCodes.TOKEN_EXPIRED) {
3887
3907
  return true;
3888
3908
  }
3889
3909
  // Check in response data
3890
3910
  const apiError = err?.response?.data || err;
3891
- if (apiError?.code === 'TOKEN_EXPIRED' || apiError?.errorCode === 'TOKEN_EXPIRED') {
3911
+ if (apiError?.code === CommonErrorCodes.TOKEN_EXPIRED || apiError?.errorCode === CommonErrorCodes.TOKEN_EXPIRED) {
3892
3912
  return true;
3893
3913
  }
3894
3914
  // Any 401 error is treated as token expiration
@@ -3958,7 +3978,9 @@ class ErrorUtils {
3958
3978
  // Try extraction strategies in priority order
3959
3979
  const candidates = [
3960
3980
  this.parseHttpClientError(error), // React Native format
3961
- httpError.response?.data, // Axios/fetch format
3981
+ httpError.response && typeof httpError.response === 'object'
3982
+ ? httpError.response.data
3983
+ : undefined, // Axios/fetch format
3962
3984
  httpError.error, // Angular HttpErrorResponse
3963
3985
  httpError // Direct error object
3964
3986
  ];
@@ -3977,8 +3999,9 @@ class ErrorUtils {
3977
3999
  * Check if object has structured error fields from backend (StructuredError)
3978
4000
  */
3979
4001
  static hasStructuredErrorFields(obj) {
3980
- return obj && typeof obj === 'object' &&
3981
- ((obj.code && obj.category) || (obj.status && (obj.detail || obj.message || obj.title)));
4002
+ return obj !== null && typeof obj === 'object' &&
4003
+ (('code' in obj && 'category' in obj) ||
4004
+ ('status' in obj && ('detail' in obj || 'message' in obj || 'title' in obj)));
3982
4005
  }
3983
4006
  /**
3984
4007
  * Create fallback error structure when no structured error found
@@ -3989,9 +4012,9 @@ class ErrorUtils {
3989
4012
  title: 'Request failed',
3990
4013
  message: this.getMessage(error),
3991
4014
  detail: this.getMessage(error),
3992
- code: 'REQUEST_FAILED',
4015
+ code: CommonErrorCodes.TECHNICAL_ERROR,
3993
4016
  category: exports.ErrorCategory.TECHNICAL,
3994
- domain: 'api',
4017
+ domain: ErrorDomains.EXTERNAL,
3995
4018
  retryable: this.isRetryable(error)
3996
4019
  };
3997
4020
  }
@@ -4070,7 +4093,7 @@ class PersApiError extends Error {
4070
4093
  this.method = method;
4071
4094
  this.name = 'PersApiError';
4072
4095
  // Use backend error details directly
4073
- this.code = backendError?.code || 'API_ERROR';
4096
+ this.code = backendError?.code || CommonErrorCodes.TECHNICAL_ERROR;
4074
4097
  this.domain = backendError?.domain || ErrorDomains.EXTERNAL;
4075
4098
  this.category = backendError?.category || exports.ErrorCategory.TECHNICAL;
4076
4099
  this.retryable = backendError?.retryable ?? (statusCode ? statusCode >= 500 : false);
@@ -4094,7 +4117,7 @@ class AuthenticationError extends PersApiError {
4094
4117
  class NetworkError extends Error {
4095
4118
  constructor(message) {
4096
4119
  super(message);
4097
- this.code = SdkErrorCodes.NETWORK_ERROR;
4120
+ this.code = CommonErrorCodes.EXTERNAL_SERVICE_ERROR;
4098
4121
  this.domain = ErrorDomains.EXTERNAL;
4099
4122
  this.category = exports.ErrorCategory.INFRASTRUCTURE;
4100
4123
  this.retryable = true;
@@ -5142,6 +5165,58 @@ class TokenApi {
5142
5165
  }
5143
5166
  return this.apiClient.get(url);
5144
5167
  }
5168
+ /**
5169
+ * Get all token metadata with filtering and pagination
5170
+ *
5171
+ * Useful for displaying rewards (ERC1155) or stamps (ERC721) in tables.
5172
+ * Non-admin users always get active metadata only.
5173
+ *
5174
+ * @param options - Filter and pagination options
5175
+ * @returns Paginated response with token metadata
5176
+ *
5177
+ * @example Get all active rewards
5178
+ * ```typescript
5179
+ * const rewards = await tokenApi.getTokenMetadata({
5180
+ * tokenType: 'ERC1155',
5181
+ * active: true,
5182
+ * limit: 10
5183
+ * });
5184
+ * ```
5185
+ *
5186
+ * @example Get stamps with search
5187
+ * ```typescript
5188
+ * const stamps = await tokenApi.getTokenMetadata({
5189
+ * tokenType: 'ERC721',
5190
+ * search: 'gold',
5191
+ * sortBy: 'name',
5192
+ * sortOrder: 'ASC'
5193
+ * });
5194
+ * ```
5195
+ */
5196
+ async getTokenMetadata(options) {
5197
+ const params = buildPaginationParams(options);
5198
+ if (options?.active !== undefined) {
5199
+ params.set('active', String(options.active));
5200
+ }
5201
+ if (options?.tokenType) {
5202
+ params.set('tokenType', options.tokenType);
5203
+ }
5204
+ if (options?.tokenId) {
5205
+ params.set('tokenId', options.tokenId);
5206
+ }
5207
+ if (options?.search) {
5208
+ params.set('search', options.search);
5209
+ }
5210
+ // Handle tags as string or array
5211
+ if (options?.tags) {
5212
+ const tagsArray = Array.isArray(options.tags) ? options.tags : [options.tags];
5213
+ if (tagsArray.length > 0) {
5214
+ params.set('tags', tagsArray.join(','));
5215
+ }
5216
+ }
5217
+ const response = await this.apiClient.get(`${this.basePath}/metadata?${params.toString()}`);
5218
+ return normalizeToPaginated(response);
5219
+ }
5145
5220
  // ==========================================
5146
5221
  // ADMIN OPERATIONS
5147
5222
  // ==========================================
@@ -5251,6 +5326,40 @@ class TokenService {
5251
5326
  async getTokenByContractAddress(contractAddress, contractTokenId) {
5252
5327
  return this.tokenApi.getTokenByContractAddress(contractAddress, contractTokenId);
5253
5328
  }
5329
+ /**
5330
+ * Get all token metadata with filtering and pagination
5331
+ *
5332
+ * Useful for displaying rewards (ERC1155) or stamps (ERC721) in tables.
5333
+ * Non-admin users always get active metadata only.
5334
+ *
5335
+ * @param options - Filter and pagination options
5336
+ * @returns Paginated response with token metadata
5337
+ */
5338
+ async getTokenMetadata(options) {
5339
+ return this.tokenApi.getTokenMetadata(options);
5340
+ }
5341
+ /**
5342
+ * Get rewards (ERC1155 token metadata) with filtering and pagination
5343
+ *
5344
+ * Convenience method for fetching reward metadata.
5345
+ *
5346
+ * @param options - Filter and pagination options (tokenType is set automatically)
5347
+ * @returns Paginated response with reward metadata
5348
+ */
5349
+ async getRewards(options) {
5350
+ return this.tokenApi.getTokenMetadata({ ...options, tokenType: NativeTokenTypes.ERC1155 });
5351
+ }
5352
+ /**
5353
+ * Get stamps (ERC721 token metadata) with filtering and pagination
5354
+ *
5355
+ * Convenience method for fetching stamp metadata.
5356
+ *
5357
+ * @param options - Filter and pagination options (tokenType is set automatically)
5358
+ * @returns Paginated response with stamp metadata
5359
+ */
5360
+ async getStamps(options) {
5361
+ return this.tokenApi.getTokenMetadata({ ...options, tokenType: NativeTokenTypes.ERC721 });
5362
+ }
5254
5363
  // ==========================================
5255
5364
  // ADMIN OPERATIONS
5256
5365
  // ==========================================
@@ -5377,7 +5486,7 @@ class BusinessApi {
5377
5486
  /**
5378
5487
  * Get all businesses with role-based filtering and pagination
5379
5488
  *
5380
- * Endpoint: GET /businesses?active={boolean}&sanitize={mode}&page={number}&limit={number}
5489
+ * Endpoint: GET /businesses?active={boolean}&sanitize={mode}&page={number}&limit={number}&businessTypeId={number}
5381
5490
  * Auth: @ApiSecurity('projectKey') (enhanced with role-based filtering)
5382
5491
  *
5383
5492
  * Note:
@@ -5400,8 +5509,11 @@ class BusinessApi {
5400
5509
  * });
5401
5510
  *
5402
5511
  * // Filter by tag(s)
5403
- * const tagged = await businessApi.getBusinesses({ tag: 'restaurant' });
5404
- * const multiTag = await businessApi.getBusinesses({ tag: ['restaurant', 'cafe'] });
5512
+ * const tagged = await businessApi.getBusinesses({ tags: 'restaurant' });
5513
+ * const multiTag = await businessApi.getBusinesses({ tags: ['restaurant', 'cafe'] });
5514
+ *
5515
+ * // Filter by business type
5516
+ * const hotels = await businessApi.getBusinesses({ businessTypeId: 1 });
5405
5517
  * ```
5406
5518
  */
5407
5519
  async getBusinesses(options) {
@@ -5421,6 +5533,9 @@ class BusinessApi {
5421
5533
  if (options?.sanitize) {
5422
5534
  params.set('sanitize', options.sanitize);
5423
5535
  }
5536
+ if (options?.businessTypeId !== undefined) {
5537
+ params.set('businessTypeId', String(options.businessTypeId));
5538
+ }
5424
5539
  const response = await this.apiClient.get(`${this.basePath}?${params.toString()}`);
5425
5540
  return normalizeToPaginated(response);
5426
5541
  }
@@ -5767,20 +5882,16 @@ class BusinessService {
5767
5882
  return this.businessApi.getBusinessByAccount(accountAddress);
5768
5883
  }
5769
5884
  /**
5770
- * Get businesses by type (client-side filtering)
5771
- * @deprecated Consider implementing server-side filtering for better performance
5885
+ * Get businesses by type (server-side filtering)
5886
+ * Uses the businessTypeId parameter for efficient server-side filtering.
5772
5887
  */
5773
5888
  async getBusinessesByType(typeId, options) {
5774
- const response = await this.getActiveBusinesses(options);
5775
- const filtered = response.data.filter((business) => business.businessType && business.businessType.id === parseInt(typeId));
5776
- return {
5777
- data: filtered,
5778
- pagination: {
5779
- ...response.pagination,
5780
- total: filtered.length,
5781
- pages: Math.ceil(filtered.length / (response.pagination.limit || 20))
5782
- }
5783
- };
5889
+ const businessTypeId = parseInt(typeId, 10);
5890
+ return this.businessApi.getBusinesses({
5891
+ ...options,
5892
+ businessTypeId,
5893
+ active: options?.active ?? true // Default to active businesses
5894
+ });
5784
5895
  }
5785
5896
  // ==========================================
5786
5897
  // ADMIN OPERATIONS
@@ -7019,6 +7130,14 @@ class RedemptionService {
7019
7130
  async getRedemptions(options) {
7020
7131
  return this.redemptionApi.getRedemptions(options);
7021
7132
  }
7133
+ /**
7134
+ * Get a specific redemption by ID
7135
+ * @param id - Redemption UUID
7136
+ * @param include - Optional relations to include: 'redeemCount'
7137
+ */
7138
+ async getRedemptionById(id, include) {
7139
+ return this.redemptionApi.getRedemptionById(id, include);
7140
+ }
7022
7141
  /**
7023
7142
  * Convenience: Get active redemptions (public)
7024
7143
  */
@@ -9575,13 +9694,17 @@ class AuthService {
9575
9694
  * Returns true for:
9576
9695
  * - Known fatal error codes (REFRESH_TOKEN_EXPIRED, TOKEN_REVOKED, etc.)
9577
9696
  * - Fatal error codes in error messages (wrapped errors)
9578
- * - 401/403 HTTP status codes
9579
9697
  *
9580
- * Returns false for (retryable errors):
9698
+ * Returns false for (retryable/recoverable):
9581
9699
  * - Network errors
9582
9700
  * - Timeouts
9583
9701
  * - 5xx server errors
9584
9702
  * - TOKEN_EXPIRED (normal refresh trigger)
9703
+ * - Raw 401/403 without fatal code (should try refresh first)
9704
+ *
9705
+ * NOTE: We intentionally DO NOT treat raw 401/403 status as fatal.
9706
+ * The HTTP client handles 401 by attempting token refresh first.
9707
+ * Only if refresh fails WITH a fatal code do we logout.
9585
9708
  */
9586
9709
  isDefinitiveAuthFailure(error) {
9587
9710
  if (!error)
@@ -9596,11 +9719,12 @@ class AuthService {
9596
9719
  if (isFatalAuthErrorInMessage(errorMessage)) {
9597
9720
  return true;
9598
9721
  }
9599
- // Check HTTP status if available
9600
- const status = errorAny?.status || errorAny?.response?.status;
9601
- if (status === 401 || status === 403) {
9602
- return true;
9603
- }
9722
+ // NOTE: We intentionally DO NOT check HTTP status codes here.
9723
+ // A raw 401/403 without a fatal error code should trigger a refresh attempt,
9724
+ // not an immediate logout. The HTTP client (pers-api-client.ts) handles this:
9725
+ // 1. 401 received → try refresh token
9726
+ // 2. Refresh succeeds → retry original request
9727
+ // 3. Refresh fails with fatal CODE → then logout
9604
9728
  // Everything else is considered retryable
9605
9729
  return false;
9606
9730
  }
@@ -9684,12 +9808,17 @@ class AuthService {
9684
9808
  const contextClaims = refreshToken ? decodeJwtPayload(refreshToken) : null;
9685
9809
  // Re-authenticate using provider token based on auth type
9686
9810
  // Pass the context from the previous session to avoid MULTIPLE_CONTEXT_SELECTION_REQUIRED
9811
+ // Note: In AuthJWTPayload, when accountType=BUSINESS, sub contains the businessId
9687
9812
  switch (authType) {
9688
9813
  case exports.AccountOwnerType.USER:
9689
9814
  await this.loginUser(providerToken);
9690
9815
  break;
9691
9816
  case exports.AccountOwnerType.BUSINESS:
9692
- await this.loginBusiness(providerToken, contextClaims?.businessId ? { businessId: contextClaims.businessId } : undefined);
9817
+ // For BUSINESS auth, sub contains the businessId when accountType is BUSINESS
9818
+ const businessId = contextClaims?.accountType === exports.AccountOwnerType.BUSINESS
9819
+ ? contextClaims.sub
9820
+ : undefined;
9821
+ await this.loginBusiness(providerToken, businessId ? { businessId } : undefined);
9693
9822
  break;
9694
9823
  case exports.AccountOwnerType.TENANT:
9695
9824
  await this.loginTenantAdmin(providerToken, contextClaims?.tenantId ? { tenantId: contextClaims.tenantId } : undefined);
@@ -10457,17 +10586,16 @@ class DPoPManager {
10457
10586
  * Generate a short fingerprint of the public key for debugging
10458
10587
  * This helps identify if the same key is being used across operations
10459
10588
  */
10460
- getPublicKeyFingerprint(publicKey) {
10461
- try {
10462
- // Use the 'x' coordinate of the EC key as a fingerprint
10463
- const x = publicKey.x || '';
10464
- const y = publicKey.y || '';
10465
- return `${x.substring(0, 8)}...${y.substring(0, 4)}`;
10466
- }
10467
- catch {
10468
- return 'unknown';
10469
- }
10470
- }
10589
+ /* private getPublicKeyFingerprint(publicKey: JsonWebKey): string {
10590
+ try {
10591
+ // Use the 'x' coordinate of the EC key as a fingerprint
10592
+ const x = publicKey.x || '';
10593
+ const y = publicKey.y || '';
10594
+ return `${x.substring(0, 8)}...${y.substring(0, 4)}`;
10595
+ } catch {
10596
+ return 'unknown';
10597
+ }
10598
+ } */
10471
10599
  /**
10472
10600
  * Clears DPoP keys (e.g. on logout)
10473
10601
  */
@@ -10716,7 +10844,7 @@ class DefaultAuthProvider {
10716
10844
  /** SDK package name */
10717
10845
  const SDK_NAME = "@explorins/pers-sdk";
10718
10846
  /** SDK version - injected from package.json at build time */
10719
- const SDK_VERSION = "2.1.33";
10847
+ const SDK_VERSION = "2.1.37";
10720
10848
  /** Full SDK identifier for headers */
10721
10849
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
10722
10850
 
@@ -10927,9 +11055,9 @@ class PersApiClient {
10927
11055
  }); */
10928
11056
  this._events?.emitError({
10929
11057
  domain: errorDetails.domain || 'external',
10930
- type: errorDetails.code || 'API_ERROR',
10931
- userMessage: errorDetails.message || errorMessage, // Event system uses userMessage for display
10932
- code: errorDetails.code,
11058
+ type: 'api_error', // Event type discriminator (fixed value)
11059
+ userMessage: errorDetails.message || errorMessage,
11060
+ code: errorDetails.code, // Actual error code from backend
10933
11061
  details: {
10934
11062
  endpoint,
10935
11063
  method,
@@ -12565,6 +12693,107 @@ class TokenManager {
12565
12693
  async getTokenByContract(contractAddress, contractTokenId = null) {
12566
12694
  return this.tokenService.getTokenByContractAddress(contractAddress, contractTokenId);
12567
12695
  }
12696
+ /**
12697
+ * Get all token metadata with filtering and pagination
12698
+ *
12699
+ * Retrieves token metadata (rewards/stamps) with server-side filtering and pagination.
12700
+ * Useful for displaying rewards (ERC1155) or stamps (ERC721) in admin tables.
12701
+ * Non-admin users always get active metadata only.
12702
+ *
12703
+ * @param options - Filter and pagination options
12704
+ * @returns Promise resolving to paginated token metadata
12705
+ *
12706
+ * @example Get paginated rewards
12707
+ * ```typescript
12708
+ * const rewards = await sdk.tokens.getTokenMetadata({
12709
+ * tokenType: 'ERC1155',
12710
+ * active: true,
12711
+ * page: 1,
12712
+ * limit: 10,
12713
+ * sortBy: 'name',
12714
+ * sortOrder: 'ASC'
12715
+ * });
12716
+ *
12717
+ * console.log(`Page 1 of ${rewards.pagination.pages}`);
12718
+ * rewards.data.forEach(r => console.log(r.name));
12719
+ * ```
12720
+ *
12721
+ * @example Get stamps with search
12722
+ * ```typescript
12723
+ * const stamps = await sdk.tokens.getTokenMetadata({
12724
+ * tokenType: 'ERC721',
12725
+ * search: 'gold',
12726
+ * limit: 25
12727
+ * });
12728
+ * ```
12729
+ */
12730
+ async getTokenMetadata(options) {
12731
+ return this.tokenService.getTokenMetadata(options);
12732
+ }
12733
+ /**
12734
+ * Get rewards (ERC1155 token metadata) with filtering and pagination
12735
+ *
12736
+ * Convenience method for fetching reward metadata. Rewards are ERC1155 tokens
12737
+ * that represent collectible items, vouchers, or other redeemable rewards.
12738
+ *
12739
+ * @param options - Filter and pagination options (tokenType is set automatically)
12740
+ * @returns Promise resolving to paginated reward metadata
12741
+ *
12742
+ * @example Get active rewards
12743
+ * ```typescript
12744
+ * const rewards = await sdk.tokens.getRewards({
12745
+ * active: true,
12746
+ * limit: 10
12747
+ * });
12748
+ *
12749
+ * console.log(`Found ${rewards.pagination.total} rewards`);
12750
+ * rewards.data.forEach(r => console.log(`${r.name}: ${r.description}`));
12751
+ * ```
12752
+ *
12753
+ * @example Search rewards
12754
+ * ```typescript
12755
+ * const discountRewards = await sdk.tokens.getRewards({
12756
+ * search: 'discount',
12757
+ * sortBy: 'name',
12758
+ * sortOrder: 'ASC'
12759
+ * });
12760
+ * ```
12761
+ */
12762
+ async getRewards(options) {
12763
+ return this.tokenService.getRewards(options);
12764
+ }
12765
+ /**
12766
+ * Get stamps (ERC721 token metadata) with filtering and pagination
12767
+ *
12768
+ * Convenience method for fetching stamp metadata. Stamps are ERC721 tokens
12769
+ * that represent achievements, badges, or collectible status markers.
12770
+ *
12771
+ * @param options - Filter and pagination options (tokenType is set automatically)
12772
+ * @returns Promise resolving to paginated stamp metadata
12773
+ *
12774
+ * @example Get active stamps
12775
+ * ```typescript
12776
+ * const stamps = await sdk.tokens.getStamps({
12777
+ * active: true,
12778
+ * limit: 10
12779
+ * });
12780
+ *
12781
+ * console.log(`Found ${stamps.pagination.total} stamps`);
12782
+ * stamps.data.forEach(s => console.log(`${s.name}: ${s.description}`));
12783
+ * ```
12784
+ *
12785
+ * @example Filter stamps by tags
12786
+ * ```typescript
12787
+ * const goldStamps = await sdk.tokens.getStamps({
12788
+ * tags: ['gold', 'premium'],
12789
+ * sortBy: 'createdAt',
12790
+ * sortOrder: 'DESC'
12791
+ * });
12792
+ * ```
12793
+ */
12794
+ async getStamps(options) {
12795
+ return this.tokenService.getStamps(options);
12796
+ }
12568
12797
  /**
12569
12798
  * Admin: Create new token
12570
12799
  *
@@ -14469,6 +14698,52 @@ class RedemptionManager {
14469
14698
  async getRedemptions(options) {
14470
14699
  return this.redemptionService.getRedemptions(options);
14471
14700
  }
14701
+ /**
14702
+ * Get a specific redemption by ID
14703
+ *
14704
+ * Retrieves detailed information about a specific redemption offer.
14705
+ * Useful when you already have the redemption ID and need full details.
14706
+ *
14707
+ * @param id - Redemption UUID
14708
+ * @param include - Optional relations to include: 'redeemCount'
14709
+ * @returns Promise resolving to the redemption
14710
+ *
14711
+ * @example Basic usage
14712
+ * ```typescript
14713
+ * const redemption = await sdk.redemptions.getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
14714
+ * console.log(redemption.name);
14715
+ * console.log(redemption.description);
14716
+ * ```
14717
+ *
14718
+ * @example With redeem count
14719
+ * ```typescript
14720
+ * const redemption = await sdk.redemptions.getRedemptionById(
14721
+ * 'redemption-123',
14722
+ * ['redeemCount']
14723
+ * );
14724
+ * console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
14725
+ * ```
14726
+ *
14727
+ * @example Get redemption details for display
14728
+ * ```typescript
14729
+ * const redemption = await sdk.redemptions.getRedemptionById(id);
14730
+ *
14731
+ * console.log('Redemption Details:');
14732
+ * console.log(`Name: ${redemption.name}`);
14733
+ * console.log(`Description: ${redemption.description}`);
14734
+ * console.log(`Active: ${redemption.isActive}`);
14735
+ *
14736
+ * if (redemption.tokenUnits?.length) {
14737
+ * console.log('Cost:');
14738
+ * redemption.tokenUnits.forEach(unit => {
14739
+ * console.log(` ${unit.amount} ${unit.token.symbol}`);
14740
+ * });
14741
+ * }
14742
+ * ```
14743
+ */
14744
+ async getRedemptionById(id, include) {
14745
+ return this.redemptionService.getRedemptionById(id, include);
14746
+ }
14472
14747
  /**
14473
14748
  * Get available redemption types
14474
14749
  *
@@ -21157,6 +21432,99 @@ const useTokens = () => {
21157
21432
  throw error;
21158
21433
  }
21159
21434
  }, [sdk, isInitialized]);
21435
+ /**
21436
+ * Get all token metadata with filtering and pagination
21437
+ *
21438
+ * Retrieves token metadata (rewards/stamps) with server-side filtering.
21439
+ * Use tokenType: 'ERC1155' for rewards, 'ERC721' for stamps.
21440
+ *
21441
+ * @param options - Filter and pagination options
21442
+ * @returns Promise resolving to paginated token metadata
21443
+ * @throws Error if SDK is not initialized
21444
+ *
21445
+ * @example
21446
+ * ```typescript
21447
+ * const { getTokenMetadata } = useTokens();
21448
+ * const rewards = await getTokenMetadata({
21449
+ * tokenType: 'ERC1155',
21450
+ * active: true,
21451
+ * limit: 10
21452
+ * });
21453
+ * ```
21454
+ */
21455
+ const getTokenMetadata = react.useCallback(async (options) => {
21456
+ if (!isInitialized || !sdk) {
21457
+ throw new Error('SDK not initialized. Call initialize() first.');
21458
+ }
21459
+ try {
21460
+ const result = await sdk.tokens.getTokenMetadata(options);
21461
+ return result;
21462
+ }
21463
+ catch (error) {
21464
+ console.error('Failed to fetch token metadata:', error);
21465
+ throw error;
21466
+ }
21467
+ }, [sdk, isInitialized]);
21468
+ /**
21469
+ * Get rewards (ERC1155 token metadata) with filtering and pagination
21470
+ *
21471
+ * Convenience method for fetching reward metadata. Rewards are ERC1155 tokens
21472
+ * that represent collectible items, vouchers, or other redeemable rewards.
21473
+ *
21474
+ * @param options - Filter and pagination options
21475
+ * @returns Promise resolving to paginated reward metadata
21476
+ * @throws Error if SDK is not initialized
21477
+ *
21478
+ * @example
21479
+ * ```typescript
21480
+ * const { getRewards } = useTokens();
21481
+ * const rewards = await getRewards({ active: true, limit: 10 });
21482
+ * console.log(`Found ${rewards.pagination.total} rewards`);
21483
+ * ```
21484
+ */
21485
+ const getRewards = react.useCallback(async (options) => {
21486
+ if (!isInitialized || !sdk) {
21487
+ throw new Error('SDK not initialized. Call initialize() first.');
21488
+ }
21489
+ try {
21490
+ const result = await sdk.tokens.getRewards(options);
21491
+ return result;
21492
+ }
21493
+ catch (error) {
21494
+ console.error('Failed to fetch rewards:', error);
21495
+ throw error;
21496
+ }
21497
+ }, [sdk, isInitialized]);
21498
+ /**
21499
+ * Get stamps (ERC721 token metadata) with filtering and pagination
21500
+ *
21501
+ * Convenience method for fetching stamp metadata. Stamps are ERC721 tokens
21502
+ * that represent achievements, badges, or collectible status markers.
21503
+ *
21504
+ * @param options - Filter and pagination options
21505
+ * @returns Promise resolving to paginated stamp metadata
21506
+ * @throws Error if SDK is not initialized
21507
+ *
21508
+ * @example
21509
+ * ```typescript
21510
+ * const { getStamps } = useTokens();
21511
+ * const stamps = await getStamps({ active: true, limit: 10 });
21512
+ * console.log(`Found ${stamps.pagination.total} stamps`);
21513
+ * ```
21514
+ */
21515
+ const getStamps = react.useCallback(async (options) => {
21516
+ if (!isInitialized || !sdk) {
21517
+ throw new Error('SDK not initialized. Call initialize() first.');
21518
+ }
21519
+ try {
21520
+ const result = await sdk.tokens.getStamps(options);
21521
+ return result;
21522
+ }
21523
+ catch (error) {
21524
+ console.error('Failed to fetch stamps:', error);
21525
+ throw error;
21526
+ }
21527
+ }, [sdk, isInitialized]);
21160
21528
  return {
21161
21529
  getTokens,
21162
21530
  getActiveCreditToken,
@@ -21164,6 +21532,9 @@ const useTokens = () => {
21164
21532
  getTokenTypes,
21165
21533
  getStatusTokens,
21166
21534
  getTokenByContract,
21535
+ getTokenMetadata,
21536
+ getRewards,
21537
+ getStamps,
21167
21538
  isAvailable: isInitialized && !!sdk?.tokens,
21168
21539
  };
21169
21540
  };
@@ -44400,6 +44771,25 @@ const useRedemptions = () => {
44400
44771
  /**
44401
44772
  * Redeem a redemption offer with optional dynamic context
44402
44773
  *
44774
+ * ## When to use `redeem()` vs `createTransaction()`
44775
+ *
44776
+ * | Use Case | Method | Auto-Signs |
44777
+ * |----------|--------|------------|
44778
+ * | **Voucher redemption** | `redeem()` | ✅ Yes |
44779
+ * | **Reward redemption** | `redeem()` | ✅ Yes |
44780
+ * | **Direct token transfer** | `createTransaction()` | ⚠️ Depends |
44781
+ * | **POS burn with QR** | `buildPOSBurnRequest()` | ❌ Manual |
44782
+ *
44783
+ * **Use `redeem()` for voucher/reward redemptions.** It automatically:
44784
+ * 1. Creates redemption record
44785
+ * 2. Triggers passkey/biometric prompt
44786
+ * 3. Signs the transaction
44787
+ * 4. Submits to blockchain
44788
+ * 5. Returns result with `transactionHash`
44789
+ *
44790
+ * **No admin approval required** - The "PENDING" state is brief (seconds).
44791
+ * The user's passkey signature IS the approval.
44792
+ *
44403
44793
  * Supports dynamic context for personalized NFT generation when the redemption
44404
44794
  * issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
44405
44795
  * and custom metadata.
@@ -44408,11 +44798,21 @@ const useRedemptions = () => {
44408
44798
  * @param options - Optional settings
44409
44799
  * @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
44410
44800
  * @param options.onStatusUpdate - Callback for transaction signing status updates
44411
- * @returns Promise resolving to redemption result
44801
+ * @returns Promise resolving to redemption result with `transactionHash` on success
44412
44802
  *
44413
- * @example Basic redemption
44803
+ * @example Basic redemption (most common)
44414
44804
  * ```typescript
44415
- * const result = await redeem('redemption-123');
44805
+ * const { redeem } = useRedemptions();
44806
+ *
44807
+ * const result = await redeem('redemption-123', {
44808
+ * onStatusUpdate: (status, message) => {
44809
+ * console.log(`Status: ${status} - ${message}`);
44810
+ * }
44811
+ * });
44812
+ *
44813
+ * if (result.transactionHash) {
44814
+ * console.log('Redemption complete!', result.transactionHash);
44815
+ * }
44416
44816
  * ```
44417
44817
  *
44418
44818
  * @example Redemption with dynamic context (personalized NFT)
@@ -44443,6 +44843,8 @@ const useRedemptions = () => {
44443
44843
  * }
44444
44844
  * });
44445
44845
  * ```
44846
+ *
44847
+ * @see {@link useTransactions.createTransaction | createTransaction()} for direct transfers without redemption flow
44446
44848
  */
44447
44849
  const redeem = react.useCallback(async (redemptionId, options) => {
44448
44850
  if (!isInitialized || !sdk) {
@@ -44592,8 +44994,43 @@ const useRedemptions = () => {
44592
44994
  throw error;
44593
44995
  }
44594
44996
  }, [sdk, isInitialized]);
44997
+ /**
44998
+ * Get a specific redemption by ID
44999
+ *
45000
+ * Retrieves detailed information about a specific redemption offer.
45001
+ *
45002
+ * @param id - Redemption UUID
45003
+ * @param include - Optional relations to include: 'redeemCount'
45004
+ * @returns Promise resolving to the redemption
45005
+ *
45006
+ * @example Basic usage
45007
+ * ```typescript
45008
+ * const redemption = await getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
45009
+ * console.log(redemption.name);
45010
+ * ```
45011
+ *
45012
+ * @example With redeem count
45013
+ * ```typescript
45014
+ * const redemption = await getRedemptionById('redemption-123', ['redeemCount']);
45015
+ * console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
45016
+ * ```
45017
+ */
45018
+ const getRedemptionById = react.useCallback(async (id, include) => {
45019
+ if (!isInitialized || !sdk) {
45020
+ throw new Error('SDK not initialized. Call initialize() first.');
45021
+ }
45022
+ try {
45023
+ const result = await sdk.redemptions.getRedemptionById(id, include);
45024
+ return result;
45025
+ }
45026
+ catch (error) {
45027
+ console.error('Failed to fetch redemption by ID:', error);
45028
+ throw error;
45029
+ }
45030
+ }, [sdk, isInitialized]);
44595
45031
  return {
44596
45032
  getRedemptions,
45033
+ getRedemptionById,
44597
45034
  getUserRedemptions,
44598
45035
  redeem,
44599
45036
  getRedemptionTypes,