@explorins/pers-sdk-react-native 2.1.12 → 2.1.14-beta.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/dist/index.js CHANGED
@@ -2882,12 +2882,21 @@ exports.SignedUrlType = void 0;
2882
2882
  * Token Validity Type Enum
2883
2883
  *
2884
2884
  * Defines how token expiry is calculated:
2885
+ *
2886
+ * Static types (computed at issuance from template):
2885
2887
  * - FIXED_DATE: Uses expiryDate directly as absolute expiration
2886
2888
  * - DAYS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration days
2887
2889
  * - HOURS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration hours
2888
2890
  * - MONTHS_FROM_ISSUANCE: Computes expiry as issuance date + validityDuration months
2889
2891
  * - END_OF_MONTH: Token expires at end of the month when issued
2890
2892
  * - END_OF_YEAR: Token expires at end of the year when issued
2893
+ *
2894
+ * Trigger-based types (computed from runtime context):
2895
+ * - TRIGGER_DATE: Uses date from context.validityDate (e.g., hotel check-out date)
2896
+ * - TRIGGER_DATE_RANGE: Uses context.validityEndDate directly (for explicit start/end scenarios)
2897
+ * - DAYS_FROM_TRIGGER: Computes expiry as context.validityDate + duration days
2898
+ * Duration from context.validityDuration OR template.validityDuration
2899
+ * - HOURS_FROM_TRIGGER: Same as above but hours
2891
2900
  */
