@nokinc-flur/sdk 0.1.6 → 0.1.7

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 NokInc Flur
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NokInc Flur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,198 +1,198 @@
1
- # flur-sdk (Sprint 1 / Week 1)
2
-
3
- Typed client scaffold aligned to backend OpenAPI.
4
-
5
- ## Install (local dev)
6
- ```bash
7
- npm ci
8
- npm test
9
- npm run build
10
- ```
11
-
12
- ## Generate types (optional)
13
- This repo includes a minimal OpenAPI file at `openapi/flur.openapi.json`.
14
-
15
- ```bash
16
- npm run gen
17
- ```
18
-
19
- ## E2E test against a running backend (optional)
20
- 1) Start backend locally (see backend README)
21
- 2) Run (E2E executes when RUN_E2E=1 or FLUR_BASE_URL is set):
22
- ```bash
23
- FLUR_BASE_URL=http://localhost:8080 RUN_E2E=1 npm test
24
- ```
25
-
26
- Contract-focused E2E only:
27
- ```bash
28
- FLUR_BASE_URL=https://your-oci-gateway.example.com RUN_E2E=1 npm run test:contract
29
- ```
30
-
31
- Optional envs for full send-money E2E coverage:
32
- - `FLUR_E2E_SESSION_TOKEN` (required for account/transactions/recipient)
33
- - `FLUR_E2E_RECIPIENT_IDENTIFIER` (required for recipient resolve / transfer)
34
- - `FLUR_E2E_SEND_AUTH_TOKEN` (required for transfer create)
35
- - `FLUR_E2E_DEVICE_ID` (required for transfer create)
36
- - `FLUR_E2E_AMOUNT_MINOR` (optional, default `100`)
37
- - `FLUR_E2E_CURRENCY` (optional, default `NGN`)
38
-
39
- Generate real E2E auth inputs against deployed backend:
40
- ```bash
41
- # Step 1: start onboarding and get requestId
42
- npm run e2e:bootstrap -- --base-url https://your-oci-gateway --phone +23480xxxxxxx
43
-
44
- # Step 2: rerun with OTP/silent-auth code (and optional recipient)
45
- npm run e2e:bootstrap -- --base-url https://your-oci-gateway --phone +23480xxxxxxx --code 123456 --recipient +23481xxxxxxx
46
-
47
- # Script prints PowerShell env exports, then run:
48
- npm run test:contract
49
- ```
50
-
51
- ## Onboarding flow methods
52
-
53
- ```ts
54
- import { FlurClient } from "@nokinc-flur/sdk";
55
-
56
- const client = new FlurClient({ baseUrl: "https://api.example.com" });
57
-
58
- const started = await client.onboardingStart({
59
- phoneE164: "+14155550123",
60
- appInstanceId: "app-instance-1",
61
- platform: "ios",
62
- });
63
- // started: { requestId, checkUrl?, expiresInSec, fallback }
64
-
65
- const completed = await client.onboardingComplete({
66
- requestId: started.requestId,
67
- code: "123456",
68
- appInstanceId: "app-instance-1",
69
- });
70
- // completed: { sessionToken, userId, restricted, risk_reasons }
71
- ```
72
-
73
- ## Auth + device security methods
74
-
75
- ```ts
76
- const registered = await client.registerDevice({
77
- userId: completed.userId,
78
- appInstanceId: "app-instance-1",
79
- platform: "ios",
80
- networkSignals: { ip: "1.2.3.4" },
81
- }, { accessToken: completed.sessionToken });
82
- // { deviceId, fingerprintHash, driftScore, trustState, stepUpRequired }
83
-
84
- const refreshed = await client.authRefresh({
85
- userId: completed.userId,
86
- refreshToken: completed.sessionToken,
87
- appInstanceId: "app-instance-1",
88
- fingerprintHash: registered.fingerprintHash,
89
- });
90
- // { refreshToken, stepUpRequired }
91
-
92
- await client.pinSet({ userId: completed.userId, pin: "123456" }, { accessToken: completed.sessionToken });
93
- await client.pinVerify({ userId: completed.userId, pin: "123456" }, { accessToken: completed.sessionToken });
94
- await client.authLogout(
95
- { userId: completed.userId, refreshToken: refreshed.refreshToken },
96
- { accessToken: refreshed.refreshToken }
97
- );
98
- ```
99
-
100
- ## Send-money methods
101
-
102
- ```ts
103
- // Register per-device signing key for send authorization
104
- await client.registerSendDeviceKey({
105
- userId: completed.userId,
106
- deviceId: registered.deviceId,
107
- publicKey: "-----BEGIN PUBLIC KEY-----...",
108
- }, { accessToken: completed.sessionToken });
109
-
110
- // Obtain challenge and verify signature to get short-lived send token
111
- const challenge = await client.createSendChallenge({
112
- userId: completed.userId,
113
- deviceId: registered.deviceId,
114
- }, { accessToken: completed.sessionToken });
115
-
116
- const send = await client.verifySendChallenge({
117
- userId: completed.userId,
118
- deviceId: registered.deviceId,
119
- challengeId: challenge.challengeId,
120
- signature: "base64-signature",
121
- }, { accessToken: completed.sessionToken });
122
-
123
- // Resolve recipient and create transfer
124
- const recipient = await client.resolveRecipient(
125
- { identifier: "+14155550123" },
126
- { accessToken: completed.sessionToken }
127
- );
128
-
129
- await client.createTransfer(
130
- {
131
- recipientIdentifier: recipient.normalizedIdentifier,
132
- amountMinor: 5000,
133
- currency: "NGN",
134
- sendAuthToken: send.sendAuthToken,
135
- },
136
- {
137
- accessToken: completed.sessionToken,
138
- deviceId: registered.deviceId,
139
- idempotencyKey: crypto.randomUUID(),
140
- }
141
- );
142
-
143
- // Account + transaction endpoints
144
- await client.accountSummary({ accessToken: completed.sessionToken });
145
- await client.listTransactions({ accessToken: completed.sessionToken, limit: 20 });
146
- ```
147
-
148
- ## Error code mapping
149
-
150
- SDK maps backend error payload `code` into typed `FlurError.code` when available:
151
- - `TOKEN_REPLAYED`
152
- - `SESSION_MISMATCH`
153
- - `PIN_INVALID`
154
- - `PIN_LOCKED`
155
- - `PIN_NOT_SET`
156
- - `STEP_UP_REQUIRED`
157
-
158
- Fallback codes remain:
159
- - `HTTP_ERROR`, `NETWORK_ERROR`, `TIMEOUT`, `UNKNOWN`
160
-
161
- ## Lean prod release workflow (one-person team)
162
-
163
- PowerShell script (Windows):
164
-
165
- ```powershell
166
- # patch release (recommended for this onboarding addition)
167
- ./scripts/release.ps1 -Bump patch
168
-
169
- # exact version release (example: 1.0.1)
170
- ./scripts/release.ps1 -Version 1.0.1
171
-
172
- # include E2E run (uses default OCI API Gateway URL from release script)
173
- ./scripts/release.ps1 -Version 1.0.1 -RunE2E
174
-
175
- # include E2E run with override URL
176
- ./scripts/release.ps1 -Version 1.0.1 -RunE2E -E2EBaseUrl https://your-api.example.com
177
-
178
- # publish to npm after all checks pass
179
- ./scripts/release.ps1 -Bump patch -Publish
180
- ./scripts/release.ps1 -Version 1.0.1 -Publish
181
- ```
182
-
183
- What it does:
184
- - verifies git working tree is clean
185
- - runs `npm ci`
186
- - runs `npm run lint`, `npm run typecheck`, `npm test`, `npm run build`
187
- - creates tarball via `npm pack` and smoke-tests install in a temp project
188
- - bumps version (`patch`/`minor`/`major`) and creates git tag
189
- - pushes commit and tag (`git push`, `git push --tags`)
190
- - optionally publishes to npm (`-Publish`)
191
-
192
- NPM aliases:
193
-
194
- ```bash
195
- npm run release:patch
196
- npm run release:minor
197
- npm run release:major
198
- ```
1
+ # flur-sdk (Sprint 1 / Week 1)
2
+
3
+ Typed client scaffold aligned to backend OpenAPI.
4
+
5
+ ## Install (local dev)
6
+ ```bash
7
+ npm ci
8
+ npm test
9
+ npm run build
10
+ ```
11
+
12
+ ## Generate types (optional)
13
+ This repo includes a minimal OpenAPI file at `openapi/flur.openapi.json`.
14
+
15
+ ```bash
16
+ npm run gen
17
+ ```
18
+
19
+ ## E2E test against a running backend (optional)
20
+ 1) Start backend locally (see backend README)
21
+ 2) Run (E2E executes when RUN_E2E=1 or FLUR_BASE_URL is set):
22
+ ```bash
23
+ FLUR_BASE_URL=http://localhost:8080 RUN_E2E=1 npm test
24
+ ```
25
+
26
+ Contract-focused E2E only:
27
+ ```bash
28
+ FLUR_BASE_URL=https://your-oci-gateway.example.com RUN_E2E=1 npm run test:contract
29
+ ```
30
+
31
+ Optional envs for full send-money E2E coverage:
32
+ - `FLUR_E2E_SESSION_TOKEN` (required for account/transactions/recipient)
33
+ - `FLUR_E2E_RECIPIENT_IDENTIFIER` (required for recipient resolve / transfer)
34
+ - `FLUR_E2E_SEND_AUTH_TOKEN` (required for transfer create)
35
+ - `FLUR_E2E_DEVICE_ID` (required for transfer create)
36
+ - `FLUR_E2E_AMOUNT_MINOR` (optional, default `100`)
37
+ - `FLUR_E2E_CURRENCY` (optional, default `NGN`)
38
+
39
+ Generate real E2E auth inputs against deployed backend:
40
+ ```bash
41
+ # Step 1: start onboarding and get requestId
42
+ npm run e2e:bootstrap -- --base-url https://your-oci-gateway --phone +23480xxxxxxx
43
+
44
+ # Step 2: rerun with OTP/silent-auth code (and optional recipient)
45
+ npm run e2e:bootstrap -- --base-url https://your-oci-gateway --phone +23480xxxxxxx --code 123456 --recipient +23481xxxxxxx
46
+
47
+ # Script prints PowerShell env exports, then run:
48
+ npm run test:contract
49
+ ```
50
+
51
+ ## Onboarding flow methods
52
+
53
+ ```ts
54
+ import { FlurClient } from "@nokinc-flur/sdk";
55
+
56
+ const client = new FlurClient({ baseUrl: "https://api.example.com" });
57
+
58
+ const started = await client.onboardingStart({
59
+ phoneE164: "+14155550123",
60
+ appInstanceId: "app-instance-1",
61
+ platform: "ios",
62
+ });
63
+ // started: { requestId, checkUrl?, expiresInSec, fallback }
64
+
65
+ const completed = await client.onboardingComplete({
66
+ requestId: started.requestId,
67
+ code: "123456",
68
+ appInstanceId: "app-instance-1",
69
+ });
70
+ // completed: { sessionToken, userId, restricted, risk_reasons }
71
+ ```
72
+
73
+ ## Auth + device security methods
74
+
75
+ ```ts
76
+ const registered = await client.registerDevice({
77
+ userId: completed.userId,
78
+ appInstanceId: "app-instance-1",
79
+ platform: "ios",
80
+ networkSignals: { ip: "1.2.3.4" },
81
+ }, { accessToken: completed.sessionToken });
82
+ // { deviceId, fingerprintHash, driftScore, trustState, stepUpRequired }
83
+
84
+ const refreshed = await client.authRefresh({
85
+ userId: completed.userId,
86
+ refreshToken: completed.sessionToken,
87
+ appInstanceId: "app-instance-1",
88
+ fingerprintHash: registered.fingerprintHash,
89
+ });
90
+ // { refreshToken, stepUpRequired }
91
+
92
+ await client.pinSet({ userId: completed.userId, pin: "123456" }, { accessToken: completed.sessionToken });
93
+ await client.pinVerify({ userId: completed.userId, pin: "123456" }, { accessToken: completed.sessionToken });
94
+ await client.authLogout(
95
+ { userId: completed.userId, refreshToken: refreshed.refreshToken },
96
+ { accessToken: refreshed.refreshToken }
97
+ );
98
+ ```
99
+
100
+ ## Send-money methods
101
+
102
+ ```ts
103
+ // Register per-device signing key for send authorization
104
+ await client.registerSendDeviceKey({
105
+ userId: completed.userId,
106
+ deviceId: registered.deviceId,
107
+ publicKey: "-----BEGIN PUBLIC KEY-----...",
108
+ }, { accessToken: completed.sessionToken });
109
+
110
+ // Obtain challenge and verify signature to get short-lived send token
111
+ const challenge = await client.createSendChallenge({
112
+ userId: completed.userId,
113
+ deviceId: registered.deviceId,
114
+ }, { accessToken: completed.sessionToken });
115
+
116
+ const send = await client.verifySendChallenge({
117
+ userId: completed.userId,
118
+ deviceId: registered.deviceId,
119
+ challengeId: challenge.challengeId,
120
+ signature: "base64-signature",
121
+ }, { accessToken: completed.sessionToken });
122
+
123
+ // Resolve recipient and create transfer
124
+ const recipient = await client.resolveRecipient(
125
+ { identifier: "+14155550123" },
126
+ { accessToken: completed.sessionToken }
127
+ );
128
+
129
+ await client.createTransfer(
130
+ {
131
+ recipientIdentifier: recipient.normalizedIdentifier,
132
+ amountMinor: 5000,
133
+ currency: "NGN",
134
+ sendAuthToken: send.sendAuthToken,
135
+ },
136
+ {
137
+ accessToken: completed.sessionToken,
138
+ deviceId: registered.deviceId,
139
+ idempotencyKey: crypto.randomUUID(),
140
+ }
141
+ );
142
+
143
+ // Account + transaction endpoints
144
+ await client.accountSummary({ accessToken: completed.sessionToken });
145
+ await client.listTransactions({ accessToken: completed.sessionToken, limit: 20 });
146
+ ```
147
+
148
+ ## Error code mapping
149
+
150
+ SDK maps backend error payload `code` into typed `FlurError.code` when available:
151
+ - `TOKEN_REPLAYED`
152
+ - `SESSION_MISMATCH`
153
+ - `PIN_INVALID`
154
+ - `PIN_LOCKED`
155
+ - `PIN_NOT_SET`
156
+ - `STEP_UP_REQUIRED`
157
+
158
+ Fallback codes remain:
159
+ - `HTTP_ERROR`, `NETWORK_ERROR`, `TIMEOUT`, `UNKNOWN`
160
+
161
+ ## Lean prod release workflow (one-person team)
162
+
163
+ PowerShell script (Windows):
164
+
165
+ ```powershell
166
+ # patch release (recommended for this onboarding addition)
167
+ ./scripts/release.ps1 -Bump patch
168
+
169
+ # exact version release (example: 1.0.1)
170
+ ./scripts/release.ps1 -Version 1.0.1
171
+
172
+ # include E2E run (uses default OCI API Gateway URL from release script)
173
+ ./scripts/release.ps1 -Version 1.0.1 -RunE2E
174
+
175
+ # include E2E run with override URL
176
+ ./scripts/release.ps1 -Version 1.0.1 -RunE2E -E2EBaseUrl https://your-api.example.com
177
+
178
+ # publish to npm after all checks pass
179
+ ./scripts/release.ps1 -Bump patch -Publish
180
+ ./scripts/release.ps1 -Version 1.0.1 -Publish
181
+ ```
182
+
183
+ What it does:
184
+ - verifies git working tree is clean
185
+ - runs `npm ci`
186
+ - runs `npm run lint`, `npm run typecheck`, `npm test`, `npm run build`
187
+ - creates tarball via `npm pack` and smoke-tests install in a temp project
188
+ - bumps version (`patch`/`minor`/`major`) and creates git tag
189
+ - pushes commit and tag (`git push`, `git push --tags`)
190
+ - optionally publishes to npm (`-Publish`)
191
+
192
+ NPM aliases:
193
+
194
+ ```bash
195
+ npm run release:patch
196
+ npm run release:minor
197
+ npm run release:major
198
+ ```
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ type FlurClientOptions = {
11
11
  baseUrl: string;
12
12
  fetchImpl?: typeof fetch;
13
13
  timeoutMs?: number;
14
+ getExtraHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
14
15
  };
