@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/README.md +8 -0
- package/dist/hooks/useRedemptions.d.ts +1 -0
- package/dist/hooks/useRedemptions.d.ts.map +1 -1
- package/dist/hooks/useRedemptions.js +69 -3
- package/dist/hooks/useTokenBalances.d.ts +34 -1
- package/dist/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/hooks/useTokens.d.ts +4 -1
- package/dist/hooks/useTokens.d.ts.map +1 -1
- package/dist/hooks/useTokens.js +96 -0
- package/dist/index.js +487 -50
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +36 -2
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/package.json +199 -199
- package/src/hooks/useRedemptions.ts +73 -3
- package/src/hooks/useTokenBalances.ts +34 -1
- package/src/hooks/useTokens.ts +100 -1
- package/src/providers/PersSDKProvider.tsx +36 -2
- package/dist/__tests__/__mocks__/pers-sdk-core.d.ts +0 -42
- package/dist/__tests__/__mocks__/pers-sdk-core.d.ts.map +0 -1
- package/dist/__tests__/__mocks__/pers-sdk-core.js +0 -48
- package/dist/__tests__/__mocks__/pers-shared.d.ts +0 -15
- package/dist/__tests__/__mocks__/pers-shared.d.ts.map +0 -1
- package/dist/__tests__/__mocks__/pers-shared.js +0 -2
- package/src/__tests__/__mocks__/pers-sdk-core.ts +0 -83
- package/src/__tests__/__mocks__/pers-shared.ts +0 -17
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 ===
|
|
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 ===
|
|
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
|
|
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
|
-
((
|
|
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:
|
|
4015
|
+
code: CommonErrorCodes.TECHNICAL_ERROR,
|
|
3993
4016
|
category: exports.ErrorCategory.TECHNICAL,
|
|
3994
|
-
domain:
|
|
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 ||
|
|
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 =
|
|
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({
|
|
5404
|
-
* const multiTag = await businessApi.getBusinesses({
|
|
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 (
|
|
5771
|
-
*
|
|
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
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
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
|
|
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
|
-
//
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
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.
|
|
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:
|
|
10931
|
-
userMessage: errorDetails.message || errorMessage,
|
|
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
|
|
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,
|