@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.
@@ -5,11 +5,45 @@ export type { PersConfig } from '@explorins/pers-sdk/core';
5
5
  /**
6
6
  * Context interface for PERS SDK React Native integration
7
7
  *
8
+ * ## Session Restoration Behavior
9
+ *
10
+ * Session restoration is **asynchronous**. When the app starts:
11
+ * 1. `isInitialized` becomes `true` when SDK is ready
12
+ * 2. Session restoration happens automatically
13
+ * 3. `isAuthenticated` and `user` update when restoration completes
14
+ *
15
+ * **Always check `isInitialized` AND `isAuthenticated` before using `user`:**
16
+ *
17
+ * ```tsx
18
+ * const { user, isInitialized, isAuthenticated } = usePersSDK();
19
+ *
20
+ * if (!isInitialized) return <LoadingSpinner />;
21
+ * if (!isAuthenticated || !user) return <LoginScreen />;
22
+ * return <WalletContent user={user} />;
23
+ * ```
24
+ *
25
+ * **Why `user` might be null on app restart:**
26
+ * - Access tokens expire after 1 hour (auto-refreshed if app is active)
27
+ * - Refresh tokens expire after 30 days (user must re-login)
28
+ * - Session restoration is in progress
29
+ *
30
+ * Listen for auth events to debug:
31
+ * ```typescript
32
+ * sdk.events.subscribe((event) => {
33
+ * console.log('[Auth Event]', event.type, event.details);
34
+ * }, { domains: ['authentication'], replay: true });
35
+ * ```
36
+ *
37
+ * | Event | Meaning |
38
+ * |-------|---------|
39
+ * | `session_restored` | Tokens valid, user logged in |
40
+ * | `session_restoration_failed` | Tokens expired, needs re-login |
41
+ *
8
42
  * @property sdk - Main SDK instance (null until initialized)
9
43
  * @property authProvider - Platform-specific auth provider
10
- * @property isInitialized - Whether SDK has been initialized
44
+ * @property isInitialized - Whether SDK has been initialized (not whether user is authenticated!)
11
45
  * @property isAuthenticated - Whether user is currently authenticated
12
- * @property user - Current user data (null if not authenticated)
46
+ * @property user - Current user data (null if not authenticated OR restoration in progress)
13
47
  */