15
16
  type OnboardingFallback = "SILENT_AUTH" | "OTP";
16
17
  type OnboardingStartInput = {
@@ -220,6 +221,7 @@ declare class FlurClient {
220
221
  private readonly baseUrl;
221
222
  private readonly fetchImpl;
222
223
  private readonly timeoutMs;
224
+ private readonly getExtraHeaders?;
223
225
  constructor(opts: FlurClientOptions);
224
226
  health(): Promise<{
225
227
  ok: boolean;
@@ -273,9 +275,11 @@ declare class FlurError extends Error {
273
275
  readonly code: FlurErrorCode;
274
276
  readonly status?: number;
275
277
  readonly details?: unknown;
278
+ readonly reqId?: string | null;
276
279
  constructor(message: string, code: FlurErrorCode, opts?: {
277
280
  status?: number;
278
281
  details?: unknown;
282
+ reqId?: string | null;
279
283
  });
280
284
  }
281
285
 
package/dist/index.js CHANGED
@@ -215,15 +215,18 @@ var FlurError = class extends Error {
215
215
  code;
216
216
  status;
217
217
  details;
218
+ reqId;
218
219
  constructor(message, code, opts) {
219
220
  super(message);
220
221
  this.name = "FlurError";
221
222
  this.code = code;
222
223
  this.status = opts?.status;
223
224
  this.details = opts?.details;
225
+ this.reqId = opts?.reqId;
224
226
  }
225
227
  };
226
228
  async function mapToFlurError(res) {
229
+ const reqId = res.headers.get("x-request-id");
227
230
  let details = void 0;
228
231
  let mappedCode = "HTTP_ERROR";
229
232
  let mappedMessage = `HTTP ${res.status}`;
@@ -245,7 +248,7 @@ async function mapToFlurError(res) {
245
248
  }
246
249
  } catch {
247
250
  }
248
- return new FlurError(mappedMessage, mappedCode, { status: res.status, details });
251
+ return new FlurError(mappedMessage, mappedCode, { status: res.status, details, reqId });
249
252
  }
250
253
 
251
254
  // src/primitives.ts
@@ -348,10 +351,12 @@ var FlurClient = class {
348
351
  baseUrl;
349
352
  fetchImpl;
350
353
  timeoutMs;
354
+ getExtraHeaders;
351
355
  constructor(opts) {
352
356
  this.baseUrl = opts.baseUrl.replace(/\/$/, "");
353
357
  this.fetchImpl = opts.fetchImpl ?? fetch;
354
358
  this.timeoutMs = opts.timeoutMs ?? 1e4;
359
+ this.getExtraHeaders = opts.getExtraHeaders;
355
360
  }
356
361
  async health() {
357
362
  return this.requestJson("/health", { method: "GET" }, void 0, HealthResponseSchema);
@@ -676,7 +681,15 @@ var FlurClient = class {
676
681
  throw err;
677
682
  }
678
683
  try {
679
- const res = await this.fetchImpl(url, { ...init, body, signal: controller.signal });
684
+ let extraHeaders = {};
685
+ if (this.getExtraHeaders) {
686
+ extraHeaders = await this.getExtraHeaders();
687
+ }
688
+ const finalHeaders = {
689
+ ...init.headers,
690
+ ...extraHeaders
691
+ };
692
+ const res = await this.fetchImpl(url, { ...init, headers: finalHeaders, body, signal: controller.signal });
680
693
  if (!res.ok) throw await mapToFlurError(res);
681
694
  const payload = await res.json();
682
695
  if (!responseSchema) return payload;
@@ -715,3 +728,4 @@ export {
715
728
  normalizeE164,
716
729
  parseAmountInput
717
730
  };
731
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/contracts.ts","../src/errors.ts","../src/primitives.ts"],"sourcesContent":["import { z, type ZodType } from \"zod\";\r\nimport {\r\n AccountSummaryResponseSchema,\r\n AuthLogoutRequestSchema,\r\n AuthRefreshRequestSchema,\r\n AuthRefreshResponseSchema,\r\n CreatePayLinkResponseSchema,\r\n CreateTransferRequestSchema,\r\n E164Regex,\r\n HealthResponseSchema,\r\n OkResponseSchema,\r\n OnboardingCompleteRequestSchema,\r\n OnboardingCompleteResponseSchema,\r\n OnboardingStartRequestSchema,\r\n OnboardingStartResponseSchema,\r\n PinSetRequestSchema,\r\n PinVerifyRequestSchema,\r\n PushRegisterRequestSchema,\r\n RegisterDeviceRequestSchema,\r\n RegisterDeviceResponseSchema,\r\n RegisterSendDeviceKeyRequestSchema,\r\n ResolvePayLinkResponseSchema,\r\n ResolveRecipientRequestSchema,\r\n ResolveRecipientResponseSchema,\r\n SendChallengeRequestSchema,\r\n SendChallengeResponseSchema,\r\n SendVerifyRequestSchema,\r\n SendVerifyResponseSchema,\r\n TransactionDetailResponseSchema,\r\n TransactionsListResponseSchema,\r\n TransferResponseSchema,\r\n WelcomeResponseSchema,\r\n} from \"./contracts.js\";\r\nimport { FlurError, mapToFlurError } from \"./errors.js\";\r\nimport { type Money, moneyMinorToNumber, normalizeE164 } from \"./primitives.js\";\r\n\r\nexport type FlurClientOptions = {\r\n baseUrl: string;\r\n fetchImpl?: typeof fetch;\r\n timeoutMs?: number;\r\n getExtraHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\r\n};\r\n\r\nexport type OnboardingFallback = \"SILENT_AUTH\" | \"OTP\";\r\n\r\nexport type OnboardingStartInput = {\r\n phoneE164: string;\r\n appInstanceId: string;\r\n platform: \"android\" | \"ios\" | \"web\";\r\n turnstileToken?: string;\r\n appAttestation?: {\r\n provider: \"android\" | \"ios\" | \"web\";\r\n token: string;\r\n };\r\n firstName?: string;\r\n lastName?: string;\r\n};\r\n\r\nexport type OnboardingStartResponse = {\r\n requestId: string;\r\n checkUrl?: string;\r\n expiresInSec: number;\r\n fallback: OnboardingFallback;\r\n};\r\n\r\nexport type OnboardingRiskReason = \"SIM_SWAP_RECENT\" | \"ROAMING\" | \"CARRIER_CHANGED\";\r\n\r\nexport type OnboardingCompleteInput = {\r\n requestId: string;\r\n code: string;\r\n appInstanceId: string;\r\n fingerprintHash?: string;\r\n};\r\n\r\nexport type OnboardingCompleteResponse = {\r\n sessionToken: string;\r\n userId: string;\r\n restricted: boolean;\r\n risk_reasons: OnboardingRiskReason[];\r\n stepUpRequired?: boolean;\r\n riskStatus?: \"ok\" | \"unavailable\";\r\n};\r\n\r\nexport type RegisterDeviceInput = {\r\n userId: string;\r\n appInstanceId: string;\r\n platform: string;\r\n model?: string;\r\n networkSignals: {\r\n ip: string;\r\n asn?: number;\r\n country?: string;\r\n carrier?: string;\r\n };\r\n};\r\n\r\nexport type DeviceTrustState = \"TRUSTED_PRIMARY\" | \"TRUSTED_SECONDARY\" | \"UNVERIFIED\";\r\n\r\nexport type RegisterDeviceResponse = {\r\n deviceId: string;\r\n fingerprintHash: string;\r\n driftScore: number;\r\n trustState: DeviceTrustState;\r\n stepUpRequired: boolean;\r\n};\r\n\r\nexport type AuthRefreshInput = {\r\n userId: string;\r\n refreshToken: string;\r\n appInstanceId: string;\r\n fingerprintHash: string;\r\n};\r\n\r\nexport type AuthRefreshResponse = {\r\n refreshToken: string;\r\n stepUpRequired: boolean;\r\n};\r\n\r\nexport type AuthLogoutInput = {\r\n userId: string;\r\n refreshToken: string;\r\n};\r\n\r\nexport type PinSetInput = {\r\n userId: string;\r\n pin: string;\r\n};\r\n\r\nexport type PinVerifyInput = {\r\n userId: string;\r\n pin: string;\r\n};\r\n\r\nexport type RegisterSendDeviceKeyInput = {\r\n userId: string;\r\n deviceId: string;\r\n publicKey: string;\r\n};\r\n\r\nexport type SendChallengeInput = {\r\n userId: string;\r\n deviceId: string;\r\n};\r\n\r\nexport type SendChallengeResponse = {\r\n challengeId: string;\r\n nonce: string;\r\n expiresAt: string;\r\n};\r\n\r\nexport type SendVerifyInput = {\r\n userId: string;\r\n deviceId: string;\r\n challengeId: string;\r\n signature: string;\r\n};\r\n\r\nexport type SendVerifyResponse = {\r\n sendAuthToken: string;\r\n};\r\n\r\nexport type RecipientResolveInput = {\r\n identifier: string;\r\n};\r\n\r\nexport type RecipientResolveResponse = {\r\n recipientUserId: string;\r\n displayName: string;\r\n normalizedIdentifier: string;\r\n isActive: boolean;\r\n};\r\n\r\nexport type TransferInput = {\r\n recipientIdentifier: string;\r\n amountMinor: number;\r\n currency: string;\r\n sendAuthToken: string;\r\n};\r\n\r\nexport type TransferStatus = \"SETTLED\" | \"PENDING_REVIEW\" | \"DECLINED\";\r\n\r\nexport type TransferResponse = {\r\n transactionId: string;\r\n status: TransferStatus;\r\n userStatus: TransferStatus;\r\n recipientName: string;\r\n timestamp: string;\r\n};\r\n\r\nexport type TransactionDirection = \"OUTGOING\" | \"INCOMING\";\r\n\r\nexport type AccountActivityItem = {\r\n id: string;\r\n type: string;\r\n direction: TransactionDirection;\r\n name: string;\r\n identifier: string;\r\n amountMinor: number;\r\n currency: string;\r\n status: string;\r\n timestamp: string;\r\n};\r\n\r\nexport type AccountSummaryResponse = {\r\n balance: number;\r\n currency: string;\r\n dailySendLimit: number;\r\n dailySendRemaining: number;\r\n kycTier: string;\r\n kycStatus: string;\r\n recentActivity: AccountActivityItem[];\r\n};\r\n\r\nexport type TransactionsListResponse = {\r\n items: AccountActivityItem[];\r\n nextCursor: string | null;\r\n};\r\n\r\nexport type TransactionDetailResponse = {\r\n transactionId: string;\r\n type: string;\r\n direction: TransactionDirection;\r\n counterpartyName: string;\r\n counterpartyIdentifier: string;\r\n amountMinor: number;\r\n currency: string;\r\n status: string;\r\n timestamp: string;\r\n};\r\n\r\nexport type PushPlatform = \"ios\" | \"android\" | \"web\";\r\n\r\nexport type PushRegisterInput = {\r\n deviceId: string;\r\n platform: PushPlatform;\r\n token: string;\r\n};\r\n\r\nexport type CreatePayLinkResponse = {\r\n token: string;\r\n};\r\n\r\nexport type ResolvePayLinkResponse = {\r\n recipientUserId: string;\r\n displayName: string;\r\n normalizedIdentifier: string;\r\n isActive: boolean;\r\n};\r\n\r\nexport type AuthorizedOptions = {\r\n accessToken: string;\r\n};\r\n\r\nexport type CreateTransferOptions = AuthorizedOptions & {\r\n deviceId: string;\r\n idempotencyKey: string;\r\n};\r\n\r\nexport type ListTransactionsOptions = AuthorizedOptions & {\r\n cursor?: string;\r\n limit?: number;\r\n};\r\n\r\nexport type BiometricSigner = {\r\n getOrCreateKeyPair(deviceId: string): Promise<string>;\r\n sign(payload: string): Promise<string>;\r\n};\r\n\r\nexport type SendMoneyInput = {\r\n recipientIdentifier: string;\r\n money: Money;\r\n sendAuthToken: string;\r\n idempotencyKey?: string;\r\n defaultCountry?: string;\r\n};\r\n\r\nexport type SendMoneyOptions = AuthorizedOptions & {\r\n deviceId: string;\r\n};\r\n\r\nexport type AuthorizeSendWithBiometricInput = {\r\n userId: string;\r\n deviceId: string;\r\n accessToken: string;\r\n signer: BiometricSigner;\r\n};\r\n\r\nexport class FlurClient {\r\n private readonly baseUrl: string;\r\n private readonly fetchImpl: typeof fetch;\r\n private readonly timeoutMs: number;\r\n private readonly getExtraHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\r\n\r\n constructor(opts: FlurClientOptions) {\r\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\r\n this.fetchImpl = opts.fetchImpl ?? fetch;\r\n this.timeoutMs = opts.timeoutMs ?? 10_000;\r\n this.getExtraHeaders = opts.getExtraHeaders;\r\n }\r\n\r\n async health(): Promise<{ ok: boolean }> {\r\n return this.requestJson(\"/health\", { method: \"GET\" }, undefined, HealthResponseSchema);\r\n }\r\n\r\n async welcome(): Promise<{ message: string }> {\r\n return this.requestJson(\"/welcome\", { method: \"GET\" }, undefined, WelcomeResponseSchema);\r\n }\r\n\r\n async onboardingStart(input: OnboardingStartInput): Promise<OnboardingStartResponse> {\r\n return this.requestJson(\r\n \"/v1/onboarding/start\",\r\n {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/json\" },\r\n },\r\n OnboardingStartRequestSchema,\r\n OnboardingStartResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async onboardingComplete(input: OnboardingCompleteInput): Promise<OnboardingCompleteResponse> {\r\n return this.requestJson(\r\n \"/v1/onboarding/complete\",\r\n {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/json\" },\r\n },\r\n OnboardingCompleteRequestSchema,\r\n OnboardingCompleteResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async registerDevice(input: RegisterDeviceInput, options: AuthorizedOptions): Promise<RegisterDeviceResponse> {\r\n return this.requestJson(\r\n \"/v1/devices/register\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n RegisterDeviceRequestSchema,\r\n RegisterDeviceResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async authRefresh(input: AuthRefreshInput): Promise<AuthRefreshResponse> {\r\n return this.requestJson(\r\n \"/v1/auth/refresh\",\r\n {\r\n method: \"POST\",\r\n headers: { \"content-type\": \"application/json\" },\r\n },\r\n AuthRefreshRequestSchema,\r\n AuthRefreshResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async authLogout(input: AuthLogoutInput, options: AuthorizedOptions): Promise<{ ok: boolean }> {\r\n return this.requestJson(\r\n \"/v1/auth/logout\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n AuthLogoutRequestSchema,\r\n OkResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async pinSet(input: PinSetInput, options: AuthorizedOptions): Promise<{ ok: boolean }> {\r\n return this.requestJson(\r\n \"/v1/auth/pin/set\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n PinSetRequestSchema,\r\n OkResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async pinVerify(input: PinVerifyInput, options: AuthorizedOptions): Promise<{ ok: boolean }> {\r\n return this.requestJson(\r\n \"/v1/auth/pin/verify\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n PinVerifyRequestSchema,\r\n OkResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async registerSendDeviceKey(input: RegisterSendDeviceKeyInput, options: AuthorizedOptions): Promise<{ ok: boolean }> {\r\n return this.requestJson(\r\n \"/api/v1/auth/send/device-key\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n RegisterSendDeviceKeyRequestSchema,\r\n OkResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async createSendChallenge(input: SendChallengeInput, options: AuthorizedOptions): Promise<SendChallengeResponse> {\r\n return this.requestJson(\r\n \"/api/v1/auth/send/challenge\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n SendChallengeRequestSchema,\r\n SendChallengeResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async verifySendChallenge(input: SendVerifyInput, options: AuthorizedOptions): Promise<SendVerifyResponse> {\r\n return this.requestJson(\r\n \"/api/v1/auth/send/verify\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n SendVerifyRequestSchema,\r\n SendVerifyResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async registerBiometricDeviceKey(input: {\r\n userId: string;\r\n deviceId: string;\r\n accessToken: string;\r\n signer: BiometricSigner;\r\n }): Promise<{ ok: boolean }> {\r\n const publicKey = await input.signer.getOrCreateKeyPair(input.deviceId);\r\n return this.registerSendDeviceKey({\r\n userId: input.userId,\r\n deviceId: input.deviceId,\r\n publicKey,\r\n }, { accessToken: input.accessToken });\r\n }\r\n\r\n async authorizeSendWithBiometric(input: AuthorizeSendWithBiometricInput): Promise<SendVerifyResponse> {\r\n const challenge = await this.createSendChallenge({\r\n userId: input.userId,\r\n deviceId: input.deviceId,\r\n }, { accessToken: input.accessToken });\r\n\r\n const signature = await input.signer.sign(challenge.nonce);\r\n return this.verifySendChallenge({\r\n userId: input.userId,\r\n deviceId: input.deviceId,\r\n challengeId: challenge.challengeId,\r\n signature,\r\n }, { accessToken: input.accessToken });\r\n }\r\n\r\n async resolveRecipient(input: RecipientResolveInput, options: AuthorizedOptions): Promise<RecipientResolveResponse> {\r\n return this.requestJson(\r\n \"/api/v1/recipients/resolve\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n ResolveRecipientRequestSchema,\r\n ResolveRecipientResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async createTransfer(input: TransferInput, options: CreateTransferOptions): Promise<TransferResponse> {\r\n return this.requestJson(\r\n \"/api/v1/transfers\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n \"x-device-id\": options.deviceId,\r\n \"x-idempotency-key\": options.idempotencyKey,\r\n },\r\n },\r\n CreateTransferRequestSchema,\r\n TransferResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async sendMoney(input: SendMoneyInput, options: SendMoneyOptions): Promise<TransferResponse> {\r\n const normalizedRecipient = E164Regex.test(input.recipientIdentifier)\r\n ? input.recipientIdentifier\r\n : normalizeE164(input.recipientIdentifier, input.defaultCountry);\r\n\r\n const idempotencyKey = input.idempotencyKey ?? getSecureRandomUuid();\r\n return this.createTransfer(\r\n {\r\n recipientIdentifier: normalizedRecipient,\r\n amountMinor: moneyMinorToNumber(input.money.amountMinor),\r\n currency: input.money.currency,\r\n sendAuthToken: input.sendAuthToken,\r\n },\r\n {\r\n accessToken: options.accessToken,\r\n deviceId: options.deviceId,\r\n idempotencyKey,\r\n }\r\n );\r\n }\r\n\r\n async accountSummary(options: AuthorizedOptions): Promise<AccountSummaryResponse> {\r\n return this.requestJson(\r\n \"/api/v1/account/summary\",\r\n {\r\n method: \"GET\",\r\n headers: {\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n undefined,\r\n AccountSummaryResponseSchema\r\n );\r\n }\r\n\r\n async listTransactions(options: ListTransactionsOptions): Promise<TransactionsListResponse> {\r\n const query = new URLSearchParams();\r\n if (typeof options.cursor === \"string\" && options.cursor.trim().length > 0) {\r\n query.set(\"cursor\", options.cursor);\r\n }\r\n if (typeof options.limit === \"number\") {\r\n query.set(\"limit\", String(options.limit));\r\n }\r\n\r\n const path = query.size > 0 ? `/api/v1/transactions?${query.toString()}` : \"/api/v1/transactions\";\r\n return this.requestJson(\r\n path,\r\n {\r\n method: \"GET\",\r\n headers: {\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n undefined,\r\n TransactionsListResponseSchema\r\n );\r\n }\r\n\r\n async transactionDetail(transactionId: string, options: AuthorizedOptions): Promise<TransactionDetailResponse> {\r\n const encodedId = encodeURIComponent(transactionId);\r\n return this.requestJson(\r\n `/api/v1/transactions/${encodedId}`,\r\n {\r\n method: \"GET\",\r\n headers: {\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n undefined,\r\n TransactionDetailResponseSchema\r\n );\r\n }\r\n\r\n async registerPushToken(input: PushRegisterInput, options: AuthorizedOptions): Promise<{ ok: boolean }> {\r\n return this.requestJson(\r\n \"/api/v1/push/register\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n \"content-type\": \"application/json\",\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n PushRegisterRequestSchema,\r\n OkResponseSchema,\r\n input\r\n );\r\n }\r\n\r\n async createPayLink(options: AuthorizedOptions): Promise<CreatePayLinkResponse> {\r\n return this.requestJson(\r\n \"/api/v1/pay-links\",\r\n {\r\n method: \"POST\",\r\n headers: {\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n undefined,\r\n CreatePayLinkResponseSchema,\r\n );\r\n }\r\n\r\n async resolvePayLink(token: string, options: AuthorizedOptions): Promise<ResolvePayLinkResponse> {\r\n const encodedToken = encodeURIComponent(token);\r\n return this.requestJson(\r\n `/api/v1/pay-links/${encodedToken}`,\r\n {\r\n method: \"GET\",\r\n headers: {\r\n authorization: `Bearer ${options.accessToken}`,\r\n },\r\n },\r\n undefined,\r\n ResolvePayLinkResponseSchema,\r\n );\r\n }\r\n\r\n private async requestJson<TInput, TOutput>(\r\n path: string,\r\n init: RequestInit,\r\n requestSchema?: ZodType<TInput>,\r\n responseSchema?: ZodType<TOutput>,\r\n input?: TInput\r\n ): Promise<TOutput> {\r\n const url = `${this.baseUrl}${path}`;\r\n const controller = new AbortController();\r\n const t = setTimeout(() => controller.abort(), this.timeoutMs);\r\n let body = init.body;\r\n try {\r\n body = requestSchema ? JSON.stringify(requestSchema.parse(input)) : init.body;\r\n } catch (err: unknown) {\r\n if (err instanceof z.ZodError) {\r\n throw new FlurError(\"Invalid request payload\", \"INVALID_REQUEST\", {\r\n details: err.flatten(),\r\n });\r\n }\r\n throw err;\r\n }\r\n\r\n try {\r\n let extraHeaders: Record<string, string> = {};\r\n if (this.getExtraHeaders) {\r\n extraHeaders = await this.getExtraHeaders();\r\n }\r\n\r\n const finalHeaders = {\r\n ...init.headers,\r\n ...extraHeaders,\r\n };\r\n\r\n const res = await this.fetchImpl(url, { ...init, headers: finalHeaders, body, signal: controller.signal });\r\n if (!res.ok) throw await mapToFlurError(res);\r\n const payload = (await res.json()) as unknown;\r\n if (!responseSchema) return payload as TOutput;\r\n try {\r\n return responseSchema.parse(payload);\r\n } catch (err: unknown) {\r\n if (err instanceof z.ZodError) {\r\n throw new FlurError(\"SDK contract validation failed\", \"INVALID_REQUEST\", {\r\n details: err.flatten(),\r\n });\r\n }\r\n throw err;\r\n }\r\n } catch (err: unknown) {\r\n if (err instanceof Error && err.name === \"AbortError\") {\r\n throw new FlurError(\"Request timed out\", \"TIMEOUT\");\r\n }\r\n if (err instanceof FlurError) throw err;\r\n throw new FlurError(\"Network error\", \"NETWORK_ERROR\", { details: String(err) });\r\n } finally {\r\n clearTimeout(t);\r\n }\r\n }\r\n}\r\n\r\nfunction getSecureRandomUuid(): string {\r\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\r\n return globalThis.crypto.randomUUID();\r\n }\r\n throw new FlurError(\"Secure UUID generator unavailable; provide idempotencyKey\", \"INVALID_REQUEST\");\r\n}\r\n","import { z } from \"zod\";\r\n\r\nexport const E164Regex = /^\\+[1-9]\\d{7,14}$/;\r\n\r\nconst UuidSchema = z.string().uuid();\r\nconst IsoDateSchema = z.string().datetime({ offset: true });\r\nconst CurrencySchema = z.string().trim().length(3).transform((value) => value.toUpperCase());\r\n\r\nexport const HealthResponseSchema = z.object({\r\n ok: z.boolean(),\r\n});\r\n\r\nexport const WelcomeResponseSchema = z.object({\r\n message: z.string(),\r\n});\r\n\r\nexport const OnboardingStartRequestSchema = z.object({\r\n phoneE164: z.string().regex(E164Regex),\r\n appInstanceId: z.string().min(3),\r\n platform: z.enum([\"android\", \"ios\", \"web\"]),\r\n turnstileToken: z.string().min(20).optional(),\r\n appAttestation: z\r\n .object({\r\n provider: z.enum([\"android\", \"ios\", \"web\"]),\r\n token: z.string().min(24),\r\n })\r\n .optional(),\r\n firstName: z.string().trim().min(1).max(80).optional(),\r\n lastName: z.string().trim().min(1).max(80).optional(),\r\n});\r\n\r\nexport const OnboardingStartResponseSchema = z.object({\r\n requestId: z.string().min(1),\r\n checkUrl: z.string().url().optional(),\r\n expiresInSec: z.number().int().positive(),\r\n fallback: z.enum([\"SILENT_AUTH\", \"OTP\"]),\r\n});\r\n\r\nexport const OnboardingCompleteRequestSchema = z.object({\r\n requestId: z.string().min(1),\r\n code: z.string().min(1).max(32),\r\n appInstanceId: z.string().min(3),\r\n fingerprintHash: z.string().min(3).optional(),\r\n});\r\n\r\nexport const OnboardingCompleteResponseSchema = z.object({\r\n sessionToken: z.string().min(1),\r\n userId: UuidSchema,\r\n restricted: z.boolean(),\r\n risk_reasons: z.array(z.enum([\"SIM_SWAP_RECENT\", \"ROAMING\", \"CARRIER_CHANGED\"])),\r\n stepUpRequired: z.boolean().optional(),\r\n riskStatus: z.enum([\"ok\", \"unavailable\"]).optional(),\r\n});\r\n\r\nexport const RegisterDeviceRequestSchema = z.object({\r\n userId: UuidSchema,\r\n appInstanceId: z.string().min(3),\r\n platform: z.string().min(2),\r\n model: z.string().optional(),\r\n networkSignals: z.object({\r\n ip: z.string().min(3),\r\n asn: z.number().int().optional(),\r\n country: z.string().min(2).optional(),\r\n carrier: z.string().optional(),\r\n }),\r\n});\r\n\r\nexport const RegisterDeviceResponseSchema = z.object({\r\n deviceId: z.string().min(1),\r\n fingerprintHash: z.string().min(1),\r\n driftScore: z.number(),\r\n trustState: z.enum([\"TRUSTED_PRIMARY\", \"TRUSTED_SECONDARY\", \"UNVERIFIED\"]),\r\n stepUpRequired: z.boolean(),\r\n});\r\n\r\nexport const AuthRefreshRequestSchema = z.object({\r\n userId: UuidSchema,\r\n refreshToken: z.string().min(8),\r\n appInstanceId: z.string().min(3),\r\n fingerprintHash: z.string().min(3),\r\n});\r\n\r\nexport const AuthRefreshResponseSchema = z.object({\r\n refreshToken: z.string().min(8),\r\n stepUpRequired: z.boolean(),\r\n});\r\n\r\nexport const AuthLogoutRequestSchema = z.object({\r\n userId: UuidSchema,\r\n refreshToken: z.string().min(8),\r\n});\r\n\r\nexport const PinSetRequestSchema = z.object({\r\n userId: UuidSchema,\r\n pin: z.string().regex(/^\\d{6}$/),\r\n});\r\n\r\nexport const PinVerifyRequestSchema = z.object({\r\n userId: UuidSchema,\r\n pin: z.string().regex(/^\\d{6}$/),\r\n});\r\n\r\nexport const OkResponseSchema = z.object({\r\n ok: z.boolean(),\r\n});\r\n\r\nexport const RegisterSendDeviceKeyRequestSchema = z.object({\r\n userId: UuidSchema,\r\n deviceId: z.string().min(3),\r\n publicKey: z.string().min(32),\r\n});\r\n\r\nexport const SendChallengeRequestSchema = z.object({\r\n userId: UuidSchema,\r\n deviceId: z.string().min(3),\r\n});\r\n\r\nexport const SendChallengeResponseSchema = z.object({\r\n challengeId: UuidSchema,\r\n nonce: z.string().min(1),\r\n expiresAt: IsoDateSchema,\r\n});\r\n\r\nexport const SendVerifyRequestSchema = z.object({\r\n userId: UuidSchema,\r\n deviceId: z.string().min(3),\r\n challengeId: UuidSchema,\r\n signature: z.string().min(16),\r\n});\r\n\r\nexport const SendVerifyResponseSchema = z.object({\r\n sendAuthToken: z.string().min(16),\r\n});\r\n\r\nexport const ResolveRecipientRequestSchema = z.object({\r\n identifier: z.string().min(3),\r\n});\r\n\r\nexport const ResolveRecipientResponseSchema = z.object({\r\n recipientUserId: UuidSchema,\r\n displayName: z.string().min(1),\r\n normalizedIdentifier: z.string().regex(E164Regex),\r\n isActive: z.boolean(),\r\n});\r\n\r\nexport const CreateTransferRequestSchema = z.object({\r\n recipientIdentifier: z.string().min(3),\r\n amountMinor: z.number().int().positive(),\r\n currency: CurrencySchema,\r\n sendAuthToken: z.string().min(16),\r\n});\r\n\r\nconst TransferStatusSchema = z.enum([\"SETTLED\", \"PENDING_REVIEW\", \"DECLINED\"]);\r\n\r\nexport const TransferResponseSchema = z.object({\r\n transactionId: z.string().min(1),\r\n status: TransferStatusSchema,\r\n userStatus: TransferStatusSchema,\r\n recipientName: z.string().min(1),\r\n timestamp: IsoDateSchema,\r\n});\r\n\r\nconst DirectionSchema = z.enum([\"OUTGOING\", \"INCOMING\"]);\r\n\r\nexport const AccountActivityItemSchema = z.object({\r\n id: z.string().min(1),\r\n type: z.string().min(1),\r\n direction: DirectionSchema,\r\n name: z.string().min(1),\r\n identifier: z.string().min(1),\r\n amountMinor: z.number().int(),\r\n currency: CurrencySchema,\r\n status: z.string().min(1),\r\n timestamp: IsoDateSchema,\r\n});\r\n\r\nexport const AccountSummaryResponseSchema = z.object({\r\n balance: z.number().int(),\r\n currency: CurrencySchema,\r\n dailySendLimit: z.number().int().nonnegative(),\r\n dailySendRemaining: z.number().int().nonnegative(),\r\n kycTier: z.string().min(1),\r\n kycStatus: z.string().min(1),\r\n recentActivity: z.array(AccountActivityItemSchema),\r\n});\r\n\r\nexport const TransactionsListResponseSchema = z.object({\r\n items: z.array(AccountActivityItemSchema),\r\n nextCursor: z.string().nullable(),\r\n});\r\n\r\nexport const TransactionDetailResponseSchema = z.object({\r\n transactionId: z.string().min(1),\r\n type: z.string().min(1),\r\n direction: DirectionSchema,\r\n counterpartyName: z.string().min(1),\r\n counterpartyIdentifier: z.string().min(1),\r\n amountMinor: z.number().int(),\r\n currency: CurrencySchema,\r\n status: z.string().min(1),\r\n timestamp: IsoDateSchema,\r\n});\r\n\r\nexport const PushRegisterRequestSchema = z.object({\r\n deviceId: z.string().min(3),\r\n platform: z.enum([\"ios\", \"android\", \"web\"]),\r\n token: z.string().min(16),\r\n});\r\n\r\nexport const CreatePayLinkResponseSchema = z.object({\r\n token: z.string().min(1),\r\n});\r\n\r\nexport const ResolvePayLinkResponseSchema = z.object({\r\n recipientUserId: UuidSchema,\r\n displayName: z.string().min(1),\r\n normalizedIdentifier: z.string().regex(E164Regex),\r\n isActive: z.boolean(),\r\n});\r\n","export type FlurErrorCode =\r\n | \"NETWORK_ERROR\"\r\n | \"HTTP_ERROR\"\r\n | \"TIMEOUT\"\r\n | \"UNKNOWN\"\r\n | \"UNAUTHORIZED\"\r\n | \"INVALID_REQUEST\"\r\n | \"RATE_LIMITED\"\r\n | \"NOT_FOUND\"\r\n | \"USER_NOT_FOUND\"\r\n | \"TOKEN_REPLAYED\"\r\n | \"SESSION_MISMATCH\"\r\n | \"PIN_INVALID\"\r\n | \"PIN_LOCKED\"\r\n | \"PIN_NOT_SET\"\r\n | \"STEP_UP_REQUIRED\"\r\n | \"DEVICE_KEY_NOT_REGISTERED\"\r\n | \"CHALLENGE_INVALID\"\r\n | \"CHALLENGE_EXPIRED\"\r\n | \"DEVICE_KEY_REVOKED\"\r\n | \"SIGNATURE_INVALID\"\r\n | \"INVALID_RECIPIENT\"\r\n | \"SEND_AUTH_INVALID\"\r\n | \"IDEMPOTENCY_KEY_CONFLICT\"\r\n | \"IDEMPOTENCY_IN_PROGRESS\"\r\n | \"CANNOT_SEND_TO_SELF\"\r\n | \"INSUFFICIENT_FUNDS\";\r\n\r\nconst backendErrorCodeSet: ReadonlySet<FlurErrorCode> = new Set([\r\n \"UNAUTHORIZED\",\r\n \"INVALID_REQUEST\",\r\n \"RATE_LIMITED\",\r\n \"NOT_FOUND\",\r\n \"USER_NOT_FOUND\",\r\n \"TOKEN_REPLAYED\",\r\n \"SESSION_MISMATCH\",\r\n \"PIN_INVALID\",\r\n \"PIN_LOCKED\",\r\n \"PIN_NOT_SET\",\r\n \"STEP_UP_REQUIRED\",\r\n \"DEVICE_KEY_NOT_REGISTERED\",\r\n \"CHALLENGE_INVALID\",\r\n \"CHALLENGE_EXPIRED\",\r\n \"DEVICE_KEY_REVOKED\",\r\n \"SIGNATURE_INVALID\",\r\n \"INVALID_RECIPIENT\",\r\n \"SEND_AUTH_INVALID\",\r\n \"IDEMPOTENCY_KEY_CONFLICT\",\r\n \"IDEMPOTENCY_IN_PROGRESS\",\r\n \"CANNOT_SEND_TO_SELF\",\r\n \"INSUFFICIENT_FUNDS\",\r\n]);\r\n\r\nexport class FlurError extends Error {\r\n public readonly code: FlurErrorCode;\r\n public readonly status?: number;\r\n public readonly details?: unknown;\r\n public readonly reqId?: string | null;\r\n\r\n constructor(message: string, code: FlurErrorCode, opts?: { status?: number; details?: unknown; reqId?: string | null }) {\r\n super(message);\r\n this.name = \"FlurError\";\r\n this.code = code;\r\n this.status = opts?.status;\r\n this.details = opts?.details;\r\n this.reqId = opts?.reqId;\r\n }\r\n}\r\n\r\nexport async function mapToFlurError(res: Response): Promise<FlurError> {\r\n const reqId = res.headers.get(\"x-request-id\");\r\n let details: unknown = undefined;\r\n let mappedCode: FlurErrorCode = \"HTTP_ERROR\";\r\n let mappedMessage = `HTTP ${res.status}`;\r\n try {\r\n const ct = res.headers.get(\"content-type\") ?? \"\";\r\n details = ct.includes(\"application/json\") ? await res.json() : await res.text();\r\n if (details && typeof details === \"object\") {\r\n const candidateCode = (details as { code?: unknown }).code;\r\n const candidateMessage = (details as { message?: unknown; error?: unknown }).message;\r\n const fallbackMessage = (details as { error?: unknown }).error;\r\n\r\n if (typeof candidateCode === \"string\" && backendErrorCodeSet.has(candidateCode as FlurErrorCode)) {\r\n mappedCode = candidateCode as FlurErrorCode;\r\n }\r\n\r\n if (typeof candidateMessage === \"string\" && candidateMessage.length > 0) {\r\n mappedMessage = candidateMessage;\r\n } else if (typeof fallbackMessage === \"string\" && fallbackMessage.length > 0) {\r\n mappedMessage = fallbackMessage;\r\n }\r\n }\r\n } catch {\r\n // ignore parse issues\r\n }\r\n return new FlurError(mappedMessage, mappedCode, { status: res.status, details, reqId });\r\n}\r\n","import { z } from \"zod\";\r\nimport { FlurError } from \"./errors.js\";\r\nimport { E164Regex } from \"./contracts.js\";\r\n\r\nexport type Money = {\r\n amountMinor: bigint;\r\n currency: string;\r\n};\r\n\r\nconst CurrencyCodeSchema = z.string().trim().length(3).transform((value) => value.toUpperCase());\r\n\r\nconst currencyFractionDigits: Record<string, number> = {\r\n NGN: 2,\r\n USD: 2,\r\n EUR: 2,\r\n GBP: 2,\r\n CAD: 2,\r\n JPY: 0,\r\n};\r\n\r\nfunction getFractionDigits(currency: string): number {\r\n return currencyFractionDigits[currency] ?? 2;\r\n}\r\n\r\nexport function parseAmountInput(value: string, currency: string): Money {\r\n const normalizedCurrency = CurrencyCodeSchema.parse(currency);\r\n const raw = value.trim();\r\n\r\n if (!/^\\d+(\\.\\d+)?$/.test(raw)) {\r\n throw new FlurError(\"Invalid amount format\", \"INVALID_REQUEST\");\r\n }\r\n\r\n const [whole, fraction = \"\"] = raw.split(\".\");\r\n const fractionDigits = getFractionDigits(normalizedCurrency);\r\n\r\n if (fraction.length > fractionDigits) {\r\n throw new FlurError(\"Too many decimal places for currency\", \"INVALID_REQUEST\");\r\n }\r\n\r\n const paddedFraction = fraction.padEnd(fractionDigits, \"0\");\r\n const minorAsString = `${whole}${paddedFraction}`.replace(/^0+(\\d)/, \"$1\") || \"0\";\r\n const amountMinor = BigInt(minorAsString);\r\n\r\n if (amountMinor < 0n) {\r\n throw new FlurError(\"Amount cannot be negative\", \"INVALID_REQUEST\");\r\n }\r\n\r\n return {\r\n amountMinor,\r\n currency: normalizedCurrency,\r\n };\r\n}\r\n\r\nexport function formatAmount(amountMinor: bigint, currency: string): string {\r\n const normalizedCurrency = CurrencyCodeSchema.parse(currency);\r\n if (amountMinor < 0n) {\r\n throw new FlurError(\"Amount cannot be negative\", \"INVALID_REQUEST\");\r\n }\r\n\r\n const fractionDigits = getFractionDigits(normalizedCurrency);\r\n if (fractionDigits === 0) {\r\n return amountMinor.toString();\r\n }\r\n\r\n const divisor = 10n ** BigInt(fractionDigits);\r\n const whole = amountMinor / divisor;\r\n const fraction = (amountMinor % divisor).toString().padStart(fractionDigits, \"0\");\r\n return `${whole.toString()}.${fraction}`;\r\n}\r\n\r\nconst defaultCountryDialingCode: Record<string, string> = {\r\n NG: \"234\",\r\n US: \"1\",\r\n CA: \"1\",\r\n GB: \"44\",\r\n};\r\n\r\nexport function normalizeE164(input: string, defaultCountry?: string): string {\r\n const trimmed = input.trim();\r\n\r\n if (trimmed.startsWith(\"+\")) {\r\n if (!E164Regex.test(trimmed)) {\r\n throw new FlurError(\"Invalid phone number\", \"INVALID_REQUEST\");\r\n }\r\n return trimmed;\r\n }\r\n\r\n const digits = trimmed.replace(/\\D/g, \"\");\r\n if (digits.length === 0) {\r\n throw new FlurError(\"Invalid phone number\", \"INVALID_REQUEST\");\r\n }\r\n\r\n if (digits.startsWith(\"00\")) {\r\n const candidate = `+${digits.slice(2)}`;\r\n if (!E164Regex.test(candidate)) {\r\n throw new FlurError(\"Invalid phone number\", \"INVALID_REQUEST\");\r\n }\r\n return candidate;\r\n }\r\n\r\n const normalizedCountry = defaultCountry?.trim().toUpperCase();\r\n const countryCode = normalizedCountry ? defaultCountryDialingCode[normalizedCountry] : undefined;\r\n if (!countryCode) {\r\n throw new FlurError(\"Invalid phone number\", \"INVALID_REQUEST\");\r\n }\r\n\r\n const localDigits = digits.startsWith(\"0\") ? digits.slice(1) : digits;\r\n const candidate = `+${countryCode}${localDigits}`;\r\n if (!E164Regex.test(candidate)) {\r\n throw new FlurError(\"Invalid phone number\", \"INVALID_REQUEST\");\r\n }\r\n return candidate;\r\n}\r\n\r\nexport function moneyMinorToNumber(amountMinor: bigint): number {\r\n const asNumber = Number(amountMinor);\r\n if (!Number.isSafeInteger(asNumber)) {\r\n throw new FlurError(\"Amount exceeds safe integer range\", \"INVALID_REQUEST\");\r\n }\r\n return asNumber;\r\n}\r\n"],"mappings":"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","names":["z","z","z","candidate","z"]}
@@ -1,235 +1,235 @@
1
- {
2
- "openapi": "3.0.3",
3
- "info": {
4
- "title": "Flur API",
5
- "version": "0.1.0"
6
- },
7
- "paths": {
8
- "/health": {
9
- "get": {
10
- "responses": {
11
- "200": {
12
- "description": "OK",
13
- "content": {
14
- "application/json": {
15
- "schema": {
16
- "type": "object",
17
- "properties": {
18
- "ok": {
19
- "type": "boolean"
20
- }
21
- },
22
- "required": [
23
- "ok"
24
- ]
25
- }
26
- }
27
- }
28
- }
29
- }
30
- }
31
- },
32
- "/welcome": {
33
- "get": {
34
- "responses": {
35
- "200": {
36
- "description": "OK",
37
- "content": {
38
- "application/json": {
39
- "schema": {
40
- "type": "object",
41
- "properties": {
42
- "message": {
43
- "type": "string",
44
- "example": "welcome to flur wallet. Today is 2026-02-07T19:09:53.238Z"
45
- }
46
- },
47
- "required": [
48
- "message"
49
- ]
50
- }
51
- }
52
- }
53
- }
54
- }
55
- }
56
- },
57
- "/v1/onboarding/start": {
58
- "post": {
59
- "requestBody": {
60
- "required": true,
61
- "content": {
62
- "application/json": {
63
- "schema": {
64
- "type": "object",
65
- "properties": {
66
- "phoneE164": {
67
- "type": "string"
68
- }
69
- },
70
- "required": [
71
- "phoneE164"
72
- ]
73
- }
74
- }
75
- }
76
- },
77
- "responses": {
78
- "200": {
79
- "description": "Verification started",
80
- "content": {
81
- "application/json": {
82
- "schema": {
83
- "type": "object",
84
- "properties": {
85
- "requestId": {
86
- "type": "string"
87
- },
88
- "checkUrl": {
89
- "type": "string"
90
- },
91
- "expiresInSec": {
92
- "type": "integer"
93
- },
94
- "fallback": {
95
- "type": "string",
96
- "enum": [
97
- "SILENT_AUTH",
98
- "OTP"
99
- ]
100
- }
101
- },
102
- "required": [
103
- "requestId",
104
- "expiresInSec",
105
- "fallback"
106
- ]
107
- }
108
- }
109
- }
110
- },
111
- "400": {
112
- "description": "Unable to start verification",
113
- "content": {
114
- "application/json": {
115
- "schema": {
116
- "type": "object",
117
- "properties": {
118
- "error": {
119
- "type": "string"
120
- }
121
- },
122
- "required": [
123
- "error"
124
- ]
125
- }
126
- }
127
- }
128
- },
129
- "429": {
130
- "description": "Rate limited",
131
- "content": {
132
- "application/json": {
133
- "schema": {
134
- "type": "object",
135
- "properties": {
136
- "error": {
137
- "type": "string"
138
- }
139
- },
140
- "required": [
141
- "error"
142
- ]
143
- }
144
- }
145
- }
146
- }
147
- }
148
- }
149
- },
150
- "/v1/onboarding/complete": {
151
- "post": {
152
- "requestBody": {
153
- "required": true,
154
- "content": {
155
- "application/json": {
156
- "schema": {
157
- "type": "object",
158
- "properties": {
159
- "requestId": {
160
- "type": "string"
161
- },
162
- "code": {
163
- "type": "string"
164
- }
165
- },
166
- "required": [
167
- "requestId",
168
- "code"
169
- ]
170
- }
171
- }
172
- }
173
- },
174
- "responses": {
175
- "200": {
176
- "description": "Verification completed",
177
- "content": {
178
- "application/json": {
179
- "schema": {
180
- "type": "object",
181
- "properties": {
182
- "sessionToken": {
183
- "type": "string"
184
- },
185
- "userId": {
186
- "type": "string"
187
- },
188
- "restricted": {
189
- "type": "boolean"
190
- },
191
- "risk_reasons": {
192
- "type": "array",
193
- "items": {
194
- "type": "string",
195
- "enum": [
196
- "SIM_SWAP_RECENT",
197
- "ROAMING",
198
- "CARRIER_CHANGED"
199
- ]
200
- }
201
- }
202
- },
203
- "required": [
204
- "sessionToken",
205
- "userId",
206
- "restricted",
207
- "risk_reasons"
208
- ]
209
- }
210
- }
211
- }
212
- },
213
- "400": {
214
- "description": "Verification failed",
215
- "content": {
216
- "application/json": {
217
- "schema": {
218
- "type": "object",
219
- "properties": {
220
- "error": {
221
- "type": "string"
222
- }
223
- },
224
- "required": [
225
- "error"
226
- ]
227
- }
228
- }
229
- }
230
- }
231
- }
232
- }
233
- }
234
- }
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "Flur API",
5
+ "version": "0.1.0"
6
+ },
7
+ "paths": {
8
+ "/health": {
9
+ "get": {
10
+ "responses": {
11
+ "200": {
12
+ "description": "OK",
13
+ "content": {
14
+ "application/json": {
15
+ "schema": {
16
+ "type": "object",
17
+ "properties": {
18
+ "ok": {
19
+ "type": "boolean"
20
+ }
21
+ },
22
+ "required": [
23
+ "ok"
24
+ ]
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ },
32
+ "/welcome": {
33
+ "get": {
34
+ "responses": {
35
+ "200": {
36
+ "description": "OK",
37
+ "content": {
38
+ "application/json": {
39
+ "schema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "message": {
43
+ "type": "string",
44
+ "example": "welcome to flur wallet. Today is 2026-02-07T19:09:53.238Z"
45
+ }
46
+ },
47
+ "required": [
48
+ "message"
49
+ ]
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "/v1/onboarding/start": {
58
+ "post": {
59
+ "requestBody": {
60
+ "required": true,
61
+ "content": {
62
+ "application/json": {
63
+ "schema": {
64
+ "type": "object",
65
+ "properties": {
66
+ "phoneE164": {
67
+ "type": "string"
68
+ }
69
+ },
70
+ "required": [
71
+ "phoneE164"
72
+ ]
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "responses": {
78
+ "200": {
79
+ "description": "Verification started",
80
+ "content": {
81
+ "application/json": {
82
+ "schema": {
83
+ "type": "object",
84
+ "properties": {
85
+ "requestId": {
86
+ "type": "string"
87
+ },
88
+ "checkUrl": {
89
+ "type": "string"
90
+ },
91
+ "expiresInSec": {
92
+ "type": "integer"
93
+ },
94
+ "fallback": {
95
+ "type": "string",
96
+ "enum": [
97
+ "SILENT_AUTH",
98
+ "OTP"
99
+ ]
100
+ }
101
+ },
102
+ "required": [
103
+ "requestId",
104
+ "expiresInSec",
105
+ "fallback"
106
+ ]
107
+ }
108
+ }
109
+ }
110
+ },
111
+ "400": {
112
+ "description": "Unable to start verification",
113
+ "content": {
114
+ "application/json": {
115
+ "schema": {
116
+ "type": "object",
117
+ "properties": {
118
+ "error": {
119
+ "type": "string"
120
+ }
121
+ },
122
+ "required": [
123
+ "error"
124
+ ]
125
+ }
126
+ }
127
+ }
128
+ },
129
+ "429": {
130
+ "description": "Rate limited",
131
+ "content": {
132
+ "application/json": {
133
+ "schema": {
134
+ "type": "object",
135
+ "properties": {
136
+ "error": {
137
+ "type": "string"
138
+ }
139
+ },
140
+ "required": [
141
+ "error"
142
+ ]
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
150
+ "/v1/onboarding/complete": {
151
+ "post": {
152
+ "requestBody": {
153
+ "required": true,
154
+ "content": {
155
+ "application/json": {
156
+ "schema": {
157
+ "type": "object",
158
+ "properties": {
159
+ "requestId": {
160
+ "type": "string"
161
+ },
162
+ "code": {
163
+ "type": "string"
164
+ }
165
+ },
166
+ "required": [
167
+ "requestId",
168
+ "code"
169
+ ]
170
+ }
171
+ }
172
+ }
173
+ },
174
+ "responses": {
175
+ "200": {
176
+ "description": "Verification completed",
177
+ "content": {
178
+ "application/json": {
179
+ "schema": {
180
+ "type": "object",
181
+ "properties": {
182
+ "sessionToken": {
183
+ "type": "string"
184
+ },
185
+ "userId": {
186
+ "type": "string"
187
+ },
188
+ "restricted": {
189
+ "type": "boolean"
190
+ },
191
+ "risk_reasons": {
192
+ "type": "array",
193
+ "items": {
194
+ "type": "string",
195
+ "enum": [
196
+ "SIM_SWAP_RECENT",
197
+ "ROAMING",
198
+ "CARRIER_CHANGED"
199
+ ]
200
+ }
201
+ }
202
+ },
203
+ "required": [
204
+ "sessionToken",
205
+ "userId",
206
+ "restricted",
207
+ "risk_reasons"
208
+ ]
209
+ }
210
+ }
211
+ }
212
+ },
213
+ "400": {
214
+ "description": "Verification failed",
215
+ "content": {
216
+ "application/json": {
217
+ "schema": {
218
+ "type": "object",
219
+ "properties": {
220
+ "error": {
221
+ "type": "string"
222
+ }
223
+ },
224
+ "required": [
225
+ "error"
226
+ ]
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
235
  }
package/package.json CHANGED
@@ -1,50 +1,50 @@
1
- {
2
- "name": "@nokinc-flur/sdk",
3
- "version": "0.1.6",
4
- "description": "Flur Wallet SDK (sprint 1 scaffold)",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "openapi",
18
- "README.md",
19
- "LICENSE"
20
- ],
21
- "scripts": {
22
- "lint": "eslint .",
23
- "typecheck": "tsc -p tsconfig.json --noEmit",
24
- "test": "vitest run",
25
- "test:contract": "vitest run test/e2e.health.test.ts test/e2e.send-money.test.ts",
26
- "e2e:bootstrap": "node scripts/bootstrap-e2e-sendmoney.mjs",
27
- "build": "tsup src/index.ts --format esm --dts",
28
- "gen": "openapi-typescript openapi/flur.openapi.json -o src/gen/openapi-types.ts",
29
- "prod:release": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/prod-build-publish-npm.ps1",
30
- "prod:release:publish": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/prod-build-publish-npm.ps1 -Publish",
31
- "release:patch": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump patch",
32
- "release:minor": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump minor",
33
- "release:major": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump major"
34
- },
35
- "dependencies": {
36
- "zod": "^3.23.8"
37
- },
38
- "devDependencies": {
39
- "@types/node": "^20.11.30",
40
- "@typescript-eslint/eslint-plugin": "^7.7.1",
41
- "@typescript-eslint/parser": "^7.7.1",
42
- "eslint": "^8.57.0",
43
- "eslint-config-prettier": "^9.1.0",
44
- "openapi-typescript": "^7.5.1",
45
- "prettier": "^3.2.5",
46
- "tsup": "^8.0.2",
47
- "typescript": "^5.4.5",
48
- "vitest": "^1.5.0"
49
- }
50
- }
1
+ {
2
+ "name": "@nokinc-flur/sdk",
3
+ "version": "0.1.7",
4
+ "description": "Flur Wallet SDK (sprint 1 scaffold)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "openapi",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "lint": "eslint .",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run",
25
+ "test:contract": "vitest run test/e2e.health.test.ts test/e2e.send-money.test.ts",
26
+ "e2e:bootstrap": "node scripts/bootstrap-e2e-sendmoney.mjs",
27
+ "build": "tsup src/index.ts --format esm --dts --sourcemap",
28
+ "gen": "openapi-typescript openapi/flur.openapi.json -o src/gen/openapi-types.ts",
29
+ "prod:release": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/prod-build-publish-npm.ps1",
30
+ "prod:release:publish": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/prod-build-publish-npm.ps1 -Publish",
31
+ "release:patch": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump patch",
32
+ "release:minor": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump minor",
33
+ "release:major": "pwsh -NoProfile -ExecutionPolicy Bypass -File ./scripts/release.ps1 -Bump major"
34
+ },
35
+ "dependencies": {
36
+ "zod": "^3.23.8"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.11.30",
40
+ "@typescript-eslint/eslint-plugin": "^7.7.1",
41
+ "@typescript-eslint/parser": "^7.7.1",
42
+ "eslint": "^8.57.0",
43
+ "eslint-config-prettier": "^9.1.0",
44
+ "openapi-typescript": "^7.5.1",
45
+ "prettier": "^3.2.5",
46
+ "tsup": "^8.0.2",
47
+ "typescript": "^5.4.5",
48
+ "vitest": "^1.5.0"
49
+ }
50
+ }