2892
2901
  const TokenValidityTypes = {
2893
2902
  FIXED_DATE: 'fixed_date',
@@ -2896,6 +2905,11 @@ const TokenValidityTypes = {
2896
2905
  MONTHS_FROM_ISSUANCE: 'months_from_issuance',
2897
2906
  END_OF_MONTH: 'end_of_month',
2898
2907
  END_OF_YEAR: 'end_of_year',
2908
+ // Trigger-based types - require context at runtime
2909
+ TRIGGER_DATE: 'trigger_date',
2910
+ TRIGGER_DATE_RANGE: 'trigger_date_range',
2911
+ DAYS_FROM_TRIGGER: 'days_from_trigger',
2912
+ HOURS_FROM_TRIGGER: 'hours_from_trigger',
2899
2913
  };
2900
2914
  const TOKEN_VALIDITY_TYPE_VALUES = Object.values(TokenValidityTypes);
2901
2915
 
@@ -5924,6 +5938,8 @@ class CampaignApi {
5924
5938
  params.push(`tag=${encodeURIComponent(options.tag)}`);
5925
5939
  if (options?.businessId)
5926
5940
  params.push(`businessId=${encodeURIComponent(options.businessId)}`);
5941
+ if (options?.search)
5942
+ params.push(`search=${encodeURIComponent(options.search)}`);
5927
5943
  if (options?.sortBy)
5928
5944
  params.push(`sortBy=${options.sortBy}`);
5929
5945
  if (options?.sortOrder)
@@ -6290,7 +6306,9 @@ class CampaignService {
6290
6306
  /**
6291
6307
  * Get campaigns with pagination support
6292
6308
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all
6293
- * @param options.active - Filter by active status
6309
+ * @param options.active - Filter by active status (Admin only)
6310
+ * @param options.tag - Filter by tag
6311
+ * @param options.search - Free text search across name, description, and tags (case-insensitive)
6294
6312
  * @param options.businessId - Filter by business engagement
6295
6313
  * @param options.page - Page number (1-based, default: 1)
6296
6314
  * @param options.limit - Items per page (default: 50)
@@ -6466,15 +6484,23 @@ class RedemptionApi {
6466
6484
  * - Public users: Get active redemptions only
6467
6485
  * - Admin users: Get all redemptions with optional filtering
6468
6486
  *
6469
- * @param options - Filter and pagination options (active, adminAccess, page, limit, sortBy, sortOrder)
6487
+ * @param options - Filter and pagination options (active, search, adminAccess, page, limit, sortBy, sortOrder)
6470
6488
  * @returns Paginated response with redemptions
6471
6489
  */
6472
6490
  async getRedemptions(options) {
6473
- const params = buildPaginationParams(options);
6474
- if (options?.active !== undefined) {
6475
- params.append('active', String(options.active));
6476
- }
6477
- const response = await this.apiClient.get(`${this.basePath}?${params.toString()}`);
6491
+ const params = [];
6492
+ // Always include pagination params to ensure paginated response
6493
+ params.push(`page=${options?.page || 1}`);
6494
+ params.push(`limit=${options?.limit || 50}`);
6495
+ if (options?.active !== undefined)
6496
+ params.push(`active=${options.active}`);
6497
+ if (options?.search)
6498
+ params.push(`search=${encodeURIComponent(options.search)}`);
6499
+ if (options?.sortBy)
6500
+ params.push(`sortBy=${options.sortBy}`);
6501
+ if (options?.sortOrder)
6502
+ params.push(`sortOrder=${options.sortOrder}`);
6503
+ const response = await this.apiClient.get(`${this.basePath}?${params.join('&')}`);
6478
6504
  return normalizeToPaginated(response);
6479
6505
  }
6480
6506
  /**
@@ -6516,12 +6542,11 @@ class RedemptionApi {
6516
6542
  * - USER: Direct user redemption processing
6517
6543
  * - ADMIN: Can process redemptions for any account type
6518
6544
  * - BUSINESS: Process redemptions for customers
6545
+ *
6546
+ * @param request - Redemption request with optional context for dynamic NFT generation
6519
6547
  */
6520
- async redeemRedemption(redemptionId) {
6521
- const body = {
6522
- redemptionId: redemptionId,
6523
- };
6524
- return this.apiClient.post(this.basePath + '/redeems', body);
6548
+ async redeemRedemption(request) {
6549
+ return this.apiClient.post(this.basePath + '/redeems', request);
6525
6550
  }
6526
6551
  // ==========================================
6527
6552
  // REDEMPTION REDEEMS QUERIES (NEW ENDPOINTS)
@@ -6531,10 +6556,10 @@ class RedemptionApi {
6531
6556
  *
6532
6557
  * Role-based access with unified filtering:
6533
6558
  * - Users: See only their own redeems (userId/businessId filters ignored)
6534
- * - Admins: Can filter by userId, businessId, or redemptionId
6559
+ * - Admins: Can filter by userId, businessId, redemptionId, or status
6535
6560
  *
6536
- * @param filters - Filter and pagination options (redemptionId, userId, businessId, myRedeems, page, limit, sortBy, sortOrder)
6537
- * @param include - Optional relations to include (redemption, user, business)
6561
+ * @param filters - Filter and pagination options (redemptionId, userId, businessId, status, myRedeems, page, limit, sortBy, sortOrder)
6562
+ * @param include - Optional relations to include (redemption, user, business, transactions)
6538
6563
  * @returns Paginated response with redemption redeems
6539
6564
  */
6540
6565
  async getRedemptionRedeems(filters, include) {
@@ -6546,6 +6571,9 @@ class RedemptionApi {
6546
6571
  if (filters?.redemptionId) {
6547
6572
  params.append('redemptionId', filters.redemptionId);
6548
6573
  }
6574
+ if (filters?.status) {
6575
+ params.append('status', filters.status);
6576
+ }
6549
6577
  // Add include parameter
6550
6578
  if (include && include.length > 0) {
6551
6579
  include.forEach(relation => params.append('include', relation));
@@ -6560,6 +6588,8 @@ class RedemptionApi {
6560
6588
  params.append('userId', filters.userId);
6561
6589
  if (filters?.businessId)
6562
6590
  params.append('businessId', filters.businessId);
6591
+ if (filters?.status)
6592
+ params.append('status', filters.status);
6563
6593
  // Add include parameter
6564
6594
  if (include && include.length > 0) {
6565
6595
  include.forEach(relation => params.append('include', relation));
@@ -6588,13 +6618,13 @@ class RedemptionApi {
6588
6618
  * UNIFIED: Get user redemption history
6589
6619
  *
6590
6620
  * Uses convenience endpoint /redemption-redeems/me with optional filtering
6591
- * @param redemptionId - Optional filter by specific redemption
6621
+ * @param filters - Optional filters (redemptionId, status)
6592
6622
  * @param options - Pagination options (page, limit, sortBy, sortOrder)
6593
- * @param include - Optional relations to include (redemption, user, business)
6623
+ * @param include - Optional relations to include (redemption, user, business, transactions)
6594
6624
  * @returns Paginated response with user's redemption redeems
6595
6625
  */
6596
- async getUserRedeems(redemptionId, options, include) {
6597
- return this.getRedemptionRedeems({ myRedeems: true, redemptionId, ...options }, include);
6626
+ async getUserRedeems(filters, options, include) {
6627
+ return this.getRedemptionRedeems({ myRedeems: true, ...filters, ...options }, include);
6598
6628
  }
6599
6629
  // ==========================================
6600
6630
  // ADMIN OPERATIONS (Tenant Admin JWT)
@@ -6699,7 +6729,12 @@ class RedemptionService {
6699
6729
  /**
6700
6730
  * UNIFIED: Get redemptions with intelligent access control and pagination
6701
6731
  * @param options.active - Filter by active status
6732
+ * @param options.search - Free text search across name, description, tags (case-insensitive)
6702
6733
  * @param options.adminAccess - Force admin access
6734
+ * @param options.page - Page number (1-indexed)
6735
+ * @param options.limit - Results per page
6736
+ * @param options.sortBy - Field to sort by ('name' | 'createdAt')
6737
+ * @param options.sortOrder - Sort direction ('ASC' | 'DESC')
6703
6738
  */
6704
6739
  async getRedemptions(options) {
6705
6740
  return this.redemptionApi.getRedemptions(options);
@@ -6720,35 +6755,43 @@ class RedemptionService {
6720
6755
  // AUTHENTICATED OPERATIONS
6721
6756
  // ==========================================
6722
6757
  /**
6723
- * AUTH: Redeem a redemption
6758
+ * AUTH: Redeem a redemption with optional dynamic context
6759
+ *
6760
+ * @param request - Redemption request (can be just redemptionId string or full request with context)
6724
6761
  */
6725
- async redeemRedemption(redemptionId) {
6726
- return this.redemptionApi.redeemRedemption(redemptionId);
6762
+ async redeemRedemption(request) {
6763
+ // Support both simple string and full request object
6764
+ const requestBody = typeof request === 'string'
6765
+ ? { redemptionId: request }
6766
+ : request;
6767
+ return this.redemptionApi.redeemRedemption(requestBody);
6727
6768
  }
6728
6769
  /**
6729
6770
  * UNIFIED: Get redemption redeems with filtering and pagination
6730
- * @param filters - Filter options
6731
- * @param include - Optional relations to include (redemption, user, business)
6771
+ * @param filters - Filter options (redemptionId, userId, businessId, status, myRedeems)
6772
+ * @param include - Optional relations to include (redemption, user, business, transactions)
6732
6773
  */
6733
6774
  async getRedemptionRedeems(filters, include) {
6734
6775
  return this.redemptionApi.getRedemptionRedeems(filters, include);
6735
6776
  }
6736
6777
  /**
6737
- * Convenience: Get user redemptions with pagination
6778
+ * Convenience: Get user redemptions with pagination and filtering
6779
+ * @param filters - Optional filters (redemptionId, status)
6738
6780
  * @param options - Pagination options
6739
- * @param include - Optional relations to include (redemption, user, business)
6781
+ * @param include - Optional relations to include (redemption, user, business, transactions)
6740
6782
  */
6741
- async getUserRedeems(options, include) {
6742
- return this.redemptionApi.getUserRedeems(undefined, options, include);
6783
+ async getUserRedeems(filters, options, include) {
6784
+ return this.redemptionApi.getUserRedeems(filters, options, include);
6743
6785
  }
6744
6786
  // ==========================================
6745
6787
  // ADMIN OPERATIONS
6746
6788
  // ==========================================
6747
6789
  /**
6748
- * Convenience: Get redemptions as admin
6790
+ * Convenience: Get redemptions as admin with filtering and search
6791
+ * @param options - Filter options (active, search, pagination)
6749
6792
  */
6750
- async getRedemptionsAsAdmin(active, options) {
6751
- return this.redemptionApi.getRedemptions({ active, adminAccess: true, ...options });
6793
+ async getRedemptionsAsAdmin(options) {
6794
+ return this.redemptionApi.getRedemptions({ ...options, adminAccess: true });
6752
6795
  }
6753
6796
  /**
6754
6797
  * ADMIN: Create redemption
@@ -7281,7 +7324,7 @@ function buildAccountSelector(data) {
7281
7324
  * 🎯 Build a MINT transaction request (admin/business endpoint)
7282
7325
  * Creates tokens and assigns them to a recipient account
7283
7326
  *
7284
- * @example
7327
+ * @example Basic Mint
7285
7328
  * ```typescript
7286
7329
  * const request = buildMintRequest({
7287
7330
  * amount: 100,
@@ -7292,6 +7335,27 @@ function buildAccountSelector(data) {
7292
7335
  * engagedBusinessId: 'business-456' // optional: for tracking which business triggered the mint
7293
7336
  * });
7294
7337
  * ```
7338
+ *
7339
+ * @example ERC721 Mint with Dynamic Context
7340
+ * ```typescript
7341
+ * const request = buildMintRequest({
7342
+ * amount: 1,
7343
+ * contractAddress: '0x...',
7344
+ * contractTokenId: '123', // TokenMetadata ID
7345
+ * chainId: 137,
7346
+ * recipientAccountId: 'user-123',
7347
+ * recipientAccountType: AccountOwnerType.USER,
7348
+ * context: {
7349
+ * // Validity control
7350
+ * validityDate: '2026-04-15T14:00:00Z',
7351
+ * validityEndDate: '2026-04-20T11:00:00Z',
7352
+ * // AI prompt placeholders (available as {{context.xxx}})
7353
+ * guestName: 'John Doe',
7354
+ * roomNumber: '305',
7355
+ * awardReason: 'Employee of the Month'
7356
+ * }
7357
+ * });
7358
+ * ```
7295
7359
  */
7296
7360
  function buildMintRequest(data) {
7297
7361
  return {
@@ -7305,7 +7369,8 @@ function buildMintRequest(data) {
7305
7369
  accountAddress: data.recipientAccountAddress
7306
7370
  })
7307
7371
  },
7308
- ...(data.engagedBusinessId && { engagedBusinessId: data.engagedBusinessId })
7372
+ ...(data.engagedBusinessId && { engagedBusinessId: data.engagedBusinessId }),
7373
+ ...(data.context && { context: data.context })
7309
7374
  };
7310
7375
  }
7311
7376
  /**
@@ -11843,7 +11908,7 @@ class BusinessManager {
11843
11908
  *
11844
11909
  * // Get specific campaign details
11845
11910
  * const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
11846
- * console.log('Campaign:', campaign.title);
11911
+ * console.log('Campaign:', campaign.name);
11847
11912
  *
11848
11913
  * // User claims campaign reward
11849
11914
  * const claim = await sdk.campaigns.claimCampaign({
@@ -11859,7 +11924,7 @@ class BusinessManager {
11859
11924
  *
11860
11925
  * console.log('My Campaign Claims:');
11861
11926
  * userClaims.forEach(claim => {
11862
- * console.log(`- ${claim.campaign.title}`);
11927
+ * console.log(`- ${claim.campaign.name}`);
11863
11928
  * console.log(` Rewards: ${claim.rewards?.length || 0} tokens`);
11864
11929
  * console.log(` Date: ${claim.claimedAt}`);
11865
11930
  * });
@@ -11875,7 +11940,7 @@ class BusinessManager {
11875
11940
  * ```typescript
11876
11941
  * // Admin: Create seasonal campaign
11877
11942
  * const newCampaign = await sdk.campaigns.createCampaign({
11878
- * title: 'Holiday Rewards',
11943
+ * name: 'Holiday Rewards',
11879
11944
  * description: 'Special holiday loyalty bonuses',
11880
11945
  * startDate: '2024-12-01',
11881
11946
  * endDate: '2024-12-31',
@@ -11914,7 +11979,7 @@ class CampaignManager {
11914
11979
  * const campaign = await sdk.campaigns.getCampaignById('summer-promo-2024');
11915
11980
  *
11916
11981
  * console.log('Campaign Details:');
11917
- * console.log('Title:', campaign.title);
11982
+ * console.log('Name:', campaign.name);
11918
11983
  * console.log('Description:', campaign.description);
11919
11984
  * console.log('Active:', campaign.isActive);
11920
11985
  * console.log('Period:', `${campaign.startDate} to ${campaign.endDate}`);
@@ -12028,7 +12093,7 @@ class CampaignManager {
12028
12093
  * console.log(`Campaign History (${userClaims.length} claims):`);
12029
12094
  *
12030
12095
  * userClaims.forEach((claim, index) => {
12031
- * console.log(`\n${index + 1}. ${claim.campaign.title}`);
12096
+ * console.log(`\n${index + 1}. ${claim.campaign.name}`);
12032
12097
  * console.log(` Claimed: ${new Date(claim.claimedAt).toLocaleDateString()}`);
12033
12098
  * console.log(` Business: ${claim.business?.displayName || 'N/A'}`);
12034
12099
  *
@@ -12058,7 +12123,7 @@ class CampaignManager {
12058
12123
  * Returns campaigns with pagination metadata for efficient data loading.
12059
12124
  * Intelligent access: Public gets active only, Business gets own campaigns, Admin gets all.
12060
12125
  *
12061
- * @param options - Pagination and filter options (page, limit, sortBy, sortOrder, active, businessId, include)
12126
+ * @param options - Pagination and filter options (page, limit, sortBy, sortOrder, active, tag, search, businessId, include)
12062
12127
  * @returns Promise resolving to paginated campaigns with metadata
12063
12128
  *
12064
12129
  * @example
@@ -12075,6 +12140,19 @@ class CampaignManager {
12075
12140
  * limit: 20
12076
12141
  * });
12077
12142
  *
12143
+ * // Search campaigns by name or description
12144
+ * const searchResults = await sdk.campaigns.getCampaigns({
12145
+ * search: 'summer rewards',
12146
+ * page: 1,
12147
+ * limit: 20
12148
+ * });
12149
+ *
12150
+ * // Filter by tag
12151
+ * const taggedCampaigns = await sdk.campaigns.getCampaigns({
12152
+ * tag: 'seasonal',
12153
+ * active: true
12154
+ * });
12155
+ *
12078
12156
  * // Get active campaigns sorted by creation date
12079
12157
  * const activeCampaigns = await sdk.campaigns.getCampaigns({
12080
12158
  * active: true,
@@ -12108,7 +12186,7 @@ class CampaignManager {
12108
12186
  * ```typescript
12109
12187
  * // Admin operation - create seasonal campaign
12110
12188
  * const campaign = await sdk.campaigns.createCampaign({
12111
- * title: 'Summer Travel Rewards',
12189
+ * name: 'Summer Travel Rewards',
12112
12190
  * description: 'Earn bonus points for summer bookings',
12113
12191
  * startDate: '2024-06-01T00:00:00Z',
12114
12192
  * endDate: '2024-08-31T23:59:59Z',
@@ -12117,7 +12195,7 @@ class CampaignManager {
12117
12195
  * campaignType: 'SEASONAL'
12118
12196
  * });
12119
12197
  *
12120
- * console.log('Campaign created:', campaign.title);
12198
+ * console.log('Campaign created:', campaign.name);
12121
12199
  * console.log('Campaign ID:', campaign.id);
12122
12200
  *
12123
12201
  * // Set up campaign rewards
@@ -12147,13 +12225,13 @@ class CampaignManager {
12147
12225
  * ```typescript
12148
12226
  * // Admin operation - extend campaign duration
12149
12227
  * const updated = await sdk.campaigns.updateCampaign('summer-2024', {
12150
- * title: 'Extended Summer Travel Rewards',
12228
+ * name: 'Extended Summer Travel Rewards',
12151
12229
  * description: 'Earn bonus points for summer and early fall bookings',
12152
12230
  * endDate: '2024-09-30T23:59:59Z', // Extended end date
12153
12231
  * isActive: true
12154
12232
  * });
12155
12233
  *
12156
- * console.log('Campaign updated:', updated.title);
12234
+ * console.log('Campaign updated:', updated.name);
12157
12235
  * console.log('New end date:', updated.endDate);
12158
12236
  * ```
12159
12237
  */
@@ -12618,7 +12696,7 @@ class CampaignManager {
12618
12696
  * console.log(`Total campaigns claimed: ${userClaims.length}`);
12619
12697
  *
12620
12698
  * userClaims.forEach((claim, index) => {
12621
- * console.log(`\n${index + 1}. ${claim.campaign.title}`);
12699
+ * console.log(`\n${index + 1}. ${claim.campaign.name}`);
12622
12700
  * console.log(` Date: ${claim.claimedAt}`);
12623
12701
  * console.log(` Business: ${claim.business?.displayName || 'Direct'}`);
12624
12702
  * console.log(` Rewards: ${claim.rewards?.length || 0} items`);
@@ -12655,8 +12733,8 @@ class CampaignManager {
12655
12733
  *
12656
12734
  * // Analyze claims by campaign
12657
12735
  * const claimsByCampaign = businessClaims.reduce((acc, claim) => {
12658
- * const title = claim.campaign.title;
12659
- * acc[title] = (acc[title] || 0) + 1;
12736
+ * const campaignName = claim.campaign.name;
12737
+ * acc[campaignName] = (acc[campaignName] || 0) + 1;
12660
12738
  * return acc;
12661
12739
  * }, {});
12662
12740
  *
@@ -12860,6 +12938,7 @@ class RedemptionManager {
12860
12938
  *
12861
12939
  * @param options - Optional filters and pagination:
12862
12940
  * - `active`: Filter by active status (true/false). Admins only - regular users always see active offers
12941
+ * - `search`: Free text search across name, description, tags (case-insensitive)
12863
12942
  * - Pagination options: `page`, `limit`, `sortBy`, `sortOrder`
12864
12943
  * @returns Promise resolving to paginated redemption offers
12865
12944
  *
@@ -12872,9 +12951,9 @@ class RedemptionManager {
12872
12951
  * console.log(`Showing ${offers.length} of ${pagination.total} offers`);
12873
12952
  *
12874
12953
  * offers.forEach(offer => {
12875
- * console.log(`\n${offer.title}`);
12954
+ * console.log(`\n${offer.name}`);
12876
12955
  * console.log(`${offer.description}`);
12877
- * console.log(`Type: ${offer.redemptionType?.name || 'General'}`);
12956
+ * console.log(`Type: ${offer.type?.name || 'General'}`);
12878
12957
  *
12879
12958
  * if (offer.tokenUnits?.length) {
12880
12959
  * console.log('Cost:');
@@ -12897,6 +12976,23 @@ class RedemptionManager {
12897
12976
  * console.log(`\n${affordableOffers.length} offers within your budget`);
12898
12977
  * ```
12899
12978
  *
12979
+ * @example Search Redemptions
12980
+ * ```typescript
12981
+ * // Search by text (matches name, description, tags)
12982
+ * const { data: coffeeOffers } = await sdk.redemptions.getRedemptions({
12983
+ * search: 'coffee'
12984
+ * });
12985
+ *
12986
+ * console.log(`Found ${coffeeOffers.length} coffee-related offers`);
12987
+ *
12988
+ * // Search with sorting
12989
+ * const { data: sortedResults } = await sdk.redemptions.getRedemptions({
12990
+ * search: 'discount',
12991
+ * sortBy: 'name',
12992
+ * sortOrder: 'ASC'
12993
+ * });
12994
+ * ```
12995
+ *
12900
12996
  * @example Admin - Manage All Redemptions
12901
12997
  * ```typescript
12902
12998
  * // Admins can see all redemptions and filter by status
@@ -12909,14 +13005,11 @@ class RedemptionManager {
12909
13005
  * console.log(`Active offers: ${activeOnly.length}`);
12910
13006
  * console.log(`Inactive offers: ${inactiveOnly.length}`);
12911
13007
  *
12912
- * // Analyze by type
12913
- * const offersByType = allRedemptions.reduce((acc, offer) => {
12914
- * const type = offer.redemptionType?.name || 'Unspecified';
12915
- * acc[type] = (acc[type] || 0) + 1;
12916
- * return acc;
12917
- * }, {});
12918
- *
12919
- * console.log('\nBy type:', offersByType);
13008
+ * // Search inactive redemptions
13009
+ * const { data: inactiveVouchers } = await sdk.redemptions.getRedemptions({
13010
+ * active: false,
13011
+ * search: 'voucher'
13012
+ * });
12920
13013
  * ```
12921
13014
  *
12922
13015
  * @example Pagination
@@ -12987,7 +13080,12 @@ class RedemptionManager {
12987
13080
  * redemption confirmation details. Creates a permanent redemption record
12988
13081
  * and may generate voucher codes or instructions.
12989
13082
  *
13083
+ * Supports dynamic context for personalized NFT generation when the redemption
13084
+ * issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
13085
+ * and custom metadata.
13086
+ *
12990
13087
  * @param redemptionId - ID of the redemption offer to redeem
13088
+ * @param context - Optional dynamic context for NFT personalization (validity dates, AI prompts)
12991
13089
  * @returns Promise resolving to redemption result with confirmation details
12992
13090
  * @throws {PersApiError} When redemption is not available or user lacks required tokens
12993
13091
  *
@@ -13035,6 +13133,30 @@ class RedemptionManager {
13035
13133
  * }
13036
13134
  * ```
13037
13135
  *
13136
+ * @example Redemption with Dynamic Context (Personalized NFT)
13137
+ * ```typescript
13138
+ * // Event ticket with dynamic validity and personalization
13139
+ * const result = await sdk.redemptions.redeem('event-ticket-123', {
13140
+ * // Validity - token valid for event duration + 2 hours
13141
+ * validityDate: new Date().toISOString(),
13142
+ * validityDuration: 8, // 8 hours
13143
+ *
13144
+ * // AI prompt context - available as {{context.xxx}} in prompts
13145
+ * attendeeName: 'John Doe',
13146
+ * ticketTier: 'VIP',
13147
+ * seatSection: 'A12',
13148
+ * });
13149
+ *
13150
+ * // Hotel stay NFT with check-in/check-out dates
13151
+ * const hotelResult = await sdk.redemptions.redeem('hotel-nft-456', {
13152
+ * validityDate: '2026-04-15T14:00:00Z', // Check-in
13153
+ * validityEndDate: '2026-04-20T11:00:00Z', // Check-out
13154
+ * guestName: 'Jane Smith',
13155
+ * roomNumber: '305',
13156
+ * roomType: 'Suite',
13157
+ * });
13158
+ * ```
13159
+ *
13038
13160
  * @example Redemption with Pre-Validation
13039
13161
  * ```typescript
13040
13162
  * // Get offer details first
@@ -13058,13 +13180,16 @@ class RedemptionManager {
13058
13180
  * }
13059
13181
  * ```
13060
13182
  */
13061
- async redeem(redemptionId) {
13062
- const result = await this.redemptionService.redeemRedemption(redemptionId);
13183
+ async redeem(redemptionId, context) {
13184
+ const request = context
13185
+ ? { redemptionId, context }
13186
+ : { redemptionId };
13187
+ const result = await this.redemptionService.redeemRedemption(request);
13063
13188
  this.events?.emitSuccess({
13064
13189
  domain: 'redemption',
13065
13190
  type: 'redeem_success',
13066
13191
  userMessage: 'Reward redeemed successfully',
13067
- details: { redemptionId }
13192
+ details: { redemptionId, hasContext: !!context }
13068
13193
  });
13069
13194
  return result;
13070
13195
  }
@@ -13084,9 +13209,9 @@ class RedemptionManager {
13084
13209
  * console.log(`Redemption History (${userRedemptions.length} of ${pagination.total} items):`);
13085
13210
  *
13086
13211
  * userRedemptions.forEach((redemption, index) => {
13087
- * console.log(`\n${index + 1}. ${redemption.redemption.title}`);
13212
+ * console.log(`\n${index + 1}. ${redemption.redemption.name}`);
13088
13213
  * console.log(` Date: ${new Date(redemption.redeemedAt).toLocaleDateString()}`);
13089
- * console.log(` Type: ${redemption.redemption.redemptionType?.name || 'General'}`);
13214
+ * console.log(` Type: ${redemption.redemption.type?.name || 'General'}`);
13090
13215
  *
13091
13216
  * if (redemption.voucherCode) {
13092
13217
  * console.log(` Voucher: ${redemption.voucherCode}`);
@@ -13127,9 +13252,19 @@ class RedemptionManager {
13127
13252
  * console.log(`${type}: ${count} redemptions`);
13128
13253
  * });
13129
13254
  * ```
13255
+ *
13256
+ * @example Filter by Status
13257
+ * ```typescript
13258
+ * import { ProcessRecordStatus } from '@explorins/pers-sdk';
13259
+ *
13260
+ * // Get only completed redemptions
13261
+ * const { data: completed } = await sdk.redemptions.getUserRedemptions(
13262
+ * { status: ProcessRecordStatus.COMPLETED }
13263
+ * );
13264
+ * ```
13130
13265
  */
13131
- async getUserRedemptions(options, include) {
13132
- return this.redemptionService.getUserRedeems(options, include);
13266
+ async getUserRedemptions(filters, options, include) {
13267
+ return this.redemptionService.getUserRedeems(filters, options, include);
13133
13268
  }
13134
13269
  /**
13135
13270
  * Admin: Get all redemption redeems with filtering and enriched data
@@ -13195,7 +13330,7 @@ class RedemptionManager {
13195
13330
  * ```typescript
13196
13331
  * // Admin operation - create discount redemption
13197
13332
  * const newRedemption = await sdk.redemptions.createRedemption({
13198
- * title: '20% Off Weekend Stay',
13333
+ * name: '20% Off Weekend Stay',
13199
13334
  * description: 'Get 20% off your next weekend booking at partner hotels',
13200
13335
  * redemptionTypeId: 'discount-type-id',
13201
13336
  * isActive: true,
@@ -13204,7 +13339,7 @@ class RedemptionManager {
13204
13339
  * validUntil: '2024-08-31T23:59:59Z'
13205
13340
  * });
13206
13341
  *
13207
- * console.log('New redemption created:', newRedemption.title);
13342
+ * console.log('New redemption created:', newRedemption.name);
13208
13343
  * console.log('Redemption ID:', newRedemption.id);
13209
13344
  *
13210
13345
  * // Set redemption cost
@@ -13236,13 +13371,13 @@ class RedemptionManager {
13236
13371
  * ```typescript
13237
13372
  * // Admin operation - update redemption details
13238
13373
  * const updated = await sdk.redemptions.updateRedemption('weekend-discount', {
13239
- * title: '25% Off Weekend Stay - Extended!',
13374
+ * name: '25% Off Weekend Stay - Extended!',
13240
13375
  * description: 'Enhanced discount - now 25% off weekend bookings',
13241
13376
  * availableQuantity: 150, // Increased availability
13242
13377
  * validUntil: '2024-09-30T23:59:59Z' // Extended validity
13243
13378
  * });
13244
13379
  *
13245
- * console.log('Redemption updated:', updated.title);
13380
+ * console.log('Redemption updated:', updated.name);
13246
13381
  * console.log('New quantity available:', updated.availableQuantity);
13247
13382
  * ```
13248
13383
  */
@@ -17541,7 +17676,9 @@ class PersSDK {
17541
17676
  }
17542
17677
  }
17543
17678
  try {
17544
- // Fetch user data to validate tokens and restore session
17679
+ // Ensure token is valid (refresh if expired) before fetching user
17680
+ await this.auth.ensureValidToken();
17681
+ // Fetch user data to restore session
17545
17682
  const userData = await this.auth.getCurrentUser();
17546
17683
  // Emit event through proper event emitter
17547
17684
  this._events.emitSuccess({
@@ -18401,21 +18538,45 @@ function createReactNativeAuthProvider(projectKey, config = {}) {
18401
18538
  });
18402
18539
  }
18403
18540
 
18404
- // Conditionally require quick-crypto for Native platforms only
18405
- let crypto$1;
18406
- if (reactNative.Platform.OS !== 'web') {
18541
+ // Lazy-loaded crypto module - initialized on first use to avoid native module timing issues
18542
+ let _crypto = null;
18543
+ let _cryptoInitialized = false;
18544
+ /**
18545
+ * Lazily load the crypto module.
18546
+ *
18547
+ * Supports customer pre-initialization via global.__QuickCrypto for environments
18548
+ * where native module timing is unpredictable (e.g., certain React Native setups).
18549
+ *
18550
+ * @returns The crypto module or null if unavailable
18551
+ */
18552
+ function getCrypto() {
18553
+ if (_cryptoInitialized) {
18554
+ return _crypto;
18555
+ }
18556
+ _cryptoInitialized = true;
18557
+ if (reactNative.Platform.OS === 'web') {
18558
+ // On Web, we shouldn't be using this provider (Core SDK has WebDPoPProvider)
18559
+ _crypto = {
18560
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); },
18561
+ randomUUID: () => globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`
18562
+ };
18563
+ return _crypto;
18564
+ }
18565
+ // Check for customer pre-initialized global (for native module timing issues)
18566
+ if (typeof global !== 'undefined' && global.__QuickCrypto) {
18567
+ _crypto = global.__QuickCrypto;
18568
+ return _crypto;
18569
+ }
18570
+ // Standard lazy load
18407
18571
  try {
18408
- crypto$1 = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
18572
+ _crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
18409
18573
  }
18410
18574
  catch (e) {
18411
- console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
18575
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto.', e);
18576
+ console.warn('Tip: If you encounter timing issues, pre-initialize in index.js: global.__QuickCrypto = require("react-native-quick-crypto")');
18577
+ _crypto = null;
18412
18578
  }
18413
- }
18414
- else {
18415
- // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
18416
- crypto$1 = {
18417
- generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
18418
- };
18579
+ return _crypto;
18419
18580
  }
18420
18581
  class ReactNativeDPoPProvider {
18421
18582
  /**
@@ -18425,13 +18586,17 @@ class ReactNativeDPoPProvider {
18425
18586
  * Falls back to Node.js crypto API on iOS if needed.
18426
18587
  */
18427
18588
  async generateKeyPair(options) {
18589
+ const crypto = getCrypto();
18590
+ if (!crypto) {
18591
+ throw new Error('Crypto module not available. Install react-native-quick-crypto or pre-initialize global.__QuickCrypto');
18592
+ }
18428
18593
  // Try WebCrypto API first (works on both iOS and Android)
18429
- if (crypto$1.subtle) {
18594
+ if (crypto.subtle) {
18430
18595
  try {
18431
- const keyPair = await crypto$1.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
18596
+ const keyPair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, // extractable - required for JWK export
18432
18597
  ['sign', 'verify']);
18433
- const publicKeyJwk = await crypto$1.subtle.exportKey('jwk', keyPair.publicKey);
18434
- const privateKeyJwk = await crypto$1.subtle.exportKey('jwk', keyPair.privateKey);
18598
+ const publicKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
18599
+ const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
18435
18600
  return {
18436
18601
  publicKey: publicKeyJwk,
18437
18602
  privateKey: privateKeyJwk
@@ -18443,7 +18608,7 @@ class ReactNativeDPoPProvider {
18443
18608
  }
18444
18609
  // Fallback: Node.js crypto API (works on iOS)
18445
18610
  return new Promise((resolve, reject) => {
18446
- crypto$1.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
18611
+ crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
18447
18612
  if (err)
18448
18613
  return reject(err);
18449
18614
  try {
@@ -18458,6 +18623,10 @@ class ReactNativeDPoPProvider {
18458
18623
  });
18459
18624
  }
18460
18625
  async signProof(payload, keyPair) {
18626
+ const crypto = getCrypto();
18627
+ if (!crypto) {
18628
+ throw new Error('Crypto module not available for signing');
18629
+ }
18461
18630
  // 1. Construct Header
18462
18631
  const header = {
18463
18632
  typ: 'dpop+jwt',
@@ -18468,17 +18637,17 @@ class ReactNativeDPoPProvider {
18468
18637
  const finalPayload = {
18469
18638
  ...payload,
18470
18639
  iat: payload.iat || Math.floor(Date.now() / 1000),
18471
- jti: payload.jti || crypto$1.randomUUID()
18640
+ jti: payload.jti || (crypto.randomUUID?.() || `${Date.now()}-${Math.random()}`)
18472
18641
  };
18473
18642
  // 3. Encode
18474
18643
  const b64Header = this.base64Url(JSON.stringify(header));
18475
18644
  const b64Payload = this.base64Url(JSON.stringify(finalPayload));
18476
18645
  const signingInput = `${b64Header}.${b64Payload}`;
18477
18646
  // 4. Sign - try WebCrypto first, fallback to Node.js crypto
18478
- if (crypto$1.subtle) {
18647
+ if (crypto.subtle) {
18479
18648
  try {
18480
- const privateKey = await crypto$1.subtle.importKey('jwk', keyPair.privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
18481
- const signatureBuffer = await crypto$1.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, buffer.Buffer.from(signingInput));
18649
+ const privateKey = await crypto.subtle.importKey('jwk', keyPair.privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
18650
+ const signatureBuffer = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, buffer.Buffer.from(signingInput));
18482
18651
  // WebCrypto returns signature in IEEE P1363 format (r || s), which is what we need for JWT
18483
18652
  return `${signingInput}.${this.base64UrlBuffer(buffer.Buffer.from(signatureBuffer))}`;
18484
18653
  }
@@ -18487,24 +18656,28 @@ class ReactNativeDPoPProvider {
18487
18656
  }
18488
18657
  }
18489
18658
  // Fallback: Node.js crypto API
18490
- const sign = crypto$1.createSign('SHA256');
18659
+ const sign = crypto.createSign('SHA256');
18491
18660
  sign.update(signingInput);
18492
- const privateKeyObj = crypto$1.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
18661
+ const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
18493
18662
  const signature = sign.sign(privateKeyObj);
18494
18663
  return `${signingInput}.${this.base64UrlBuffer(signature)}`;
18495
18664
  }
18496
18665
  async hashAccessToken(accessToken) {
18666
+ const crypto = getCrypto();
18667
+ if (!crypto) {
18668
+ throw new Error('Crypto module not available for hashing');
18669
+ }
18497
18670
  // Try WebCrypto first
18498
- if (crypto$1.subtle) {
18671
+ if (crypto.subtle) {
18499
18672
  try {
18500
- const hashBuffer = await crypto$1.subtle.digest('SHA-256', buffer.Buffer.from(accessToken));
18673
+ const hashBuffer = await crypto.subtle.digest('SHA-256', buffer.Buffer.from(accessToken));
18501
18674
  return this.base64UrlBuffer(buffer.Buffer.from(hashBuffer));
18502
18675
  }
18503
18676
  catch (err) {
18504
18677
  // Fallback to Node.js crypto
18505
18678
  }
18506
18679
  }
18507
- const hash = crypto$1.createHash('sha256').update(accessToken).digest();
18680
+ const hash = crypto.createHash('sha256').update(accessToken).digest();
18508
18681
  return this.base64UrlBuffer(hash);
18509
18682
  }
18510
18683
  // --- Helpers ---
@@ -40125,10 +40298,17 @@ const useWeb3 = () => {
40125
40298
  * **Basic Usage:**
40126
40299
  * ```typescript
40127
40300
  * import { useTokenBalances, useTokens, usePersSDK } from '@explorins/pers-sdk-react-native';
40301
+ * import { useState, useEffect } from 'react';
40128
40302
  *
40129
40303
  * function TokenBalancesScreen() {
40130
40304
  * const { user } = usePersSDK();
40131
- * const { availableTokens } = useTokens();
40305
+ * const { getTokens } = useTokens();
40306
+ * const [availableTokens, setAvailableTokens] = useState([]);
40307
+ *
40308
+ * // Load tokens on mount
40309
+ * useEffect(() => {
40310
+ * getTokens().then(result => setAvailableTokens(result.data));
40311
+ * }, []);
40132
40312
  *
40133
40313
  * // App explicitly selects which wallet to use
40134
40314
  * const walletAddress = user?.wallets?.[0]?.address;
@@ -40383,15 +40563,41 @@ const SigningStatus = {
40383
40563
  EXPIRED: 'expired'
40384
40564
  };
40385
40565
 
40386
- // Dynamic import the signer SDK to avoid build issues with static dependencies
40387
- let createPersSignerSDK = null;
40388
- try {
40389
- const signerModule = require('@explorins/pers-signer/react-native');
40390
- createPersSignerSDK = signerModule.createPersSignerSDK;
40391
- }
40392
- catch (error) {
40393
- console.warn('[useTransactionSigner] PERS Signer SDK not available:', error.message);
40394
- console.warn('[useTransactionSigner] Real blockchain signing will not be available');
40566
+ // Lazy-loaded signer SDK factory - initialized on first use to avoid native module timing issues
40567
+ let _createPersSignerSDK = null;
40568
+ let _signerInitAttempted = false;
40569
+ /**
40570
+ * Lazily load the PERS Signer SDK.
40571
+ *
40572
+ * Supports customer pre-initialization via global.__PersSigner for environments
40573
+ * where native module timing is unpredictable (e.g., certain React Native setups).
40574
+ *
40575
+ * @returns The createPersSignerSDK function or null if unavailable
40576
+ */
40577
+ function getSignerSDKFactory() {
40578
+ if (_signerInitAttempted) {
40579
+ // Already tried - return cached result (could be null if failed)
40580
+ return _createPersSignerSDK;
40581
+ }
40582
+ _signerInitAttempted = true;
40583
+ // Check for customer pre-initialized global (for native module timing issues)
40584
+ if (typeof global !== 'undefined' && global.__PersSigner?.createPersSignerSDK) {
40585
+ _createPersSignerSDK = global.__PersSigner.createPersSignerSDK;
40586
+ return _createPersSignerSDK;
40587
+ }
40588
+ // Standard lazy load
40589
+ try {
40590
+ const signerModule = require('@explorins/pers-signer/react-native');
40591
+ _createPersSignerSDK = signerModule.createPersSignerSDK;
40592
+ return _createPersSignerSDK;
40593
+ }
40594
+ catch (error) {
40595
+ console.warn('[useTransactionSigner] PERS Signer SDK not available:', error.message);
40596
+ console.warn('[useTransactionSigner] Real blockchain signing will not be available');
40597
+ console.warn('[useTransactionSigner] Tip: If timing issues, pre-initialize in index.js: global.__PersSigner = require("@explorins/pers-signer/react-native")');
40598
+ _createPersSignerSDK = null; // Mark as unavailable
40599
+ return null;
40600
+ }
40395
40601
  }
40396
40602
  // Constants - TODO: Move to environment config later
40397
40603
  const DEFAULT_ETHERS_PROVIDER = "https://sepolia.infura.io/v3/2781b4b5242343d5b0954c98f287b29e";
@@ -40581,6 +40787,7 @@ const useTransactionSigner = () => {
40581
40787
  // Auto-initialize signer SDK when PERS SDK is ready
40582
40788
  react.useEffect(() => {
40583
40789
  const initializeSignerSDK = async () => {
40790
+ const createPersSignerSDK = getSignerSDKFactory();
40584
40791
  if (!createPersSignerSDK) {
40585
40792
  console.warn('[useTransactionSigner] Signer SDK not available');
40586
40793
  return;
@@ -40695,7 +40902,7 @@ const useTransactionSigner = () => {
40695
40902
  if (!isSignerInitialized || !signerSDKRef.current) {
40696
40903
  throw new Error('Transaction signer not initialized');
40697
40904
  }
40698
- if (!createPersSignerSDK) {
40905
+ if (!getSignerSDKFactory()) {
40699
40906
  throw new Error('PERS Signer SDK not available. Blockchain signing is not supported.');
40700
40907
  }
40701
40908
  // Create status callback wrapper that updates both hook state and calls user callback
@@ -40839,7 +41046,7 @@ const useTransactionSigner = () => {
40839
41046
  * );
40840
41047
  * ```
40841
41048
  */
40842
- isSignerAvailable: isSignerInitialized && !!createPersSignerSDK,
41049
+ isSignerAvailable: isSignerInitialized && !!getSignerSDKFactory(),
40843
41050
  /**
40844
41051
  * Current signing status for UI feedback
40845
41052
  *
@@ -41408,6 +41615,63 @@ const useCampaigns = () => {
41408
41615
  throw error;
41409
41616
  }
41410
41617
  }, [sdk, isInitialized]);
41618
+ /**
41619
+ * Claim a campaign reward with optional dynamic context
41620
+ *
41621
+ * Triggers a campaign claim for the authenticated user. The campaign must be active
41622
+ * and the user must meet all eligibility requirements. Supports dynamic context for
41623
+ * personalized NFT generation when the campaign issues ERC721 tokens.
41624
+ *
41625
+ * @param request - Campaign claim request with optional context
41626
+ * @param request.campaignId - ID of the campaign to claim (optional if using triggerSourceId)
41627
+ * @param request.triggerSourceId - ID of trigger source (QR, NFC, etc.)
41628
+ * @param request.context - Dynamic context for NFT personalization
41629
+ * @param request.metadata - Additional metadata for the claim
41630
+ * @param request.latitude - User's latitude for geo-validated claims
41631
+ * @param request.longitude - User's longitude for geo-validated claims
41632
+ * @returns Promise resolving to campaign claim result
41633
+ *
41634
+ * @example Basic campaign claim
41635
+ * ```typescript
41636
+ * const claim = await claimCampaign({
41637
+ * triggerSourceId: 'qr-code-123',
41638
+ * });
41639
+ * ```
41640
+ *
41641
+ * @example Campaign claim with dynamic context (personalized NFT)
41642
+ * ```typescript
41643
+ * import { DynamicContext } from '@explorins/pers-sdk-react-native';
41644
+ *
41645
+ * const context: DynamicContext = {
41646
+ * // Validity - token valid for 30 days
41647
+ * validityDate: new Date().toISOString(),
41648
+ * validityDuration: 30,
41649
+ *
41650
+ * // AI prompt context - available as {{context.xxx}} in prompts
41651
+ * visitorName: 'John Doe',
41652
+ * visitLocation: 'Grand Hotel Lobby',
41653
+ * membershipTier: 'Gold',
41654
+ * };
41655
+ *
41656
+ * const claim = await claimCampaign({
41657
+ * triggerSourceId: 'hotel-checkin-qr',
41658
+ * context,
41659
+ * });
41660
+ * ```
41661
+ *
41662
+ * @example Geo-validated claim with context
41663
+ * ```typescript
41664
+ * const claim = await claimCampaign({
41665
+ * triggerSourceId: 'museum-entrance-nfc',
41666
+ * latitude: 48.8606,
41667
+ * longitude: 2.3376,
41668
+ * context: {
41669
+ * visitorType: 'Adult',
41670
+ * ticketCategory: 'General Admission',
41671
+ * }
41672
+ * });
41673
+ * ```
41674
+ */
41411
41675
  const claimCampaign = react.useCallback(async (request) => {
41412
41676
  if (!isInitialized || !sdk) {
41413
41677
  throw new Error('SDK not initialized. Call initialize() first.');
@@ -41435,7 +41699,7 @@ const useCampaigns = () => {
41435
41699
  * // Get user's claims with campaign details
41436
41700
  * const { data: claims } = await getUserClaims({ include: ['campaign', 'business'] });
41437
41701
  * claims.forEach(claim => {
41438
- * console.log('Campaign:', claim.included?.campaign?.title);
41702
+ * console.log('Campaign:', claim.included?.campaign?.name);
41439
41703
  * });
41440
41704
  *
41441
41705
  * // With pagination
@@ -41677,6 +41941,35 @@ const useRedemptions = () => {
41677
41941
  * **Administrators:** Can see all redemptions and filter by active/inactive status
41678
41942
  *
41679
41943
  * The backend enforces permission-based filtering automatically.
41944
+ *
41945
+ * @param options - Filter and pagination options
41946
+ * @param options.active - Filter by active status (Admin only)
41947
+ * @param options.search - Free text search across name, description, tags
41948
+ * @param options.page - Page number (1-indexed)
41949
+ * @param options.limit - Results per page
41950
+ * @param options.sortBy - Field to sort by ('name' | 'createdAt')
41951
+ * @param options.sortOrder - Sort direction ('ASC' | 'DESC')
41952
+ *
41953
+ * @example Basic usage
41954
+ * ```typescript
41955
+ * const { data: offers } = await getRedemptions();
41956
+ * ```
41957
+ *
41958
+ * @example Search redemptions
41959
+ * ```typescript
41960
+ * const { data: coffeeOffers } = await getRedemptions({ search: 'coffee' });
41961
+ * ```
41962
+ *
41963
+ * @example With pagination and sorting
41964
+ * ```typescript
41965
+ * const { data, pagination } = await getRedemptions({
41966
+ * search: 'discount',
41967
+ * page: 1,
41968
+ * limit: 10,
41969
+ * sortBy: 'name',
41970
+ * sortOrder: 'ASC'
41971
+ * });
41972
+ * ```
41680
41973
  */
41681
41974
  const getRedemptions = react.useCallback(async (options) => {
41682
41975
  if (!isInitialized || !sdk) {
@@ -41692,27 +41985,32 @@ const useRedemptions = () => {
41692
41985
  }
41693
41986
  }, [sdk, isInitialized]);
41694
41987
  /**
41695
- * Get user's redemption history with optional pagination and include relations
41988
+ * Get user's redemption history with optional filters, pagination and include relations
41696
41989
  *
41990
+ * @param filters - Optional filters (redemptionId, status)
41697
41991
  * @param options - Pagination options (page, limit, sortBy, sortOrder)
41698
- * @param include - Relations to include: 'redemption', 'user', 'business'
41992
+ * @param include - Relations to include: 'redemption', 'user', 'business', 'transactions'
41699
41993
  * @returns Promise resolving to paginated redemption history
41700
41994
  *
41701
41995
  * @example
41702
41996
  * ```typescript
41703
41997
  * // Get first page of user redemptions
41704
- * const { data: redeems, pagination } = await getUserRedemptions({ page: 1, limit: 10 });
41998
+ * const { data: redeems, pagination } = await getUserRedemptions({}, { page: 1, limit: 10 });
41705
41999
  * console.log(`Page ${pagination.page} of ${pagination.pages}`);
41706
42000
  *
42001
+ * // Get only completed redemptions
42002
+ * import { ProcessRecordStatus } from '@explorins/pers-shared';
42003
+ * const { data: completed } = await getUserRedemptions({ status: ProcessRecordStatus.COMPLETED });
42004
+ *
41707
42005
  * // Get user redemptions with full redemption details
41708
- * const { data: redeems } = await getUserRedemptions({}, ['redemption', 'business']);
42006
+ * const { data: redeems } = await getUserRedemptions({}, {}, ['redemption', 'business']);
41709
42007
  * redeems.forEach(redeem => {
41710
- * console.log('Redemption:', redeem.included?.redemption?.title);
42008
+ * console.log('Redemption:', redeem.included?.redemption?.name);
41711
42009
  * console.log('Business:', redeem.included?.business?.displayName);
41712
42010
  * });
41713
42011
  * ```
41714
42012
  */
41715
- const getUserRedemptions = react.useCallback(async (options, include) => {
42013
+ const getUserRedemptions = react.useCallback(async (filters, options, include) => {
41716
42014
  if (!isInitialized || !sdk) {
41717
42015
  throw new Error('SDK not initialized. Call initialize() first.');
41718
42016
  }
@@ -41721,7 +42019,7 @@ const useRedemptions = () => {
41721
42019
  return { data: [], pagination: { page: 1, limit: 0, total: 0, pages: 0, hasNext: false, hasPrev: false } };
41722
42020
  }
41723
42021
  try {
41724
- const result = await sdk.redemptions.getUserRedemptions(options, include);
42022
+ const result = await sdk.redemptions.getUserRedemptions(filters, options, include);
41725
42023
  return result;
41726
42024
  }
41727
42025
  catch (error) {
@@ -41729,7 +42027,54 @@ const useRedemptions = () => {
41729
42027
  throw error;
41730
42028
  }
41731
42029
  }, [sdk, isInitialized, isAuthenticated]);
41732
- const redeem = react.useCallback(async (redemptionId, onStatusUpdate) => {
42030
+ /**
42031
+ * Redeem a redemption offer with optional dynamic context
42032
+ *
42033
+ * Supports dynamic context for personalized NFT generation when the redemption
42034
+ * issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
42035
+ * and custom metadata.
42036
+ *
42037
+ * @param redemptionId - ID of the redemption to redeem
42038
+ * @param options - Optional settings
42039
+ * @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
42040
+ * @param options.onStatusUpdate - Callback for transaction signing status updates
42041
+ * @returns Promise resolving to redemption result
42042
+ *
42043
+ * @example Basic redemption
42044
+ * ```typescript
42045
+ * const result = await redeem('redemption-123');
42046
+ * ```
42047
+ *
42048
+ * @example Redemption with dynamic context (personalized NFT)
42049
+ * ```typescript
42050
+ * const result = await redeem('event-ticket-123', {
42051
+ * context: {
42052
+ * // Validity - token valid for event duration
42053
+ * validityDate: new Date().toISOString(),
42054
+ * validityDuration: 8, // 8 hours
42055
+ *
42056
+ * // AI prompt context - available as {{context.xxx}} in prompts
42057
+ * attendeeName: 'John Doe',
42058
+ * ticketTier: 'VIP',
42059
+ * seatSection: 'A12',
42060
+ * }
42061
+ * });
42062
+ * ```
42063
+ *
42064
+ * @example Hotel stay NFT with check-in/check-out dates
42065
+ * ```typescript
42066
+ * const result = await redeem('hotel-nft-456', {
42067
+ * context: {
42068
+ * validityDate: '2026-04-15T14:00:00Z', // Check-in
42069
+ * validityEndDate: '2026-04-20T11:00:00Z', // Check-out
42070
+ * guestName: 'Jane Smith',
42071
+ * roomNumber: '305',
42072
+ * roomType: 'Suite',
42073
+ * }
42074
+ * });
42075
+ * ```
42076
+ */
42077
+ const redeem = react.useCallback(async (redemptionId, options) => {
41733
42078
  if (!isInitialized || !sdk) {
41734
42079
  throw new Error('SDK not initialized. Call initialize() first.');
41735
42080
  }
@@ -41737,11 +42082,11 @@ const useRedemptions = () => {
41737
42082
  throw new Error('SDK not authenticated. redeem requires authentication.');
41738
42083
  }
41739
42084
  try {
41740
- const result = await sdk.redemptions.redeem(redemptionId);
42085
+ const result = await sdk.redemptions.redeem(redemptionId, options?.context);
41741
42086
  const txToken = result.senderTransaction?.actionable?.authToken;
41742
42087
  if (txToken && isSignerAvailable) {
41743
42088
  try {
41744
- const signingResult = await signAndSubmitTransactionWithJWT(txToken, onStatusUpdate);
42089
+ const signingResult = await signAndSubmitTransactionWithJWT(txToken, options?.onStatusUpdate);
41745
42090
  if (signingResult.success) {
41746
42091
  // Return enhanced result with signing information
41747
42092
  return {