14
48
  export interface PersSDKContext {
15
49
  sdk: PersSDK | null;
@@ -1 +1 @@
1
- {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAA2C,MAAM,OAAO,CAAC;AAEvH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAkB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAGhC,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5F,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CA6NA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
1
+ {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAA2C,MAAM,OAAO,CAAC;AAEvH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAkB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAGhC,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5F,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CA6NA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
package/package.json CHANGED
@@ -1,199 +1,199 @@
1
- {
2
- "name": "@explorins/pers-sdk-react-native",
3
- "version": "2.2.0",
4
- "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc && rollup -c --bundleConfigAsCjs",
9
- "build:watch": "rollup -c --bundleConfigAsCjs --watch",
10
- "dev": "tsc --watch",
11
- "clean": "rimraf dist",
12
- "docs": "typedoc",
13
- "docs:dev": "typedoc --watch",
14
- "docs:json": "typedoc --json docs/api.json",
15
- "docs:serve": "npx http-server docs -p 8080",
16
- "prepublishOnly": "npm run clean && npm run build",
17
- "publish-version": "npm run clean && npm run build && npm version patch && npm publish",
18
- "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish",
19
- "yalc:publish": "npm version patch --no-git-tag-version && npm run build && yalc publish",
20
- "yalc:push": "npm version patch --no-git-tag-version && npm run build && yalc push"
21
- },
22
- "keywords": [
23
- "pers",
24
- "sdk",
25
- "react-native",
26
- "tourism",
27
- "loyalty",
28
- "blockchain",
29
- "web3",
30
- "webauthn",
31
- "transaction-signing",
32
- "rewards",
33
- "authentication",
34
- "biometrics",
35
- "secure-storage"
36
- ],
37
- "author": "eXplorins",
38
- "license": "MIT",
39
- "dependencies": {
40
- "@explorins/pers-sdk": "^2.1.33",
41
- "@explorins/pers-signer": "^1.0.34",
42
- "buffer": "^6.0.3",
43
- "ethers": "^6.15.0",
44
- "react-native-url-polyfill": "^2.0.0"
45
- },
46
- "browser": {
47
- "crypto": false,
48
- "url": false,
49
- "http": false,
50
- "https": false,
51
- "zlib": false,
52
- "stream": false,
53
- "assert": false,
54
- "path": false,
55
- "fs": false,
56
- "os": false,
57
- "readable-stream": false,
58
- "util": false,
59
- "events": false,
60
- "net": false,
61
- "tls": false,
62
- "child_process": false,
63
- "pngjs": false,
64
- "pngjs/lib/bitmapper": false,
65
- "pngjs/lib/parser-async": false,
66
- "pngjs/lib/parser-sync": false,
67
- "./bitmapper": false,
68
- "hash-base": false,
69
- "@ethereumjs/common": false,
70
- "@ethereumjs/tx": false,
71
- "@ethereumjs/util": false,
72
- "stream-browserify": false,
73
- "crypto-browserify": false,
74
- "inherits": false,
75
- "safe-buffer": false,
76
- "string_decoder": false,
77
- "process-nextick-args": false,
78
- "isarray": false,
79
- "core-util-is": false,
80
- "util-deprecate": false,
81
- "ethereum-cryptography": false,
82
- "keccak": false,
83
- "secp256k1": false,
84
- "scrypt-js": false,
85
- "bech32": false,
86
- "ws": false,
87
- "xhr2-cookies": false,
88
- "aes-js": false,
89
- "@ethersproject/sha2": false,
90
- "@ethersproject/pbkdf2": false,
91
- "@ethersproject/scrypt": false
92
- },
93
- "peerDependencies": {
94
- "@react-native-async-storage/async-storage": ">=1.15.0",
95
- "react": ">=16.8.0",
96
- "react-native": ">=0.60.0",
97
- "react-native-get-random-values": ">=1.8.0",
98
- "react-native-keychain": ">=8.0.0",
99
- "react-native-passkey": ">=3.0.0",
100
- "react-native-quick-crypto": ">=0.7.5"
101
- },
102
- "peerDependenciesMeta": {
103
- "@react-native-async-storage/async-storage": {
104
- "optional": false
105
- },
106
- "react-native-get-random-values": {
107
- "optional": false
108
- },
109
- "react-native-keychain": {
110
- "optional": true
111
- },
112
- "react-native-passkey": {
113
- "optional": true
114
- },
115
- "react-native-quick-crypto": {
116
- "optional": false
117
- },
118
- "react-native-url-polyfill": {
119
- "optional": true
120
- },
121
- "react-native-polyfill-globals": {
122
- "optional": true
123
- },
124
- "web-streams-polyfill": {
125
- "optional": true
126
- }
127
- },
128
- "overrides": {
129
- "readable-stream": false,
130
- "@ethereumjs/common": false,
131
- "@ethereumjs/util": false,
132
- "@ethereumjs/rlp": false,
133
- "@ethereumjs/tx": false,
134
- "stream": false,
135
- "util": false,
136
- "events": false,
137
- "crypto": false,
138
- "fs": false,
139
- "path": false,
140
- "os": false,
141
- "net": false,
142
- "tls": false,
143
- "child_process": false,
144
- "hash-base": false,
145
- "inherits": false,
146
- "safe-buffer": false,
147
- "string_decoder": false,
148
- "process-nextick-args": false,
149
- "isarray": false,
150
- "core-util-is": false,
151
- "util-deprecate": false,
152
- "ethereum-cryptography": false,
153
- "keccak": false,
154
- "secp256k1": false,
155
- "scrypt-js": false,
156
- "bech32": false,
157
- "ws": false,
158
- "xhr2-cookies": false,
159
- "aes-js": false,
160
- "@ethersproject/sha2": false,
161
- "@ethersproject/pbkdf2": false,
162
- "@ethersproject/scrypt": false
163
- },
164
- "devDependencies": {
165
- "@explorins/pers-shared": "^2.1.128",
166
- "@react-native-async-storage/async-storage": "^3.0.2",
167
- "@rollup/plugin-commonjs": "^25.0.7",
168
- "@rollup/plugin-json": "^6.1.0",
169
- "@rollup/plugin-node-resolve": "^15.2.3",
170
- "@testing-library/dom": "^10.4.1",
171
- "@testing-library/jest-dom": "^6.9.1",
172
- "@testing-library/react": "^16.3.2",
173
- "@types/jest": "^30.0.0",
174
- "@types/react": "^19.1.1",
175
- "@types/react-native": "^0.72.8",
176
- "jest": "^30.3.0",
177
- "jest-environment-jsdom": "^30.3.0",
178
- "react": "^19.2.5",
179
- "react-dom": "^19.2.5",
180
- "react-native": "^0.85.3",
181
- "rimraf": "^5.0.0",
182
- "rollup": "^4.0.0",
183
- "rollup-plugin-typescript2": "^0.36.0",
184
- "ts-jest": "^29.4.9",
185
- "typedoc": "^0.25.13",
186
- "typedoc-plugin-markdown": "^3.17.1",
187
- "typescript": "^5.2.0"
188
- },
189
- "files": [
190
- "dist/**/*",
191
- "src/**/*",
192
- "README.md"
193
- ],
194
- "repository": {
195
- "type": "git",
196
- "url": "git+https://github.com/eXplorins/pers-sdk.git",
197
- "directory": "packages/pers-sdk-react-native"
198
- }
199
- }
1
+ {
2
+ "name": "@explorins/pers-sdk-react-native",
3
+ "version": "2.2.1",
4
+ "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc && rollup -c --bundleConfigAsCjs",
9
+ "build:watch": "rollup -c --bundleConfigAsCjs --watch",
10
+ "dev": "tsc --watch",
11
+ "clean": "rimraf dist",
12
+ "docs": "typedoc",
13
+ "docs:dev": "typedoc --watch",
14
+ "docs:json": "typedoc --json docs/api.json",
15
+ "docs:serve": "npx http-server docs -p 8080",
16
+ "prepublishOnly": "npm run clean && npm run build",
17
+ "publish-version": "npm run clean && npm run build && npm version patch && npm publish",
18
+ "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish",
19
+ "yalc:publish": "npm version patch --no-git-tag-version && npm run build && yalc publish",
20
+ "yalc:push": "npm version patch --no-git-tag-version && npm run build && yalc push"
21
+ },
22
+ "keywords": [
23
+ "pers",
24
+ "sdk",
25
+ "react-native",
26
+ "tourism",
27
+ "loyalty",
28
+ "blockchain",
29
+ "web3",
30
+ "webauthn",
31
+ "transaction-signing",
32
+ "rewards",
33
+ "authentication",
34
+ "biometrics",
35
+ "secure-storage"
36
+ ],
37
+ "author": "eXplorins",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "@explorins/pers-sdk": "^2.1.37",
41
+ "@explorins/pers-signer": "^1.0.34",
42
+ "buffer": "^6.0.3",
43
+ "ethers": "^6.15.0",
44
+ "react-native-url-polyfill": "^2.0.0"
45
+ },
46
+ "browser": {
47
+ "crypto": false,
48
+ "url": false,
49
+ "http": false,
50
+ "https": false,
51
+ "zlib": false,
52
+ "stream": false,
53
+ "assert": false,
54
+ "path": false,
55
+ "fs": false,
56
+ "os": false,
57
+ "readable-stream": false,
58
+ "util": false,
59
+ "events": false,
60
+ "net": false,
61
+ "tls": false,
62
+ "child_process": false,
63
+ "pngjs": false,
64
+ "pngjs/lib/bitmapper": false,
65
+ "pngjs/lib/parser-async": false,
66
+ "pngjs/lib/parser-sync": false,
67
+ "./bitmapper": false,
68
+ "hash-base": false,
69
+ "@ethereumjs/common": false,
70
+ "@ethereumjs/tx": false,
71
+ "@ethereumjs/util": false,
72
+ "stream-browserify": false,
73
+ "crypto-browserify": false,
74
+ "inherits": false,
75
+ "safe-buffer": false,
76
+ "string_decoder": false,
77
+ "process-nextick-args": false,
78
+ "isarray": false,
79
+ "core-util-is": false,
80
+ "util-deprecate": false,
81
+ "ethereum-cryptography": false,
82
+ "keccak": false,
83
+ "secp256k1": false,
84
+ "scrypt-js": false,
85
+ "bech32": false,
86
+ "ws": false,
87
+ "xhr2-cookies": false,
88
+ "aes-js": false,
89
+ "@ethersproject/sha2": false,
90
+ "@ethersproject/pbkdf2": false,
91
+ "@ethersproject/scrypt": false
92
+ },
93
+ "peerDependencies": {
94
+ "@react-native-async-storage/async-storage": ">=1.15.0",
95
+ "react": ">=16.8.0",
96
+ "react-native": ">=0.60.0",
97
+ "react-native-get-random-values": ">=1.8.0",
98
+ "react-native-keychain": ">=8.0.0",
99
+ "react-native-passkey": ">=3.0.0",
100
+ "react-native-quick-crypto": ">=0.7.5"
101
+ },
102
+ "peerDependenciesMeta": {
103
+ "@react-native-async-storage/async-storage": {
104
+ "optional": false
105
+ },
106
+ "react-native-get-random-values": {
107
+ "optional": false
108
+ },
109
+ "react-native-keychain": {
110
+ "optional": true
111
+ },
112
+ "react-native-passkey": {
113
+ "optional": true
114
+ },
115
+ "react-native-quick-crypto": {
116
+ "optional": false
117
+ },
118
+ "react-native-url-polyfill": {
119
+ "optional": true
120
+ },
121
+ "react-native-polyfill-globals": {
122
+ "optional": true
123
+ },
124
+ "web-streams-polyfill": {
125
+ "optional": true
126
+ }
127
+ },
128
+ "overrides": {
129
+ "readable-stream": false,
130
+ "@ethereumjs/common": false,
131
+ "@ethereumjs/util": false,
132
+ "@ethereumjs/rlp": false,
133
+ "@ethereumjs/tx": false,
134
+ "stream": false,
135
+ "util": false,
136
+ "events": false,
137
+ "crypto": false,
138
+ "fs": false,
139
+ "path": false,
140
+ "os": false,
141
+ "net": false,
142
+ "tls": false,
143
+ "child_process": false,
144
+ "hash-base": false,
145
+ "inherits": false,
146
+ "safe-buffer": false,
147
+ "string_decoder": false,
148
+ "process-nextick-args": false,
149
+ "isarray": false,
150
+ "core-util-is": false,
151
+ "util-deprecate": false,
152
+ "ethereum-cryptography": false,
153
+ "keccak": false,
154
+ "secp256k1": false,
155
+ "scrypt-js": false,
156
+ "bech32": false,
157
+ "ws": false,
158
+ "xhr2-cookies": false,
159
+ "aes-js": false,
160
+ "@ethersproject/sha2": false,
161
+ "@ethersproject/pbkdf2": false,
162
+ "@ethersproject/scrypt": false
163
+ },
164
+ "devDependencies": {
165
+ "@explorins/pers-shared": "^2.1.128",
166
+ "@react-native-async-storage/async-storage": "^3.0.2",
167
+ "@rollup/plugin-commonjs": "^25.0.7",
168
+ "@rollup/plugin-json": "^6.1.0",
169
+ "@rollup/plugin-node-resolve": "^15.2.3",
170
+ "@testing-library/dom": "^10.4.1",
171
+ "@testing-library/jest-dom": "^6.9.1",
172
+ "@testing-library/react": "^16.3.2",
173
+ "@types/jest": "^30.0.0",
174
+ "@types/react": "^19.1.1",
175
+ "@types/react-native": "^0.72.8",
176
+ "jest": "^30.3.0",
177
+ "jest-environment-jsdom": "^30.3.0",
178
+ "react": "^19.2.5",
179
+ "react-dom": "^19.2.5",
180
+ "react-native": "^0.85.3",
181
+ "rimraf": "^5.0.0",
182
+ "rollup": "^4.0.0",
183
+ "rollup-plugin-typescript2": "^0.36.0",
184
+ "ts-jest": "^29.4.9",
185
+ "typedoc": "^0.25.13",
186
+ "typedoc-plugin-markdown": "^3.17.1",
187
+ "typescript": "^5.2.0"
188
+ },
189
+ "files": [
190
+ "dist/**/*",
191
+ "src/**/*",
192
+ "README.md"
193
+ ],
194
+ "repository": {
195
+ "type": "git",
196
+ "url": "git+https://github.com/eXplorins/pers-sdk.git",
197
+ "directory": "packages/pers-sdk-react-native"
198
+ }
199
+ }
@@ -164,6 +164,25 @@ export const useRedemptions = () => {
164
164
  /**
165
165
  * Redeem a redemption offer with optional dynamic context
166
166
  *
167
+ * ## When to use `redeem()` vs `createTransaction()`
168
+ *
169
+ * | Use Case | Method | Auto-Signs |
170
+ * |----------|--------|------------|
171
+ * | **Voucher redemption** | `redeem()` | ✅ Yes |
172
+ * | **Reward redemption** | `redeem()` | ✅ Yes |
173
+ * | **Direct token transfer** | `createTransaction()` | ⚠️ Depends |
174
+ * | **POS burn with QR** | `buildPOSBurnRequest()` | ❌ Manual |
175
+ *
176
+ * **Use `redeem()` for voucher/reward redemptions.** It automatically:
177
+ * 1. Creates redemption record
178
+ * 2. Triggers passkey/biometric prompt
179
+ * 3. Signs the transaction
180
+ * 4. Submits to blockchain
181
+ * 5. Returns result with `transactionHash`
182
+ *
183
+ * **No admin approval required** - The "PENDING" state is brief (seconds).
184
+ * The user's passkey signature IS the approval.
185
+ *
167
186
  * Supports dynamic context for personalized NFT generation when the redemption
168
187
  * issues ERC721 tokens. Context can include validity dates, AI prompt placeholders,
169
188
  * and custom metadata.
@@ -172,11 +191,21 @@ export const useRedemptions = () => {
172
191
  * @param options - Optional settings
173
192
  * @param options.context - Dynamic context for NFT personalization (validity dates, AI prompts)
174
193
  * @param options.onStatusUpdate - Callback for transaction signing status updates
175
- * @returns Promise resolving to redemption result
194
+ * @returns Promise resolving to redemption result with `transactionHash` on success
176
195
  *
177
- * @example Basic redemption
196
+ * @example Basic redemption (most common)
178
197
  * ```typescript
179
- * const result = await redeem('redemption-123');
198
+ * const { redeem } = useRedemptions();
199
+ *
200
+ * const result = await redeem('redemption-123', {
201
+ * onStatusUpdate: (status, message) => {
202
+ * console.log(`Status: ${status} - ${message}`);
203
+ * }
204
+ * });
205
+ *
206
+ * if (result.transactionHash) {
207
+ * console.log('Redemption complete!', result.transactionHash);
208
+ * }
180
209
  * ```
181
210
  *
182
211
  * @example Redemption with dynamic context (personalized NFT)
@@ -207,6 +236,8 @@ export const useRedemptions = () => {
207
236
  * }
208
237
  * });
209
238
  * ```
239
+ *
240
+ * @see {@link useTransactions.createTransaction | createTransaction()} for direct transfers without redemption flow
210
241
  */
211
242
  const redeem = useCallback(async (
212
243
  redemptionId: string,
@@ -368,8 +399,47 @@ export const useRedemptions = () => {
368
399
  }
369
400
  }, [sdk, isInitialized]);
370
401
 
402
+ /**
403
+ * Get a specific redemption by ID
404
+ *
405
+ * Retrieves detailed information about a specific redemption offer.
406
+ *
407
+ * @param id - Redemption UUID
408
+ * @param include - Optional relations to include: 'redeemCount'
409
+ * @returns Promise resolving to the redemption
410
+ *
411
+ * @example Basic usage
412
+ * ```typescript
413
+ * const redemption = await getRedemptionById('7baf4d39-0bd6-45ca-9c66-8c0d655767c3');
414
+ * console.log(redemption.name);
415
+ * ```
416
+ *
417
+ * @example With redeem count
418
+ * ```typescript
419
+ * const redemption = await getRedemptionById('redemption-123', ['redeemCount']);
420
+ * console.log(`${redemption.name} has been redeemed ${redemption.redeemCount} times`);
421
+ * ```
422
+ */
423
+ const getRedemptionById = useCallback(async (
424
+ id: string,
425
+ include?: RedemptionIncludeRelation[]
426
+ ): Promise<RedemptionDTO> => {
427
+ if (!isInitialized || !sdk) {
428
+ throw new Error('SDK not initialized. Call initialize() first.');
429
+ }
430
+
431
+ try {
432
+ const result = await sdk.redemptions.getRedemptionById(id, include);
433
+ return result;
434
+ } catch (error) {
435
+ console.error('Failed to fetch redemption by ID:', error);
436
+ throw error;
437
+ }
438
+ }, [sdk, isInitialized]);
439
+
371
440
  return {
372
441
  getRedemptions,
442
+ getRedemptionById,
373
443
  getUserRedemptions,
374
444
  redeem,
375
445
  getRedemptionTypes,
@@ -10,9 +10,42 @@ import type { TokenBalance, TokenBalanceRequest } from '@explorins/pers-sdk/web3
10
10
  * This composite type combines blockchain balance data with token metadata,
11
11
  * making it easy to display token information in UI components without
12
12
  * needing to join data from multiple sources.
13
+ *
14
+ * ## CRITICAL: Token ID for Transactions
15
+ *
16
+ * For ERC721 (stamps) and ERC1155 (rewards), use `tokenBalance.tokenId` as the
17
+ * `contractTokenId` when creating transactions. This is the **on-chain token ID**
18
+ * assigned by the blockchain, NOT the metadata definition ID.
19
+ *
20
+ * | Token Type | tokenId meaning | Example |
21
+ * |------------|-----------------|---------|
22
+ * | ERC20 | `null` or `""` | N/A (fungible points) |
23
+ * | ERC721 | NFT instance ID | `"46"` (on-chain ID) |
24
+ * | ERC1155 | Token type ID | `"16"` (semi-fungible type) |
25
+ *
26
+ * @example Correct usage for token burn transaction
27
+ * ```typescript
28
+ * const stampBalance = tokenBalances.find(b => b.token.type === 'ERC721');
29
+ *
30
+ * const request = buildPOSBurnRequest({
31
+ * amount: 1,
32
+ * contractAddress: stampBalance.token.contractAddress,
33
+ * contractTokenId: stampBalance.tokenBalance.tokenId, // ✅ Correct
34
+ * chainId: stampBalance.token.chainId,
35
+ * userId: userId,
36
+ * businessId: businessId
37
+ * });
38
+ * ```
39
+ *
40
+ * @see {@link https://docs.explorins.com/pers-sdk/troubleshooting#stamp-voucher-transaction-fails | Troubleshooting: Stamp Transaction Fails}
13
41
  */
14
42
  export interface TokenBalanceWithToken {
15
- /** The balance data from blockchain */
43
+ /**
44
+ * The balance data from blockchain
45
+ *
46
+ * **Important:** For NFT operations, use `tokenBalance.tokenId` as the `contractTokenId`
47
+ * in transaction requests. This is the actual on-chain ID, not the metadata definition.
48
+ */
16
49
  tokenBalance: TokenBalance;
17
50
  /** The token definition (contract, symbol, metadata, etc.) */
18
51
  token: TokenDTO;