@nokinc-flur/sdk 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,201 +1,11 @@
1
1
  // src/client.ts
2
- import { z as z4 } from "zod";
2
+ import { z as z5 } from "zod";
3
3
 
4
4
  // src/contracts.ts
5
+ import { z as z2 } from "zod";
6
+
7
+ // src/me-offline/client.ts
5
8
  import { z } from "zod";
6
- var E164Regex = /^\+[1-9]\d{7,14}$/;
7
- var UuidSchema = z.string().uuid();
8
- var IsoDateSchema = z.string().datetime({ offset: true });
9
- var CurrencySchema = z.string().trim().length(3).transform((value) => value.toUpperCase());
10
- var HealthResponseSchema = z.object({
11
- ok: z.boolean()
12
- });
13
- var WelcomeResponseSchema = z.object({
14
- message: z.string()
15
- });
16
- var OnboardingStartRequestSchema = z.object({
17
- phoneE164: z.string().regex(E164Regex),
18
- appInstanceId: z.string().min(3),
19
- platform: z.enum(["android", "ios", "web"]),
20
- turnstileToken: z.string().min(20).optional(),
21
- appAttestation: z.object({
22
- provider: z.enum(["android", "ios", "web"]),
23
- token: z.string().min(24)
24
- }).optional(),
25
- firstName: z.string().trim().min(1).max(80).optional(),
26
- lastName: z.string().trim().min(1).max(80).optional()
27
- });
28
- var OnboardingStartResponseSchema = z.object({
29
- requestId: z.string().min(1),
30
- checkUrl: z.string().url().optional(),
31
- expiresInSec: z.number().int().positive(),
32
- fallback: z.enum(["SILENT_AUTH", "OTP"])
33
- });
34
- var OnboardingCompleteRequestSchema = z.object({
35
- requestId: z.string().min(1),
36
- code: z.string().min(1).max(32),
37
- appInstanceId: z.string().min(3),
38
- fingerprintHash: z.string().min(3).optional()
39
- });
40
- var OnboardingCompleteResponseSchema = z.object({
41
- sessionToken: z.string().min(1),
42
- userId: UuidSchema,
43
- restricted: z.boolean(),
44
- risk_reasons: z.array(
45
- z.enum(["SIM_SWAP_RECENT", "ROAMING", "CARRIER_CHANGED"])
46
- ),
47
- stepUpRequired: z.boolean().optional(),
48
- riskStatus: z.enum(["ok", "unavailable"]).optional()
49
- });
50
- var RegisterDeviceRequestSchema = z.object({
51
- userId: UuidSchema,
52
- appInstanceId: z.string().min(3),
53
- platform: z.string().min(2),
54
- model: z.string().optional(),
55
- networkSignals: z.object({
56
- ip: z.string().min(3),
57
- asn: z.number().int().optional(),
58
- country: z.string().min(2).optional(),
59
- carrier: z.string().optional()
60
- })
61
- });
62
- var RegisterDeviceResponseSchema = z.object({
63
- deviceId: z.string().min(1),
64
- fingerprintHash: z.string().min(1),
65
- driftScore: z.number(),
66
- trustState: z.enum(["TRUSTED_PRIMARY", "TRUSTED_SECONDARY", "UNVERIFIED"]),
67
- stepUpRequired: z.boolean()
68
- });
69
- var AuthRefreshRequestSchema = z.object({
70
- userId: UuidSchema,
71
- refreshToken: z.string().min(8),
72
- appInstanceId: z.string().min(3),
73
- fingerprintHash: z.string().min(3)
74
- });
75
- var AuthRefreshResponseSchema = z.object({
76
- refreshToken: z.string().min(8),
77
- stepUpRequired: z.boolean()
78
- });
79
- var AuthLogoutRequestSchema = z.object({
80
- userId: UuidSchema,
81
- refreshToken: z.string().min(8)
82
- });
83
- var PinSetRequestSchema = z.object({
84
- userId: UuidSchema,
85
- pin: z.string().regex(/^\d{6}$/)
86
- });
87
- var PinVerifyRequestSchema = z.object({
88
- userId: UuidSchema,
89
- pin: z.string().regex(/^\d{6}$/)
90
- });
91
- var OkResponseSchema = z.object({
92
- ok: z.boolean()
93
- });
94
- var RegisterSendDeviceKeyRequestSchema = z.object({
95
- userId: UuidSchema,
96
- deviceId: z.string().min(3),
97
- publicKey: z.string().min(32)
98
- });
99
- var SEND_AUTH_PURPOSES = ["send_money", "offline_revoke"];
100
- var SendChallengeRequestSchema = z.object({
101
- userId: UuidSchema,
102
- deviceId: z.string().min(3),
103
- purpose: z.enum(SEND_AUTH_PURPOSES).optional()
104
- });
105
- var SendChallengeResponseSchema = z.object({
106
- challengeId: UuidSchema,
107
- nonce: z.string().min(1),
108
- expiresAt: IsoDateSchema
109
- });
110
- var SendVerifyRequestSchema = z.object({
111
- userId: UuidSchema,
112
- deviceId: z.string().min(3),
113
- challengeId: UuidSchema,
114
- signature: z.string().min(16)
115
- });
116
- var SendVerifyResponseSchema = z.object({
117
- sendAuthToken: z.string().min(16)
118
- });
119
- var ResolveRecipientRequestSchema = z.object({
120
- identifier: z.string().min(3)
121
- });
122
- var ResolveRecipientResponseSchema = z.object({
123
- recipientUserId: UuidSchema,
124
- displayName: z.string().min(1),
125
- normalizedIdentifier: z.string().regex(E164Regex),
126
- isActive: z.boolean()
127
- });
128
- var CreateTransferRequestSchema = z.object({
129
- recipientIdentifier: z.string().min(3),
130
- amountMinor: z.number().int().positive(),
131
- currency: CurrencySchema,
132
- sendAuthToken: z.string().min(16)
133
- });
134
- var TransferStatusSchema = z.enum(["SETTLED", "PENDING_REVIEW", "DECLINED"]);
135
- var TransferResponseSchema = z.object({
136
- transactionId: z.string().min(1),
137
- status: TransferStatusSchema,
138
- userStatus: TransferStatusSchema,
139
- recipientName: z.string().min(1),
140
- timestamp: IsoDateSchema
141
- });
142
- var DirectionSchema = z.enum(["OUTGOING", "INCOMING"]);
143
- var AccountActivityItemSchema = z.object({
144
- id: z.string().min(1),
145
- type: z.string().min(1),
146
- direction: DirectionSchema,
147
- name: z.string().min(1),
148
- identifier: z.string().min(1),
149
- amountMinor: z.number().int(),
150
- currency: CurrencySchema,
151
- status: z.string().min(1),
152
- timestamp: IsoDateSchema
153
- });
154
- var AccountSummaryResponseSchema = z.object({
155
- /** Authenticated user's stable internal id. */
156
- userId: UuidSchema,
157
- /**
158
- * 10-digit Nigeria Uniform Bank Account Number (NUBAN) allocated by the
159
- * bank partner. `null` when the user has no partner-allocated account yet.
160
- */
161
- nuban: z.string().regex(/^[0-9]{10}$/).nullable(),
162
- balance: z.number().int(),
163
- currency: CurrencySchema,
164
- dailySendLimit: z.number().int().nonnegative(),
165
- dailySendRemaining: z.number().int().nonnegative(),
166
- kycTier: z.string().min(1),
167
- kycStatus: z.string().min(1),
168
- recentActivity: z.array(AccountActivityItemSchema)
169
- });
170
- var TransactionsListResponseSchema = z.object({
171
- items: z.array(AccountActivityItemSchema),
172
- nextCursor: z.string().nullable()
173
- });
174
- var TransactionDetailResponseSchema = z.object({
175
- transactionId: z.string().min(1),
176
- type: z.string().min(1),
177
- direction: DirectionSchema,
178
- counterpartyName: z.string().min(1),
179
- counterpartyIdentifier: z.string().min(1),
180
- amountMinor: z.number().int(),
181
- currency: CurrencySchema,
182
- status: z.string().min(1),
183
- timestamp: IsoDateSchema
184
- });
185
- var PushRegisterRequestSchema = z.object({
186
- deviceId: z.string().min(3),
187
- platform: z.enum(["ios", "android", "web"]),
188
- token: z.string().min(16)
189
- });
190
- var CreatePayLinkResponseSchema = z.object({
191
- token: z.string().min(1)
192
- });
193
- var ResolvePayLinkResponseSchema = z.object({
194
- recipientUserId: UuidSchema,
195
- displayName: z.string().min(1),
196
- normalizedIdentifier: z.string().regex(E164Regex),
197
- isActive: z.boolean()
198
- });
199
9
 
200
10
  // src/errors.ts
201
11
  var backendErrorCodeSet = /* @__PURE__ */ new Set([
@@ -299,9 +109,556 @@ async function mapToFlurError(res) {
299
109
  });
300
110
  }
301
111
 
112
+ // src/me-offline/client.ts
113
+ var Hex64 = z.string().regex(/^[0-9a-f]{64}$/i);
114
+ var Sha256Hex = z.string().regex(/^[0-9a-f]{64}$/i);
115
+ var Base64Std = z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
116
+ var RegisterDeviceKeyInputSchema = z.object({
117
+ deviceId: z.string().min(1).max(128),
118
+ publicKeyHex: Hex64
119
+ });
120
+ var AttestationSecurityLevelSchema = z.enum([
121
+ "STRONGBOX",
122
+ "TEE",
123
+ "SECURE_ENCLAVE",
124
+ "SOFTWARE"
125
+ ]);
126
+ var DeviceKeyAlgSchema = z.literal("p256");
127
+ var RegisterDeviceKeyP256InputSchema = z.object({
128
+ deviceId: z.string().min(1).max(128),
129
+ /** P-256 SubjectPublicKeyInfo DER, base64. */
130
+ publicKeySpkiB64: Base64Std.min(64).max(4096),
131
+ /** Base64 of the server-issued enrollment challenge string. */
132
+ challengeB64: Base64Std.min(8).max(1024),
133
+ /** iOS App Attest payload or Android X.509 Key Attestation chain. */
134
+ attestationChainB64: z.array(Base64Std.min(16).max(16384)).min(1).max(16),
135
+ securityLevel: AttestationSecurityLevelSchema
136
+ });
137
+ var P256EnrollmentChallengeInputSchema = z.object({
138
+ deviceId: z.string().min(1).max(128)
139
+ });
140
+ var P256EnrollmentChallengeResultSchema = z.object({
141
+ challenge: z.string().min(16),
142
+ expiresAtMs: z.number().int().positive()
143
+ });
144
+ var DeviceKeyRecordSchema = z.object({
145
+ id: z.string().uuid(),
146
+ userId: z.string().uuid(),
147
+ deviceId: z.string(),
148
+ /** Always 'p256' on the consumer offline rail. Field retained for forward-compat. */
149
+ alg: DeviceKeyAlgSchema.default("p256"),
150
+ /** Legacy ed25519 hex key. Always null on new records (kept for back-compat reads). */
151
+ publicKeyHex: Hex64.nullable().default(null),
152
+ /** P-256 SubjectPublicKeyInfo DER, base64. Required for new records. */
153
+ publicKeySpkiB64: Base64Std.nullable().default(null),
154
+ securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
155
+ hardwareBacked: z.boolean().default(false),
156
+ attestedAtMs: z.number().int().nonnegative().nullable().default(null),
157
+ createdAtMs: z.number().int().nonnegative(),
158
+ revokedAtMs: z.number().int().nonnegative().nullable()
159
+ });
160
+ var ConsumerOACSchema = z.object({
161
+ oacId: z.string().uuid(),
162
+ issuerId: z.string().min(1).max(64),
163
+ userId: z.string().uuid(),
164
+ deviceId: z.string().min(1).max(128),
165
+ /**
166
+ * Always 'p256'. Required on the wire (backend always emits it).
167
+ * Kept as a literal so input/output infer identically and the schema
168
+ * can be nested inside other response shapes without Zod input/output
169
+ * divergence under tsup DTS bundling.
170
+ */
171
+ alg: z.literal("p256"),
172
+ /** P-256 SubjectPublicKeyInfo DER, base64. */
173
+ devicePubkeySpkiB64: Base64Std.min(64).max(4096),
174
+ perTxCapKobo: z.number().int().positive(),
175
+ cumulativeCapKobo: z.number().int().positive(),
176
+ currency: z.string().length(3),
177
+ validFromMs: z.number().int().nonnegative(),
178
+ validUntilMs: z.number().int().nonnegative(),
179
+ counterSeed: z.number().int().nonnegative(),
180
+ issuedAtMs: z.number().int().nonnegative()
181
+ });
182
+ var SignedConsumerOACSchema = z.object({
183
+ oac: ConsumerOACSchema,
184
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
185
+ issuerSig: Base64Std.min(16).max(2048),
186
+ /** Issuer's P-256 public key as SubjectPublicKeyInfo DER, base64. */
187
+ issuerPublicKeySpkiB64: Base64Std.min(64).max(4096)
188
+ });
189
+ var OACRecordSchema = SignedConsumerOACSchema.extend({
190
+ currentOfflineSpentKobo: z.number().int().nonnegative(),
191
+ status: z.enum([
192
+ "active",
193
+ "superseded",
194
+ "expired",
195
+ "revoked",
196
+ "disabling",
197
+ "draining",
198
+ "closed"
199
+ ]),
200
+ supersededAtMs: z.number().int().nonnegative().nullable(),
201
+ revokedAtMs: z.number().int().nonnegative().nullable(),
202
+ holdId: z.string().uuid().nullable().optional()
203
+ });
204
+ var IssueOACInputSchema = z.object({
205
+ deviceId: z.string().min(1).max(128),
206
+ perTxCapKobo: z.number().int().positive().optional(),
207
+ cumulativeCapKobo: z.number().int().positive().optional(),
208
+ ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
209
+ spendableOnlineKobo: z.number().int().nonnegative().optional()
210
+ });
211
+ var IssueAccountOacInputSchema = z.object({
212
+ deviceId: z.string().min(1).max(128),
213
+ perTxCapKobo: z.number().int().positive().optional(),
214
+ cumulativeCapKobo: z.number().int().positive().optional(),
215
+ ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional()
216
+ });
217
+ var EnableOfflineInputSchema = z.object({
218
+ deviceId: z.string().min(1).max(128),
219
+ amountKobo: z.number().int().positive(),
220
+ perTxCapKobo: z.number().int().positive().optional(),
221
+ ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
222
+ installId: z.string().min(1).max(128),
223
+ partnerId: z.string().min(1).max(64).optional()
224
+ });
225
+ var ProvisionOfflineAllowanceInputSchema = EnableOfflineInputSchema;
226
+ var DisableOfflineInputSchema = z.object({
227
+ deviceId: z.string().min(1).max(128),
228
+ installId: z.string().min(1).max(128).optional(),
229
+ claims: z.array(z.unknown()).max(256).optional()
230
+ });
231
+ var OfflineHoldRecordSchema = z.object({
232
+ holdId: z.string().uuid(),
233
+ userId: z.string().uuid(),
234
+ deviceId: z.string(),
235
+ partnerId: z.string(),
236
+ adapterKind: z.string(),
237
+ externalHoldRef: z.string().nullable(),
238
+ amountKobo: z.number().int().nonnegative(),
239
+ capturedKobo: z.number().int().nonnegative(),
240
+ releasedKobo: z.number().int().nonnegative(),
241
+ remainingKobo: z.number().int().nonnegative(),
242
+ currency: z.string().length(3),
243
+ status: z.enum([
244
+ "placing",
245
+ "active",
246
+ "disabling",
247
+ "draining",
248
+ "closed",
249
+ "revoked",
250
+ "failed"
251
+ ]),
252
+ installId: z.string().nullable(),
253
+ drainDeadlineMs: z.number().int().nonnegative(),
254
+ disableRequestedAtMs: z.number().int().nonnegative().nullable(),
255
+ createdAtMs: z.number().int().nonnegative(),
256
+ closedAtMs: z.number().int().nonnegative().nullable(),
257
+ isTrusted: z.boolean().optional()
258
+ });
259
+ var EnableOfflineResultSchema = z.object({
260
+ hold: OfflineHoldRecordSchema,
261
+ oac: OACRecordSchema
262
+ });
263
+ var ProvisionOfflineAllowanceResultSchema = EnableOfflineResultSchema;
264
+ var DisableOfflineResultSchema = z.object({
265
+ hold: OfflineHoldRecordSchema,
266
+ trusted: z.boolean(),
267
+ settledClaims: z.number().int().nonnegative()
268
+ });
269
+ var OfflineStatusResultSchema = z.object({
270
+ hold: OfflineHoldRecordSchema.nullable(),
271
+ active: OACRecordSchema.nullable()
272
+ });
273
+ var OfflineStateResultSchema = z.object({
274
+ active: OACRecordSchema.nullable()
275
+ });
276
+ var ConsumerPaymentClaimSchema = z.object({
277
+ /** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
278
+ alg: z.literal("p256").default("p256"),
279
+ oacId: z.string().uuid(),
280
+ encounterId: Sha256Hex.optional(),
281
+ payerUserId: z.string().uuid(),
282
+ payeeUserId: z.string().uuid(),
283
+ payerDeviceId: z.string().min(1).max(128),
284
+ payerNonce: z.string().min(8).max(128),
285
+ payeeNonce: z.string().min(8).max(128),
286
+ amountKobo: z.number().int().positive(),
287
+ currency: z.string().length(3).default("NGN"),
288
+ occurredAtMs: z.number().int().nonnegative(),
289
+ completedAtMs: z.number().int().nonnegative().optional(),
290
+ contextId: z.string().max(128).optional(),
291
+ payerPubkeySpkiB64: Base64Std.min(64).max(4096),
292
+ payerSignatureDerB64: Base64Std.min(16).max(2048),
293
+ payeePubkeySpkiB64: Base64Std.min(64).max(4096).optional(),
294
+ payeeSignatureDerB64: Base64Std.min(16).max(2048).optional()
295
+ });
296
+ var ConsumerSettlementSchema = z.object({
297
+ settlementId: z.string().uuid(),
298
+ settlementKey: Sha256Hex,
299
+ encounterId: Sha256Hex,
300
+ oacId: z.string().uuid(),
301
+ payerUserId: z.string().uuid(),
302
+ payeeUserId: z.string().uuid(),
303
+ amountKobo: z.number().int().positive(),
304
+ currency: z.string().length(3),
305
+ status: z.enum(["SETTLED", "REVIEW"]),
306
+ reviewReason: z.string().nullable(),
307
+ ledgerRef: z.string().nullable(),
308
+ /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
309
+ issuerSig: Base64Std.min(16).max(2048),
310
+ createdAtMs: z.number().int().nonnegative()
311
+ });
312
+ var ConsumerSettleResultSchema = z.object({
313
+ settlement: ConsumerSettlementSchema,
314
+ encounterId: Sha256Hex,
315
+ replayed: z.boolean()
316
+ });
317
+ var RevokeDeviceKeyInputSchema = z.object({
318
+ deviceId: z.string().min(1).max(128),
319
+ /** Step-up token from /api/v1/auth/send/verify with purpose='offline_revoke'. */
320
+ sendAuthToken: z.string().min(16)
321
+ });
322
+ function createMeOfflineClient(opts) {
323
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
324
+ if (!fetchImpl) {
325
+ throw new Error("createMeOfflineClient: no fetch implementation available");
326
+ }
327
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
328
+ async function call(method, path, body, parser) {
329
+ const init2 = {
330
+ method,
331
+ headers: {
332
+ "content-type": "application/json",
333
+ accept: "application/json"
334
+ }
335
+ };
336
+ if (body !== void 0) init2.body = JSON.stringify(body);
337
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
338
+ const text = await resp.text();
339
+ let raw = void 0;
340
+ if (text) {
341
+ try {
342
+ raw = JSON.parse(text);
343
+ } catch {
344
+ }
345
+ }
346
+ if (!resp.ok) {
347
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
348
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
349
+ throw new FlurApiError(resp.status, code, message, raw);
350
+ }
351
+ return parser(raw);
352
+ }
353
+ const deviceKeyItems = z.object({ items: z.array(DeviceKeyRecordSchema) });
354
+ return {
355
+ registerDeviceKey: (input) => call(
356
+ "POST",
357
+ "/v1/me/offline/keys",
358
+ RegisterDeviceKeyInputSchema.parse(input),
359
+ (raw) => DeviceKeyRecordSchema.parse(raw)
360
+ ),
361
+ issueP256EnrollmentChallenge: (input) => call(
362
+ "POST",
363
+ "/v1/me/offline/keys/p256/challenge",
364
+ P256EnrollmentChallengeInputSchema.parse(input),
365
+ (raw) => P256EnrollmentChallengeResultSchema.parse(raw)
366
+ ),
367
+ registerDeviceKeyP256: (input) => call(
368
+ "POST",
369
+ "/v1/me/offline/keys/p256",
370
+ RegisterDeviceKeyP256InputSchema.parse(input),
371
+ (raw) => DeviceKeyRecordSchema.parse(raw)
372
+ ),
373
+ listDeviceKeys: () => call(
374
+ "GET",
375
+ "/v1/me/offline/keys",
376
+ void 0,
377
+ (raw) => deviceKeyItems.parse(raw)
378
+ ),
379
+ revokeDeviceKey: (input) => call(
380
+ "POST",
381
+ "/v1/me/offline/keys/revoke",
382
+ RevokeDeviceKeyInputSchema.parse(input),
383
+ () => void 0
384
+ ),
385
+ issueAccountOac: (input) => call(
386
+ "POST",
387
+ "/v1/me/offline/oac",
388
+ IssueAccountOacInputSchema.parse(input),
389
+ (raw) => OACRecordSchema.parse(raw)
390
+ ),
391
+ provisionAllowance: (input) => call(
392
+ "POST",
393
+ "/v1/me/offline/allowance",
394
+ ProvisionOfflineAllowanceInputSchema.parse(input),
395
+ (raw) => ProvisionOfflineAllowanceResultSchema.parse(raw)
396
+ ),
397
+ enable: (input) => call(
398
+ "POST",
399
+ "/v1/me/offline/enable",
400
+ EnableOfflineInputSchema.parse(input),
401
+ (raw) => EnableOfflineResultSchema.parse(raw)
402
+ ),
403
+ refresh: (input) => call(
404
+ "POST",
405
+ "/v1/me/offline/refresh",
406
+ IssueOACInputSchema.parse(input),
407
+ (raw) => OACRecordSchema.parse(raw)
408
+ ),
409
+ disable: (input) => call(
410
+ "POST",
411
+ "/v1/me/offline/disable",
412
+ DisableOfflineInputSchema.parse(input),
413
+ (raw) => DisableOfflineResultSchema.parse(raw)
414
+ ),
415
+ getStatus: (deviceId) => {
416
+ const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
417
+ return call(
418
+ "GET",
419
+ `/v1/me/offline/status${qs}`,
420
+ void 0,
421
+ (raw) => OfflineStatusResultSchema.parse(raw)
422
+ );
423
+ },
424
+ getState: (deviceId) => {
425
+ const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
426
+ return call(
427
+ "GET",
428
+ `/v1/me/offline/state${qs}`,
429
+ void 0,
430
+ (raw) => OfflineStateResultSchema.parse(raw)
431
+ );
432
+ },
433
+ submitClaim: (claim) => call(
434
+ "POST",
435
+ "/v1/me/offline/claims",
436
+ ConsumerPaymentClaimSchema.parse(claim),
437
+ (raw) => ConsumerSettleResultSchema.parse(raw)
438
+ )
439
+ };
440
+ }
441
+
442
+ // src/contracts.ts
443
+ var E164Regex = /^\+[1-9]\d{7,14}$/;
444
+ var UuidSchema = z2.string().uuid();
445
+ var IsoDateSchema = z2.string().datetime({ offset: true });
446
+ var CurrencySchema = z2.string().trim().length(3).transform((value) => value.toUpperCase());
447
+ var HealthResponseSchema = z2.object({
448
+ ok: z2.boolean()
449
+ });
450
+ var WelcomeResponseSchema = z2.object({
451
+ message: z2.string()
452
+ });
453
+ var OnboardingStartRequestSchema = z2.object({
454
+ phoneE164: z2.string().regex(E164Regex),
455
+ appInstanceId: z2.string().min(3),
456
+ platform: z2.enum(["android", "ios", "web"]),
457
+ turnstileToken: z2.string().min(20).optional(),
458
+ appAttestation: z2.object({
459
+ provider: z2.enum(["android", "ios", "web"]),
460
+ token: z2.string().min(24)
461
+ }).optional(),
462
+ firstName: z2.string().trim().min(1).max(80).optional(),
463
+ lastName: z2.string().trim().min(1).max(80).optional()
464
+ });
465
+ var OnboardingStartResponseSchema = z2.object({
466
+ requestId: z2.string().min(1),
467
+ checkUrl: z2.string().url().optional(),
468
+ expiresInSec: z2.number().int().positive(),
469
+ fallback: z2.enum(["SILENT_AUTH", "OTP"])
470
+ });
471
+ var OnboardingCompleteRequestSchema = z2.object({
472
+ requestId: z2.string().min(1),
473
+ code: z2.string().min(1).max(32),
474
+ appInstanceId: z2.string().min(3),
475
+ fingerprintHash: z2.string().min(3).optional()
476
+ });
477
+ var OnboardingCompleteResponseSchema = z2.object({
478
+ sessionToken: z2.string().min(1),
479
+ userId: UuidSchema,
480
+ restricted: z2.boolean(),
481
+ risk_reasons: z2.array(
482
+ z2.enum(["SIM_SWAP_RECENT", "ROAMING", "CARRIER_CHANGED"])
483
+ ),
484
+ stepUpRequired: z2.boolean().optional(),
485
+ riskStatus: z2.enum(["ok", "unavailable"]).optional()
486
+ });
487
+ var RegisterDeviceRequestSchema = z2.object({
488
+ userId: UuidSchema,
489
+ appInstanceId: z2.string().min(3),
490
+ platform: z2.string().min(2),
491
+ model: z2.string().optional(),
492
+ networkSignals: z2.object({
493
+ ip: z2.string().min(3),
494
+ asn: z2.number().int().optional(),
495
+ country: z2.string().min(2).optional(),
496
+ carrier: z2.string().optional()
497
+ })
498
+ });
499
+ var RegisterDeviceResponseSchema = z2.object({
500
+ deviceId: z2.string().min(1),
501
+ fingerprintHash: z2.string().min(1),
502
+ driftScore: z2.number(),
503
+ trustState: z2.enum(["TRUSTED_PRIMARY", "TRUSTED_SECONDARY", "UNVERIFIED"]),
504
+ stepUpRequired: z2.boolean()
505
+ });
506
+ var AuthRefreshRequestSchema = z2.object({
507
+ userId: UuidSchema,
508
+ refreshToken: z2.string().min(8),
509
+ appInstanceId: z2.string().min(3),
510
+ fingerprintHash: z2.string().min(3)
511
+ });
512
+ var AuthRefreshResponseSchema = z2.object({
513
+ refreshToken: z2.string().min(8),
514
+ stepUpRequired: z2.boolean()
515
+ });
516
+ var AuthLogoutRequestSchema = z2.object({
517
+ userId: UuidSchema,
518
+ refreshToken: z2.string().min(8)
519
+ });
520
+ var PinSetRequestSchema = z2.object({
521
+ userId: UuidSchema,
522
+ pin: z2.string().regex(/^\d{6}$/)
523
+ });
524
+ var PinVerifyRequestSchema = z2.object({
525
+ userId: UuidSchema,
526
+ pin: z2.string().regex(/^\d{6}$/)
527
+ });
528
+ var OkResponseSchema = z2.object({
529
+ ok: z2.boolean()
530
+ });
531
+ var RegisterSendDeviceKeyRequestSchema = z2.object({
532
+ userId: UuidSchema,
533
+ deviceId: z2.string().min(3),
534
+ publicKey: z2.string().min(32)
535
+ });
536
+ var SEND_AUTH_PURPOSES = ["send_money", "offline_revoke"];
537
+ var SendChallengeRequestSchema = z2.object({
538
+ userId: UuidSchema,
539
+ deviceId: z2.string().min(3),
540
+ purpose: z2.enum(SEND_AUTH_PURPOSES).optional()
541
+ });
542
+ var SendChallengeResponseSchema = z2.object({
543
+ challengeId: UuidSchema,
544
+ nonce: z2.string().min(1),
545
+ expiresAt: IsoDateSchema
546
+ });
547
+ var SendVerifyRequestSchema = z2.object({
548
+ userId: UuidSchema,
549
+ deviceId: z2.string().min(3),
550
+ challengeId: UuidSchema,
551
+ signature: z2.string().min(16)
552
+ });
553
+ var SendVerifyResponseSchema = z2.object({
554
+ sendAuthToken: z2.string().min(16)
555
+ });
556
+ var ResolveRecipientRequestSchema = z2.object({
557
+ identifier: z2.string().min(3)
558
+ });
559
+ var ResolveRecipientResponseSchema = z2.object({
560
+ recipientUserId: UuidSchema,
561
+ displayName: z2.string().min(1),
562
+ normalizedIdentifier: z2.string().regex(E164Regex),
563
+ isActive: z2.boolean()
564
+ });
565
+ var CreateTransferRequestSchema = z2.object({
566
+ recipientIdentifier: z2.string().min(3),
567
+ amountMinor: z2.number().int().positive(),
568
+ currency: CurrencySchema,
569
+ sendAuthToken: z2.string().min(16)
570
+ });
571
+ var TransferStatusSchema = z2.enum(["SETTLED", "PENDING_REVIEW", "DECLINED"]);
572
+ var TransferResponseSchema = z2.object({
573
+ transactionId: z2.string().min(1),
574
+ status: TransferStatusSchema,
575
+ userStatus: TransferStatusSchema,
576
+ recipientName: z2.string().min(1),
577
+ timestamp: IsoDateSchema,
578
+ /**
579
+ * Fresh issuer-signed OACs returned by the backend's post-commit
580
+ * rotation hook on `SETTLED` transfers.
581
+ *
582
+ * In the current backend implementation this hook rotates LEGACY
583
+ * hold-backed OACs only. Account-funded (no-hold) OACs introduced by
584
+ * the unified-pay-rails refactor are NOT rotated here — their
585
+ * cumulative spend counter and main-balance check are enforced at
586
+ * claim submission time via `createTransfer` under the per-sender
587
+ * advisory lock, so a stale no-hold OAC cannot authorise overspending
588
+ * (it can only resolve to REVIEW on submission).
589
+ *
590
+ * Optional: omitted when the sender has no registered hold-backed
591
+ * devices, when the refresh failed (best-effort), or on retry replays
592
+ * that pre-date the refresh. When present, the entries supersede any
593
+ * locally cached OACs for the listed devices; when absent, clients
594
+ * should fall back to `/v1/me/offline/refresh`.
595
+ *
596
+ * Each entry is validated against the canonical `OACRecordSchema`,
597
+ * so consumers can trust the runtime shape matches the static type.
598
+ */
599
+ offlineOacs: OACRecordSchema.array().optional()
600
+ });
601
+ var DirectionSchema = z2.enum(["OUTGOING", "INCOMING"]);
602
+ var AccountActivityItemSchema = z2.object({
603
+ id: z2.string().min(1),
604
+ type: z2.string().min(1),
605
+ direction: DirectionSchema,
606
+ name: z2.string().min(1),
607
+ identifier: z2.string().min(1),
608
+ amountMinor: z2.number().int(),
609
+ currency: CurrencySchema,
610
+ status: z2.string().min(1),
611
+ timestamp: IsoDateSchema
612
+ });
613
+ var AccountSummaryResponseSchema = z2.object({
614
+ /** Authenticated user's stable internal id. */
615
+ userId: UuidSchema,
616
+ /**
617
+ * 10-digit Nigeria Uniform Bank Account Number (NUBAN) allocated by the
618
+ * bank partner. `null` when the user has no partner-allocated account yet.
619
+ */
620
+ nuban: z2.string().regex(/^[0-9]{10}$/).nullable(),
621
+ balance: z2.number().int(),
622
+ currency: CurrencySchema,
623
+ dailySendLimit: z2.number().int().nonnegative(),
624
+ dailySendRemaining: z2.number().int().nonnegative(),
625
+ kycTier: z2.string().min(1),
626
+ kycStatus: z2.string().min(1),
627
+ recentActivity: z2.array(AccountActivityItemSchema)
628
+ });
629
+ var TransactionsListResponseSchema = z2.object({
630
+ items: z2.array(AccountActivityItemSchema),
631
+ nextCursor: z2.string().nullable()
632
+ });
633
+ var TransactionDetailResponseSchema = z2.object({
634
+ transactionId: z2.string().min(1),
635
+ type: z2.string().min(1),
636
+ direction: DirectionSchema,
637
+ counterpartyName: z2.string().min(1),
638
+ counterpartyIdentifier: z2.string().min(1),
639
+ amountMinor: z2.number().int(),
640
+ currency: CurrencySchema,
641
+ status: z2.string().min(1),
642
+ timestamp: IsoDateSchema
643
+ });
644
+ var PushRegisterRequestSchema = z2.object({
645
+ deviceId: z2.string().min(3),
646
+ platform: z2.enum(["ios", "android", "web"]),
647
+ token: z2.string().min(16)
648
+ });
649
+ var CreatePayLinkResponseSchema = z2.object({
650
+ token: z2.string().min(1)
651
+ });
652
+ var ResolvePayLinkResponseSchema = z2.object({
653
+ recipientUserId: UuidSchema,
654
+ displayName: z2.string().min(1),
655
+ normalizedIdentifier: z2.string().regex(E164Regex),
656
+ isActive: z2.boolean()
657
+ });
658
+
302
659
  // src/primitives.ts
303
- import { z as z2 } from "zod";
304
- var CurrencyCodeSchema = z2.string().trim().length(3).transform((value) => value.toUpperCase());
660
+ import { z as z3 } from "zod";
661
+ var CurrencyCodeSchema = z3.string().trim().length(3).transform((value) => value.toUpperCase());
305
662
  var currencyFractionDigits = {
306
663
  NGN: 2,
307
664
  USD: 2,
@@ -395,7 +752,7 @@ function moneyMinorToNumber(amountMinor) {
395
752
  }
396
753
 
397
754
  // src/collections/client.ts
398
- import { z as z3 } from "zod";
755
+ import { z as z4 } from "zod";
399
756
  var MERCHANT_PROFILE_STATUSES = [
400
757
  "pending",
401
758
  "active",
@@ -425,172 +782,172 @@ var MERCHANT_PAYOUT_STATUSES = [
425
782
  "failed",
426
783
  "cancelled"
427
784
  ];
428
- var MoneyKoboSchema = z3.number().int().positive().max(Number.MAX_SAFE_INTEGER);
429
- var MetadataSchema = z3.record(
430
- z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])
785
+ var MoneyKoboSchema = z4.number().int().positive().max(Number.MAX_SAFE_INTEGER);
786
+ var MetadataSchema = z4.record(
787
+ z4.union([z4.string(), z4.number(), z4.boolean(), z4.null()])
431
788
  );
432
- var CurrencySchema2 = z3.string().trim().length(3).transform((value) => value.toUpperCase());
433
- var ReferenceSchema = z3.string().trim().min(6).max(128).regex(/^[A-Za-z0-9._:-]+$/);
434
- var MerchantProfileSchema = z3.object({
435
- accountId: z3.string().uuid(),
436
- legalName: z3.string(),
437
- tradingName: z3.string(),
438
- merchantCategoryCode: z3.string().regex(/^\d{4}$/),
439
- nqrMerchantId: z3.string(),
440
- settlementBankCode: z3.string(),
441
- settlementAccountNumber: z3.string(),
442
- settlementAccountName: z3.string(),
443
- settlementSchedule: z3.enum(SETTLEMENT_SCHEDULES),
444
- status: z3.enum(MERCHANT_PROFILE_STATUSES),
445
- offlineEnabled: z3.boolean(),
789
+ var CurrencySchema2 = z4.string().trim().length(3).transform((value) => value.toUpperCase());
790
+ var ReferenceSchema = z4.string().trim().min(6).max(128).regex(/^[A-Za-z0-9._:-]+$/);
791
+ var MerchantProfileSchema = z4.object({
792
+ accountId: z4.string().uuid(),
793
+ legalName: z4.string(),
794
+ tradingName: z4.string(),
795
+ merchantCategoryCode: z4.string().regex(/^\d{4}$/),
796
+ nqrMerchantId: z4.string(),
797
+ settlementBankCode: z4.string(),
798
+ settlementAccountNumber: z4.string(),
799
+ settlementAccountName: z4.string(),
800
+ settlementSchedule: z4.enum(SETTLEMENT_SCHEDULES),
801
+ status: z4.enum(MERCHANT_PROFILE_STATUSES),
802
+ offlineEnabled: z4.boolean(),
446
803
  perTxLimitKobo: MoneyKoboSchema,
447
804
  dailyLimitKobo: MoneyKoboSchema,
448
805
  metadata: MetadataSchema,
449
- createdAtMs: z3.number().int().nonnegative(),
450
- updatedAtMs: z3.number().int().nonnegative()
806
+ createdAtMs: z4.number().int().nonnegative(),
807
+ updatedAtMs: z4.number().int().nonnegative()
451
808
  });
452
- var UpsertMerchantProfileInputSchema = z3.object({
453
- legalName: z3.string().trim().min(1).max(200),
454
- tradingName: z3.string().trim().min(1).max(25),
455
- merchantCategoryCode: z3.string().trim().regex(/^\d{4}$/),
456
- nqrMerchantId: z3.string().trim().min(3).max(64),
457
- settlementBankCode: z3.string().trim().min(2).max(16),
458
- settlementAccountNumber: z3.string().trim().min(5).max(32),
459
- settlementAccountName: z3.string().trim().min(1).max(200),
460
- settlementSchedule: z3.enum(SETTLEMENT_SCHEDULES).optional(),
461
- status: z3.enum(MERCHANT_PROFILE_STATUSES).optional(),
462
- offlineEnabled: z3.boolean().optional(),
809
+ var UpsertMerchantProfileInputSchema = z4.object({
810
+ legalName: z4.string().trim().min(1).max(200),
811
+ tradingName: z4.string().trim().min(1).max(25),
812
+ merchantCategoryCode: z4.string().trim().regex(/^\d{4}$/),
813
+ nqrMerchantId: z4.string().trim().min(3).max(64),
814
+ settlementBankCode: z4.string().trim().min(2).max(16),
815
+ settlementAccountNumber: z4.string().trim().min(5).max(32),
816
+ settlementAccountName: z4.string().trim().min(1).max(200),
817
+ settlementSchedule: z4.enum(SETTLEMENT_SCHEDULES).optional(),
818
+ status: z4.enum(MERCHANT_PROFILE_STATUSES).optional(),
819
+ offlineEnabled: z4.boolean().optional(),
463
820
  perTxLimitKobo: MoneyKoboSchema.optional(),
464
821
  dailyLimitKobo: MoneyKoboSchema.optional(),
465
822
  metadata: MetadataSchema.optional()
466
823
  });
467
- var CollectionIntentSchema = z3.object({
468
- intentId: z3.string().uuid(),
469
- accountId: z3.string().uuid(),
470
- terminalId: z3.string().uuid().nullable(),
471
- reference: z3.string(),
472
- amountKobo: z3.number().int().positive().nullable(),
473
- currency: z3.string().length(3),
474
- status: z3.enum(COLLECTION_INTENT_STATUSES),
475
- description: z3.string().nullable(),
476
- nqrPayload: z3.string(),
477
- provider: z3.string(),
478
- providerReference: z3.string().nullable(),
824
+ var CollectionIntentSchema = z4.object({
825
+ intentId: z4.string().uuid(),
826
+ accountId: z4.string().uuid(),
827
+ terminalId: z4.string().uuid().nullable(),
828
+ reference: z4.string(),
829
+ amountKobo: z4.number().int().positive().nullable(),
830
+ currency: z4.string().length(3),
831
+ status: z4.enum(COLLECTION_INTENT_STATUSES),
832
+ description: z4.string().nullable(),
833
+ nqrPayload: z4.string(),
834
+ provider: z4.string(),
835
+ providerReference: z4.string().nullable(),
479
836
  metadata: MetadataSchema,
480
- expiresAtMs: z3.number().int().nonnegative().nullable(),
481
- paidAtMs: z3.number().int().nonnegative().nullable(),
482
- createdAtMs: z3.number().int().nonnegative(),
483
- updatedAtMs: z3.number().int().nonnegative()
837
+ expiresAtMs: z4.number().int().nonnegative().nullable(),
838
+ paidAtMs: z4.number().int().nonnegative().nullable(),
839
+ createdAtMs: z4.number().int().nonnegative(),
840
+ updatedAtMs: z4.number().int().nonnegative()
484
841
  });
485
- var CreateCollectionIntentInputSchema = z3.object({
842
+ var CreateCollectionIntentInputSchema = z4.object({
486
843
  reference: ReferenceSchema.optional(),
487
844
  amountKobo: MoneyKoboSchema.optional(),
488
845
  currency: CurrencySchema2.optional(),
489
- terminalId: z3.string().uuid().optional(),
490
- terminalLabel: z3.string().trim().min(1).max(25).optional(),
491
- merchantCity: z3.string().trim().min(1).max(15).optional(),
492
- description: z3.string().trim().min(1).max(280).optional(),
493
- expiresAtMs: z3.number().int().positive().optional(),
494
- provider: z3.string().trim().min(1).max(40).optional(),
846
+ terminalId: z4.string().uuid().optional(),
847
+ terminalLabel: z4.string().trim().min(1).max(25).optional(),
848
+ merchantCity: z4.string().trim().min(1).max(15).optional(),
849
+ description: z4.string().trim().min(1).max(280).optional(),
850
+ expiresAtMs: z4.number().int().positive().optional(),
851
+ provider: z4.string().trim().min(1).max(40).optional(),
495
852
  metadata: MetadataSchema.optional()
496
853
  });
497
- var PublicCollectionIntentSchema = z3.object({
498
- intentId: z3.string().uuid(),
499
- reference: z3.string(),
500
- amountKobo: z3.number().int().positive().nullable(),
501
- currency: z3.string().length(3),
502
- status: z3.enum(COLLECTION_INTENT_STATUSES),
503
- merchantAccountId: z3.string().uuid(),
504
- merchantName: z3.string(),
505
- merchantCategoryCode: z3.string(),
506
- description: z3.string().nullable(),
507
- expiresAtMs: z3.number().int().nonnegative().nullable()
854
+ var PublicCollectionIntentSchema = z4.object({
855
+ intentId: z4.string().uuid(),
856
+ reference: z4.string(),
857
+ amountKobo: z4.number().int().positive().nullable(),
858
+ currency: z4.string().length(3),
859
+ status: z4.enum(COLLECTION_INTENT_STATUSES),
860
+ merchantAccountId: z4.string().uuid(),
861
+ merchantName: z4.string(),
862
+ merchantCategoryCode: z4.string(),
863
+ description: z4.string().nullable(),
864
+ expiresAtMs: z4.number().int().nonnegative().nullable()
508
865
  });
509
- var PayCollectionInputSchema = z3.object({
866
+ var PayCollectionInputSchema = z4.object({
510
867
  reference: ReferenceSchema,
511
868
  amountKobo: MoneyKoboSchema.optional(),
512
869
  currency: CurrencySchema2.optional(),
513
- idempotencyKey: z3.string().trim().min(8).max(160).optional()
870
+ idempotencyKey: z4.string().trim().min(8).max(160).optional()
514
871
  });
515
- var CollectionPaymentSchema = z3.object({
516
- paymentId: z3.string().uuid(),
517
- intentId: z3.string().uuid(),
518
- accountId: z3.string().uuid(),
519
- payerUserId: z3.string().uuid().nullable(),
520
- merchantOwnerUserId: z3.string().uuid(),
872
+ var CollectionPaymentSchema = z4.object({
873
+ paymentId: z4.string().uuid(),
874
+ intentId: z4.string().uuid(),
875
+ accountId: z4.string().uuid(),
876
+ payerUserId: z4.string().uuid().nullable(),
877
+ merchantOwnerUserId: z4.string().uuid(),
521
878
  amountKobo: MoneyKoboSchema,
522
- currency: z3.string().length(3),
523
- status: z3.enum(COLLECTION_PAYMENT_STATUSES),
524
- provider: z3.string(),
525
- providerReference: z3.string().nullable(),
526
- idempotencyKey: z3.string().nullable(),
527
- ledgerRef: z3.string(),
528
- failureCode: z3.string().nullable(),
529
- failureMessage: z3.string().nullable(),
530
- paidAtMs: z3.number().int().nonnegative().nullable(),
531
- createdAtMs: z3.number().int().nonnegative(),
532
- updatedAtMs: z3.number().int().nonnegative()
879
+ currency: z4.string().length(3),
880
+ status: z4.enum(COLLECTION_PAYMENT_STATUSES),
881
+ provider: z4.string(),
882
+ providerReference: z4.string().nullable(),
883
+ idempotencyKey: z4.string().nullable(),
884
+ ledgerRef: z4.string(),
885
+ failureCode: z4.string().nullable(),
886
+ failureMessage: z4.string().nullable(),
887
+ paidAtMs: z4.number().int().nonnegative().nullable(),
888
+ createdAtMs: z4.number().int().nonnegative(),
889
+ updatedAtMs: z4.number().int().nonnegative()
533
890
  });
534
- var CollectionPaymentResultSchema = z3.object({
891
+ var CollectionPaymentResultSchema = z4.object({
535
892
  payment: CollectionPaymentSchema,
536
893
  intent: CollectionIntentSchema,
537
- receipt: z3.unknown().optional(),
538
- replayed: z3.boolean()
894
+ receipt: z4.unknown().optional(),
895
+ replayed: z4.boolean()
539
896
  });
540
- var CollectionReportSummarySchema = z3.object({
541
- accountId: z3.string().uuid(),
542
- fromMs: z3.number().int().nonnegative(),
543
- toMs: z3.number().int().nonnegative(),
544
- currency: z3.string().length(3),
545
- paidCount: z3.number().int().nonnegative(),
546
- paidAmountKobo: z3.number().int().nonnegative(),
547
- pendingCount: z3.number().int().nonnegative(),
548
- failedCount: z3.number().int().nonnegative(),
549
- reversedCount: z3.number().int().nonnegative(),
550
- availableBalanceKobo: z3.number().int().nonnegative()
897
+ var CollectionReportSummarySchema = z4.object({
898
+ accountId: z4.string().uuid(),
899
+ fromMs: z4.number().int().nonnegative(),
900
+ toMs: z4.number().int().nonnegative(),
901
+ currency: z4.string().length(3),
902
+ paidCount: z4.number().int().nonnegative(),
903
+ paidAmountKobo: z4.number().int().nonnegative(),
904
+ pendingCount: z4.number().int().nonnegative(),
905
+ failedCount: z4.number().int().nonnegative(),
906
+ reversedCount: z4.number().int().nonnegative(),
907
+ availableBalanceKobo: z4.number().int().nonnegative()
551
908
  });
552
- var CollectionStatementSchema = z3.object({
553
- accountId: z3.string().uuid(),
554
- year: z3.number().int(),
555
- month: z3.number().int().min(1).max(12),
556
- currency: z3.string().length(3),
557
- totalPaidKobo: z3.number().int().nonnegative(),
558
- items: z3.array(CollectionPaymentSchema)
909
+ var CollectionStatementSchema = z4.object({
910
+ accountId: z4.string().uuid(),
911
+ year: z4.number().int(),
912
+ month: z4.number().int().min(1).max(12),
913
+ currency: z4.string().length(3),
914
+ totalPaidKobo: z4.number().int().nonnegative(),
915
+ items: z4.array(CollectionPaymentSchema)
559
916
  });
560
- var CreatePayoutInputSchema = z3.object({
917
+ var CreatePayoutInputSchema = z4.object({
561
918
  amountKobo: MoneyKoboSchema,
562
919
  currency: CurrencySchema2.optional(),
563
- idempotencyKey: z3.string().trim().min(8).max(160)
920
+ idempotencyKey: z4.string().trim().min(8).max(160)
564
921
  });
565
- var MerchantPayoutSchema = z3.object({
566
- payoutId: z3.string().uuid(),
567
- accountId: z3.string().uuid(),
922
+ var MerchantPayoutSchema = z4.object({
923
+ payoutId: z4.string().uuid(),
924
+ accountId: z4.string().uuid(),
568
925
  amountKobo: MoneyKoboSchema,
569
- currency: z3.string().length(3),
570
- status: z3.enum(MERCHANT_PAYOUT_STATUSES),
571
- idempotencyKey: z3.string().nullable(),
572
- ledgerRef: z3.string(),
573
- providerReference: z3.string().nullable(),
574
- requestedByUserId: z3.string().uuid().nullable(),
575
- failureCode: z3.string().nullable(),
576
- failureMessage: z3.string().nullable(),
577
- createdAtMs: z3.number().int().nonnegative(),
578
- updatedAtMs: z3.number().int().nonnegative()
926
+ currency: z4.string().length(3),
927
+ status: z4.enum(MERCHANT_PAYOUT_STATUSES),
928
+ idempotencyKey: z4.string().nullable(),
929
+ ledgerRef: z4.string(),
930
+ providerReference: z4.string().nullable(),
931
+ requestedByUserId: z4.string().uuid().nullable(),
932
+ failureCode: z4.string().nullable(),
933
+ failureMessage: z4.string().nullable(),
934
+ createdAtMs: z4.number().int().nonnegative(),
935
+ updatedAtMs: z4.number().int().nonnegative()
579
936
  });
580
- var ProviderEventInputSchema = z3.object({
581
- provider: z3.string().trim().min(1).max(80),
582
- eventId: z3.string().trim().min(1).max(160),
583
- eventType: z3.string().trim().min(1).max(120),
584
- payload: z3.record(z3.unknown()).optional()
937
+ var ProviderEventInputSchema = z4.object({
938
+ provider: z4.string().trim().min(1).max(80),
939
+ eventId: z4.string().trim().min(1).max(160),
940
+ eventType: z4.string().trim().min(1).max(120),
941
+ payload: z4.record(z4.unknown()).optional()
585
942
  });
586
- var ProviderEventRecordSchema = z3.object({
587
- id: z3.string().uuid(),
588
- provider: z3.string(),
589
- eventId: z3.string(),
590
- eventType: z3.string(),
591
- signatureVerified: z3.boolean(),
592
- receivedAtMs: z3.number().int().nonnegative(),
593
- processedAtMs: z3.number().int().nonnegative().nullable()
943
+ var ProviderEventRecordSchema = z4.object({
944
+ id: z4.string().uuid(),
945
+ provider: z4.string(),
946
+ eventId: z4.string(),
947
+ eventType: z4.string(),
948
+ signatureVerified: z4.boolean(),
949
+ receivedAtMs: z4.number().int().nonnegative(),
950
+ processedAtMs: z4.number().int().nonnegative().nullable()
594
951
  });
595
952
  function createCollectionsClient(opts) {
596
953
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
@@ -1105,7 +1462,7 @@ var FlurClient = class {
1105
1462
  try {
1106
1463
  body = requestSchema ? JSON.stringify(requestSchema.parse(input)) : init2.body;
1107
1464
  } catch (err) {
1108
- if (err instanceof z4.ZodError) {
1465
+ if (err instanceof z5.ZodError) {
1109
1466
  throw new FlurError("Invalid request payload", "INVALID_REQUEST", {
1110
1467
  details: err.flatten()
1111
1468
  });
@@ -1133,7 +1490,7 @@ var FlurClient = class {
1133
1490
  try {
1134
1491
  return responseSchema.parse(payload);
1135
1492
  } catch (err) {
1136
- if (err instanceof z4.ZodError) {
1493
+ if (err instanceof z5.ZodError) {
1137
1494
  throw new FlurError(
1138
1495
  "SDK contract validation failed",
1139
1496
  "INVALID_REQUEST",
@@ -1563,64 +1920,113 @@ function constantTimeEqual(a, b) {
1563
1920
  return diff === 0;
1564
1921
  }
1565
1922
 
1566
- // src/crypto/ed25519.ts
1567
- import { ed25519 } from "@noble/curves/ed25519";
1568
- function generateKeyPair() {
1569
- const privateKey = ed25519.utils.randomPrivateKey();
1570
- const publicKey = ed25519.getPublicKey(privateKey);
1571
- return { privateKey, publicKey };
1923
+ // src/offline/oac.ts
1924
+ import { z as z6 } from "zod";
1925
+
1926
+ // src/crypto/p256-issuer.ts
1927
+ import { p256 } from "@noble/curves/nist";
1928
+ function bytesToBase64(bytes) {
1929
+ if (typeof Buffer !== "undefined") {
1930
+ return Buffer.from(bytes).toString("base64");
1931
+ }
1932
+ let bin = "";
1933
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
1934
+ return btoa(bin);
1572
1935
  }
1573
- function publicKeyFromPrivate(privateKey) {
1574
- return ed25519.getPublicKey(privateKey);
1936
+ function base64ToBytes(b64) {
1937
+ if (typeof Buffer !== "undefined") {
1938
+ return new Uint8Array(Buffer.from(b64, "base64"));
1939
+ }
1940
+ const bin = atob(b64);
1941
+ const out = new Uint8Array(bin.length);
1942
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
1943
+ return out;
1575
1944
  }
1576
- function sign(message, privateKey) {
1577
- return ed25519.sign(message, privateKey);
1945
+ var P256_SPKI_HEADER = new Uint8Array([
1946
+ 48,
1947
+ 89,
1948
+ 48,
1949
+ 19,
1950
+ 6,
1951
+ 7,
1952
+ 42,
1953
+ 134,
1954
+ 72,
1955
+ 206,
1956
+ 61,
1957
+ 2,
1958
+ 1,
1959
+ 6,
1960
+ 8,
1961
+ 42,
1962
+ 134,
1963
+ 72,
1964
+ 206,
1965
+ 61,
1966
+ 3,
1967
+ 1,
1968
+ 7,
1969
+ 3,
1970
+ 66,
1971
+ 0
1972
+ ]);
1973
+ function p256SpkiB64ToRaw(spkiB64) {
1974
+ const spki = base64ToBytes(spkiB64);
1975
+ if (spki.length !== P256_SPKI_HEADER.length + 65) {
1976
+ throw new Error("p256: invalid SPKI length");
1977
+ }
1978
+ for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
1979
+ if (spki[i] !== P256_SPKI_HEADER[i]) {
1980
+ throw new Error("p256: invalid SPKI header");
1981
+ }
1982
+ }
1983
+ return spki.slice(P256_SPKI_HEADER.length);
1984
+ }
1985
+ function signIssuerP256(bytes, issuerPrivateKey) {
1986
+ const sig = p256.sign(bytes, issuerPrivateKey, { prehash: true });
1987
+ return bytesToBase64(sig.toBytes("der"));
1578
1988
  }
1579
- function verify(message, signature, publicKey) {
1989
+ function verifyIssuerP256(bytes, signatureB64, issuerPublicKeySpkiB64) {
1580
1990
  try {
1581
- return ed25519.verify(signature, message, publicKey);
1991
+ const pubRaw = p256SpkiB64ToRaw(issuerPublicKeySpkiB64);
1992
+ const sigBytes = base64ToBytes(signatureB64);
1993
+ return p256.verify(sigBytes, bytes, pubRaw, {
1994
+ prehash: true,
1995
+ format: "der"
1996
+ });
1582
1997
  } catch {
1583
1998
  return false;
1584
1999
  }
1585
2000
  }
1586
- function signCanonical(value, privateKey) {
1587
- return sign(canonicalJSONBytes(value), privateKey);
1588
- }
1589
- function verifyCanonical(value, signature, publicKey) {
1590
- return verify(canonicalJSONBytes(value), signature, publicKey);
1591
- }
1592
2001
 
1593
2002
  // src/offline/oac.ts
1594
- import { z as z5 } from "zod";
1595
2003
  var OAC_DEFAULT_PER_TX_KOBO = 5e5;
1596
2004
  var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
1597
2005
  var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1598
- var HexString = (length) => z5.string().regex(
1599
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1600
- `expected ${length}-byte hex string`
1601
- );
1602
- var OACSchema = z5.object({
1603
- userId: z5.string().min(1),
1604
- deviceId: z5.string().min(1),
1605
- devicePublicKey: HexString(32),
1606
- perTxCapKobo: z5.number().int().nonnegative(),
1607
- cumulativeCapKobo: z5.number().int().nonnegative(),
1608
- validFromMs: z5.number().int().nonnegative(),
1609
- validUntilMs: z5.number().int().positive(),
1610
- counterSeed: z5.number().int().nonnegative(),
1611
- nonce: z5.string().min(1),
1612
- issuerSig: HexString(64)
2006
+ var Base64Std2 = z6.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) string");
2007
+ var OACSchema = z6.object({
2008
+ userId: z6.string().min(1),
2009
+ deviceId: z6.string().min(1),
2010
+ /** SubjectPublicKeyInfo DER, base64 (P-256). */
2011
+ devicePublicKey: Base64Std2,
2012
+ perTxCapKobo: z6.number().int().nonnegative(),
2013
+ cumulativeCapKobo: z6.number().int().nonnegative(),
2014
+ validFromMs: z6.number().int().nonnegative(),
2015
+ validUntilMs: z6.number().int().positive(),
2016
+ counterSeed: z6.number().int().nonnegative(),
2017
+ nonce: z6.string().min(1),
2018
+ /** ASN.1 DER ECDSA(SHA-256) signature, base64. */
2019
+ issuerSig: Base64Std2
1613
2020
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1614
2021
  message: "validUntilMs must be greater than validFromMs"
1615
2022
  }).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
1616
2023
  message: "perTxCapKobo must not exceed cumulativeCapKobo"
1617
2024
  });
1618
2025
  function buildOAC(input) {
1619
- const devicePublicKey = typeof input.devicePublicKey === "string" ? input.devicePublicKey : bytesToHex(input.devicePublicKey);
1620
2026
  return {
1621
2027
  userId: input.userId,
1622
2028
  deviceId: input.deviceId,
1623
- devicePublicKey,
2029
+ devicePublicKey: input.devicePublicKey,
1624
2030
  perTxCapKobo: input.perTxCapKobo ?? OAC_DEFAULT_PER_TX_KOBO,
1625
2031
  cumulativeCapKobo: input.cumulativeCapKobo ?? OAC_DEFAULT_CUMULATIVE_KOBO,
1626
2032
  validFromMs: input.validFromMs,
@@ -1630,36 +2036,25 @@ function buildOAC(input) {
1630
2036
  };
1631
2037
  }
1632
2038
  function signOAC(unsigned, issuerPrivateKey) {
1633
- const issuerSig = bytesToHex(
1634
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2039
+ const issuerSig = signIssuerP256(
2040
+ canonicalJSONBytes(unsigned),
2041
+ issuerPrivateKey
1635
2042
  );
1636
2043
  return { ...unsigned, issuerSig };
1637
2044
  }
1638
- function verifyOAC(oac, issuerPublicKey) {
2045
+ function verifyOAC(oac, issuerPublicKeySpkiB64) {
1639
2046
  try {
1640
2047
  const parsed = OACSchema.parse(oac);
1641
2048
  const { issuerSig, ...unsigned } = parsed;
1642
- return verify(
2049
+ return verifyIssuerP256(
1643
2050
  canonicalJSONBytes(unsigned),
1644
- hexToBytes(issuerSig),
1645
- issuerPublicKey
2051
+ issuerSig,
2052
+ issuerPublicKeySpkiB64
1646
2053
  );
1647
2054
  } catch {
1648
2055
  return false;
1649
2056
  }
1650
2057
  }
1651
- function bytesToHex(b) {
1652
- let s = "";
1653
- for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1654
- return s;
1655
- }
1656
- function hexToBytes(s) {
1657
- if (s.length % 2 !== 0) throw new Error("hex: odd length");
1658
- const out = new Uint8Array(s.length / 2);
1659
- for (let i = 0; i < out.length; i++)
1660
- out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
1661
- return out;
1662
- }
1663
2058
 
1664
2059
  // src/offline/codec.ts
1665
2060
  var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
@@ -1715,20 +2110,20 @@ function decodeBase45(s) {
1715
2110
  }
1716
2111
 
1717
2112
  // src/offline/messages.ts
1718
- import { z as z6 } from "zod";
1719
- var HexSig = z6.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1720
- var OfflinePaymentRequestSchema = z6.object({
1721
- reference: z6.string().min(1),
1722
- amountKobo: z6.number().int().positive(),
2113
+ import { z as z7 } from "zod";
2114
+ var Base64Sig = z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) signature");
2115
+ var OfflinePaymentRequestSchema = z7.object({
2116
+ reference: z7.string().min(1),
2117
+ amountKobo: z7.number().int().positive(),
1723
2118
  merchantOAC: OACSchema,
1724
- expiresAtMs: z6.number().int().positive(),
1725
- merchantSig: HexSig
2119
+ expiresAtMs: z7.number().int().positive(),
2120
+ merchantSig: Base64Sig
1726
2121
  });
1727
- var OfflinePaymentAuthorizationSchema = z6.object({
2122
+ var OfflinePaymentAuthorizationSchema = z7.object({
1728
2123
  request: OfflinePaymentRequestSchema,
1729
2124
  payerOAC: OACSchema,
1730
- payerCounter: z6.number().int().positive(),
1731
- payerSig: HexSig
2125
+ payerCounter: z7.number().int().positive(),
2126
+ payerSig: Base64Sig
1732
2127
  });
1733
2128
  function buildPaymentRequest(input) {
1734
2129
  if (!Number.isInteger(input.amountKobo) || input.amountKobo <= 0) {
@@ -1745,27 +2140,28 @@ function buildPaymentRequest(input) {
1745
2140
  };
1746
2141
  }
1747
2142
  function signPaymentRequest(unsigned, merchantDevicePrivateKey) {
1748
- const merchantSig = bytesToHex(
1749
- sign(canonicalJSONBytes(unsigned), merchantDevicePrivateKey)
2143
+ const merchantSig = signIssuerP256(
2144
+ canonicalJSONBytes(unsigned),
2145
+ merchantDevicePrivateKey
1750
2146
  );
1751
2147
  return { ...unsigned, merchantSig };
1752
2148
  }
1753
- function verifyPaymentRequest(req, issuerPublicKey) {
2149
+ function verifyPaymentRequest(req, issuerPublicKeySpkiB64) {
1754
2150
  try {
1755
2151
  const parsed = OfflinePaymentRequestSchema.parse(req);
1756
2152
  const { issuerSig: merchantOacSig, ...merchantOacUnsigned } = parsed.merchantOAC;
1757
- if (!verify(
2153
+ if (!verifyIssuerP256(
1758
2154
  canonicalJSONBytes(merchantOacUnsigned),
1759
- hexToBytes(merchantOacSig),
1760
- issuerPublicKey
2155
+ merchantOacSig,
2156
+ issuerPublicKeySpkiB64
1761
2157
  )) {
1762
2158
  return false;
1763
2159
  }
1764
2160
  const { merchantSig, ...unsigned } = parsed;
1765
- return verify(
2161
+ return verifyIssuerP256(
1766
2162
  canonicalJSONBytes(unsigned),
1767
- hexToBytes(merchantSig),
1768
- hexToBytes(parsed.merchantOAC.devicePublicKey)
2163
+ merchantSig,
2164
+ parsed.merchantOAC.devicePublicKey
1769
2165
  );
1770
2166
  } catch {
1771
2167
  return false;
@@ -1785,28 +2181,30 @@ function buildAuthorization(input) {
1785
2181
  };
1786
2182
  }
1787
2183
  function signAuthorization(unsigned, payerDevicePrivateKey) {
1788
- const payerSig = bytesToHex(
1789
- sign(canonicalJSONBytes(unsigned), payerDevicePrivateKey)
2184
+ const payerSig = signIssuerP256(
2185
+ canonicalJSONBytes(unsigned),
2186
+ payerDevicePrivateKey
1790
2187
  );
1791
2188
  return { ...unsigned, payerSig };
1792
2189
  }
1793
- function verifyAuthorization(auth, issuerPublicKey) {
2190
+ function verifyAuthorization(auth, issuerPublicKeySpkiB64) {
1794
2191
  try {
1795
2192
  const parsed = OfflinePaymentAuthorizationSchema.parse(auth);
1796
- if (!verifyPaymentRequest(parsed.request, issuerPublicKey)) return false;
2193
+ if (!verifyPaymentRequest(parsed.request, issuerPublicKeySpkiB64))
2194
+ return false;
1797
2195
  const { issuerSig: payerOacSig, ...payerOacUnsigned } = parsed.payerOAC;
1798
- if (!verify(
2196
+ if (!verifyIssuerP256(
1799
2197
  canonicalJSONBytes(payerOacUnsigned),
1800
- hexToBytes(payerOacSig),
1801
- issuerPublicKey
2198
+ payerOacSig,
2199
+ issuerPublicKeySpkiB64
1802
2200
  )) {
1803
2201
  return false;
1804
2202
  }
1805
2203
  const { payerSig, ...unsigned } = parsed;
1806
- return verify(
2204
+ return verifyIssuerP256(
1807
2205
  canonicalJSONBytes(unsigned),
1808
- hexToBytes(payerSig),
1809
- hexToBytes(parsed.payerOAC.devicePublicKey)
2206
+ payerSig,
2207
+ parsed.payerOAC.devicePublicKey
1810
2208
  );
1811
2209
  } catch {
1812
2210
  return false;
@@ -1836,60 +2234,64 @@ function decodeAuthorizationQR(s) {
1836
2234
  }
1837
2235
 
1838
2236
  // src/offline/settlements.ts
1839
- import { z as z7 } from "zod";
2237
+ import { z as z8 } from "zod";
1840
2238
  import { sha256 } from "@noble/hashes/sha256";
1841
- import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
1842
- var OfflineTokenSchema = z7.object({
1843
- tokenId: z7.string().uuid(),
1844
- tokenSerial: z7.string(),
1845
- issuerAccountId: z7.string().uuid(),
1846
- payerUserId: z7.string().uuid(),
1847
- maxAmountKobo: z7.number().int().positive(),
1848
- currency: z7.string().length(3),
1849
- issuedAtMs: z7.number().int().nonnegative(),
1850
- expiresAtMs: z7.number().int().nonnegative(),
1851
- issuerSig: z7.string()
2239
+ import { bytesToHex } from "@noble/hashes/utils";
2240
+ var OfflineTokenSchema = z8.object({
2241
+ tokenId: z8.string().uuid(),
2242
+ tokenSerial: z8.string(),
2243
+ issuerAccountId: z8.string().uuid(),
2244
+ payerUserId: z8.string().uuid(),
2245
+ maxAmountKobo: z8.number().int().positive(),
2246
+ currency: z8.string().length(3),
2247
+ issuedAtMs: z8.number().int().nonnegative(),
2248
+ expiresAtMs: z8.number().int().nonnegative(),
2249
+ issuerSig: z8.string()
1852
2250
  });
1853
- var PaymentClaimSchema = z7.object({
1854
- encounterId: z7.string().regex(/^[0-9a-f]{64}$/i).optional(),
1855
- tokenSerial: z7.string(),
1856
- payerUserId: z7.string().uuid(),
1857
- payeeUserId: z7.string().uuid(),
1858
- payerNonce: z7.string(),
1859
- payeeNonce: z7.string(),
1860
- amountKobo: z7.number().int().positive(),
1861
- currency: z7.string().length(3).default("NGN"),
1862
- occurredAtMs: z7.number().int().nonnegative(),
1863
- completedAtMs: z7.number().int().nonnegative().optional(),
1864
- contextId: z7.string().optional(),
1865
- payerPubkey: z7.string().regex(/^[0-9a-f]{64}$/i),
1866
- payerSignature: z7.string().regex(/^[0-9a-f]+$/i),
1867
- payeePubkey: z7.string().regex(/^[0-9a-f]{64}$/i).optional(),
1868
- payeeSignature: z7.string().regex(/^[0-9a-f]+$/i).optional()
2251
+ var PaymentClaimSchema = z8.object({
2252
+ encounterId: z8.string().regex(/^[0-9a-f]{64}$/i).optional(),
2253
+ tokenSerial: z8.string(),
2254
+ payerUserId: z8.string().uuid(),
2255
+ payeeUserId: z8.string().uuid(),
2256
+ payerNonce: z8.string(),
2257
+ payeeNonce: z8.string(),
2258
+ amountKobo: z8.number().int().positive(),
2259
+ currency: z8.string().length(3).default("NGN"),
2260
+ occurredAtMs: z8.number().int().nonnegative(),
2261
+ completedAtMs: z8.number().int().nonnegative().optional(),
2262
+ contextId: z8.string().optional(),
2263
+ // Stage 2c: P-256 device keys are now SubjectPublicKeyInfo DER, base64.
2264
+ // Signatures are ASN.1 DER ECDSA(SHA-256), base64. Backwards-incompatible
2265
+ // wire change; the backend has the matching widening in offline-settlements
2266
+ // service + zod schema.
2267
+ payerPubkey: z8.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2268
+ payerSignature: z8.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2269
+ payeePubkey: z8.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional(),
2270
+ payeeSignature: z8.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional()
1869
2271
  });
1870
- var SettlementSchema = z7.object({
1871
- settlementId: z7.string().uuid(),
1872
- settlementKey: z7.string().regex(/^[0-9a-f]{64}$/i),
1873
- encounterId: z7.string().regex(/^[0-9a-f]{64}$/i),
1874
- issuerAccountId: z7.string().uuid(),
1875
- tokenSerial: z7.string(),
1876
- payerUserId: z7.string().uuid(),
1877
- payeeUserId: z7.string().uuid(),
1878
- amountKobo: z7.number().int().nonnegative(),
1879
- currency: z7.string().length(3),
1880
- receiptId: z7.string().nullable(),
1881
- status: z7.enum(["SETTLED", "REVIEW", "REJECTED"]),
1882
- issuerSig: z7.string(),
1883
- createdAtMs: z7.number().int().nonnegative()
2272
+ var SettlementSchema = z8.object({
2273
+ settlementId: z8.string().uuid(),
2274
+ settlementKey: z8.string().regex(/^[0-9a-f]{64}$/i),
2275
+ encounterId: z8.string().regex(/^[0-9a-f]{64}$/i),
2276
+ issuerAccountId: z8.string().uuid(),
2277
+ tokenSerial: z8.string(),
2278
+ payerUserId: z8.string().uuid(),
2279
+ payeeUserId: z8.string().uuid(),
2280
+ amountKobo: z8.number().int().nonnegative(),
2281
+ currency: z8.string().length(3),
2282
+ receiptId: z8.string().nullable(),
2283
+ status: z8.enum(["SETTLED", "REVIEW", "REJECTED"]),
2284
+ issuerSig: z8.string(),
2285
+ createdAtMs: z8.number().int().nonnegative()
1884
2286
  });
1885
- var SettleResponseSchema = z7.object({
2287
+ var SettleResponseSchema = z8.object({
1886
2288
  settlement: SettlementSchema,
1887
- encounterId: z7.string().regex(/^[0-9a-f]{64}$/i),
1888
- replayed: z7.boolean()
2289
+ encounterId: z8.string().regex(/^[0-9a-f]{64}$/i),
2290
+ replayed: z8.boolean()
1889
2291
  });
1890
2292
  var ENCOUNTER_DOMAIN = "offline:v1:encounter";
1891
2293
  async function sha256Hex(input) {
1892
- return bytesToHex2(sha256(new TextEncoder().encode(input)));
2294
+ return bytesToHex(sha256(new TextEncoder().encode(input)));
1893
2295
  }
1894
2296
  async function computeEncounterId(input) {
1895
2297
  return sha256Hex(
@@ -2059,7 +2461,7 @@ function createHmacFetch(opts) {
2059
2461
  }
2060
2462
 
2061
2463
  // src/partner/client.ts
2062
- import { z as z8 } from "zod";
2464
+ import { z as z9 } from "zod";
2063
2465
  import { sha256 as sha2563 } from "@noble/hashes/sha256";
2064
2466
  import { hmac as hmac2 } from "@noble/hashes/hmac";
2065
2467
  import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils";
@@ -2083,18 +2485,18 @@ var PARTNER_SCOPES = [
2083
2485
  "partner:payout:write",
2084
2486
  "partner:reconciliation:read"
2085
2487
  ];
2086
- var ApiCredentialPublicSchema = z8.object({
2087
- id: z8.string().uuid(),
2088
- accountId: z8.string().uuid(),
2089
- keyId: z8.string(),
2090
- scopes: z8.array(z8.enum(PARTNER_SCOPES)),
2091
- label: z8.string().nullable(),
2092
- createdAtMs: z8.number().int().nonnegative(),
2093
- lastUsedAtMs: z8.number().int().nonnegative().nullable(),
2094
- revokedAtMs: z8.number().int().nonnegative().nullable()
2488
+ var ApiCredentialPublicSchema = z9.object({
2489
+ id: z9.string().uuid(),
2490
+ accountId: z9.string().uuid(),
2491
+ keyId: z9.string(),
2492
+ scopes: z9.array(z9.enum(PARTNER_SCOPES)),
2493
+ label: z9.string().nullable(),
2494
+ createdAtMs: z9.number().int().nonnegative(),
2495
+ lastUsedAtMs: z9.number().int().nonnegative().nullable(),
2496
+ revokedAtMs: z9.number().int().nonnegative().nullable()
2095
2497
  });
2096
2498
  var MintedApiCredentialSchema = ApiCredentialPublicSchema.extend({
2097
- secret: z8.string().min(1)
2499
+ secret: z9.string().min(1)
2098
2500
  });
2099
2501
  var enc = new TextEncoder();
2100
2502
  async function sha256Hex2(input) {
@@ -2251,8 +2653,8 @@ function createApiCredentialsAdminClient(opts) {
2251
2653
  }
2252
2654
  return parser(raw);
2253
2655
  }
2254
- const listSchema = z8.object({
2255
- items: z8.array(ApiCredentialPublicSchema)
2656
+ const listSchema = z9.object({
2657
+ items: z9.array(ApiCredentialPublicSchema)
2256
2658
  });
2257
2659
  return {
2258
2660
  list: (accountId) => call(
@@ -2277,7 +2679,7 @@ function createApiCredentialsAdminClient(opts) {
2277
2679
  }
2278
2680
 
2279
2681
  // src/passes/pass.ts
2280
- import { z as z9 } from "zod";
2682
+ import { z as z10 } from "zod";
2281
2683
  var PASS_KINDS = [
2282
2684
  "ride-ticket",
2283
2685
  "transit-pass",
@@ -2293,42 +2695,39 @@ var PASS_STATES = [
2293
2695
  "expired",
2294
2696
  "revoked"
2295
2697
  ];
2296
- var HexString2 = (length) => z9.string().regex(
2297
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2298
- `expected ${length}-byte hex string`
2299
- );
2300
- var PassMetadataSchema = z9.record(
2301
- z9.union([z9.string(), z9.number(), z9.boolean(), z9.null()])
2698
+ var PassMetadataSchema = z10.record(
2699
+ z10.union([z10.string(), z10.number(), z10.boolean(), z10.null()])
2302
2700
  );
2303
- var PassSchema = z9.object({
2304
- passId: z9.string().min(1),
2701
+ var PassSchema = z10.object({
2702
+ passId: z10.string().min(1),
2305
2703
  /** Optional client/template grouping id (server may omit). */
2306
- templateId: z9.string().min(1).optional(),
2704
+ templateId: z10.string().min(1).optional(),
2307
2705
  /** Optional human-facing holder identity (server may omit). The cryptographic binding
2308
2706
  * is `holderDevicePubkey` below. */
2309
- holderUserId: z9.string().min(1).optional(),
2310
- kind: z9.enum(PASS_KINDS),
2311
- issuerId: z9.string().min(1),
2312
- issuedAtMs: z9.number().int().nonnegative(),
2313
- validFromMs: z9.number().int().nonnegative(),
2314
- validUntilMs: z9.number().int().positive(),
2315
- state: z9.enum(PASS_STATES),
2707
+ holderUserId: z10.string().min(1).optional(),
2708
+ kind: z10.enum(PASS_KINDS),
2709
+ issuerId: z10.string().min(1),
2710
+ issuedAtMs: z10.number().int().nonnegative(),
2711
+ validFromMs: z10.number().int().nonnegative(),
2712
+ validUntilMs: z10.number().int().positive(),
2713
+ state: z10.enum(PASS_STATES),
2316
2714
  metadata: PassMetadataSchema,
2317
- nonce: z9.string().min(1),
2715
+ nonce: z10.string().min(1),
2318
2716
  /** Device id this pass is bound to (FK to backend `device_keys`). */
2319
- holderDeviceId: z9.string().min(1),
2320
- /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
2321
- * is verified against this key — it is the security-critical binding. */
2322
- holderDevicePubkey: HexString2(32),
2717
+ holderDeviceId: z10.string().min(1),
2718
+ /** SubjectPublicKeyInfo DER (P-256) of the bound device, base64. The redemption
2719
+ * signature is verified against this key — it is the security-critical binding. */
2720
+ holderDevicePubkey: z10.string().min(64).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
2323
2721
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
2324
- amountKobo: z9.number().int().nonnegative().optional(),
2722
+ amountKobo: z10.number().int().nonnegative().optional(),
2325
2723
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
2326
- currency: z9.string().min(3).max(8),
2724
+ currency: z10.string().min(3).max(8),
2327
2725
  /** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
2328
- counterSeed: z9.number().int().nonnegative(),
2726
+ counterSeed: z10.number().int().nonnegative(),
2329
2727
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
2330
- cumulativeCapKobo: z9.number().int().nonnegative().optional(),
2331
- issuerSig: HexString2(64)
2728
+ cumulativeCapKobo: z10.number().int().nonnegative().optional(),
2729
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2730
+ issuerSig: z10.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2332
2731
  }).refine((v) => v.validUntilMs > v.validFromMs, {
2333
2732
  message: "validUntilMs must be greater than validFromMs"
2334
2733
  });
@@ -2361,19 +2760,20 @@ function buildPass(input) {
2361
2760
  return out;
2362
2761
  }
2363
2762
  function signPass(unsigned, issuerPrivateKey) {
2364
- const issuerSig = bytesToHex(
2365
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2763
+ const issuerSig = signIssuerP256(
2764
+ canonicalJSONBytes(unsigned),
2765
+ issuerPrivateKey
2366
2766
  );
2367
2767
  return { ...unsigned, issuerSig };
2368
2768
  }
2369
- function verifyPass(pass, issuerPublicKey) {
2769
+ function verifyPass(pass, issuerPublicKeySpkiB64) {
2370
2770
  try {
2371
2771
  const parsed = PassSchema.parse(pass);
2372
2772
  const { issuerSig, ...unsigned } = parsed;
2373
- return verify(
2773
+ return verifyIssuerP256(
2374
2774
  canonicalJSONBytes(unsigned),
2375
- hexToBytes(issuerSig),
2376
- issuerPublicKey
2775
+ issuerSig,
2776
+ issuerPublicKeySpkiB64
2377
2777
  );
2378
2778
  } catch {
2379
2779
  return false;
@@ -2384,19 +2784,20 @@ function isPassWithinValidity(pass, nowMs) {
2384
2784
  }
2385
2785
 
2386
2786
  // src/passes/redemption.ts
2387
- import { z as z10 } from "zod";
2388
- var HexSig2 = z10.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
2389
- var RedemptionSchema = z10.object({
2787
+ import { z as z11 } from "zod";
2788
+ var Base64Std3 = z11.string().min(16).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (std)");
2789
+ var RedemptionSchema = z11.object({
2390
2790
  pass: PassSchema,
2391
- redeemerId: z10.string().min(1),
2392
- redeemedAtMs: z10.number().int().nonnegative(),
2791
+ redeemerId: z11.string().min(1),
2792
+ redeemedAtMs: z11.number().int().nonnegative(),
2393
2793
  /** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
2394
2794
  * and > the redeemer's lastSeenCounter for this pass. */
2395
- counter: z10.number().int().positive(),
2795
+ counter: z11.number().int().positive(),
2396
2796
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
2397
- amountKobo: z10.number().int().nonnegative(),
2398
- nonce: z10.string().min(1),
2399
- holderSig: HexSig2
2797
+ amountKobo: z11.number().int().nonnegative(),
2798
+ nonce: z11.string().min(1),
2799
+ /** ASN.1 DER ECDSA P-256 signature over canonicalJSONBytes(unsigned), base64. */
2800
+ holderSig: Base64Std3
2400
2801
  });
2401
2802
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
2402
2803
  function buildRedemption(input) {
@@ -2450,74 +2851,68 @@ function buildRedemption(input) {
2450
2851
  };
2451
2852
  }
2452
2853
  function signRedemption(unsigned, holderDevicePrivateKey) {
2453
- const holderSig = bytesToHex(
2454
- sign(canonicalJSONBytes(unsigned), holderDevicePrivateKey)
2854
+ const holderSig = signIssuerP256(
2855
+ canonicalJSONBytes(unsigned),
2856
+ holderDevicePrivateKey
2455
2857
  );
2456
2858
  return { ...unsigned, holderSig };
2457
2859
  }
2458
- function verifyRedemption(r, issuerPublicKey) {
2860
+ function verifyRedemption(r, issuerPublicKeySpkiB64) {
2459
2861
  try {
2460
2862
  const parsed = RedemptionSchema.parse(r);
2461
2863
  if (parsed.counter <= parsed.pass.counterSeed) return false;
2462
2864
  const { issuerSig, ...passUnsigned } = parsed.pass;
2463
- if (!verify(
2865
+ if (!verifyIssuerP256(
2464
2866
  canonicalJSONBytes(passUnsigned),
2465
- hexToBytes(issuerSig),
2466
- issuerPublicKey
2867
+ issuerSig,
2868
+ issuerPublicKeySpkiB64
2467
2869
  )) {
2468
2870
  return false;
2469
2871
  }
2470
- const holderHex = parsed.pass.holderDevicePubkey;
2471
- if (typeof holderHex !== "string") return false;
2872
+ const holderPub = parsed.pass.holderDevicePubkey;
2873
+ if (typeof holderPub !== "string") return false;
2472
2874
  const { holderSig, ...unsigned } = parsed;
2473
- return verify(
2474
- canonicalJSONBytes(unsigned),
2475
- hexToBytes(holderSig),
2476
- hexToBytes(holderHex)
2477
- );
2875
+ return verifyIssuerP256(canonicalJSONBytes(unsigned), holderSig, holderPub);
2478
2876
  } catch {
2479
2877
  return false;
2480
2878
  }
2481
2879
  }
2482
2880
 
2483
2881
  // src/receipts/receipt.ts
2484
- import { z as z11 } from "zod";
2882
+ import { z as z12 } from "zod";
2485
2883
  var RECEIPT_CHANNELS = ["cash", "pass"];
2486
2884
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
2487
- var HexString3 = (length) => z11.string().regex(
2488
- new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
2489
- `expected ${length}-byte hex string`
2490
- );
2491
- var ReceiptPayloadSchema = z11.record(
2492
- z11.union([z11.string(), z11.number(), z11.boolean(), z11.null()])
2885
+ var ReceiptPayloadSchema = z12.record(
2886
+ z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()])
2493
2887
  );
2494
- var ReceiptSchema = z11.object({
2495
- receiptId: z11.string().min(1),
2496
- channel: z11.enum(RECEIPT_CHANNELS),
2888
+ var ReceiptSchema = z12.object({
2889
+ receiptId: z12.string().min(1),
2890
+ channel: z12.enum(RECEIPT_CHANNELS),
2497
2891
  /** Cash-channel: send_intents.id. Required when channel === 'cash'. */
2498
- intentId: z11.string().min(1).optional(),
2892
+ intentId: z12.string().min(1).optional(),
2499
2893
  /** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
2500
- passRedemptionId: z11.string().min(1).optional(),
2501
- payerUserId: z11.string().min(1),
2502
- payeeUserId: z11.string().min(1),
2503
- amountKobo: z11.number().int().nonnegative(),
2504
- currency: z11.string().min(3).max(8),
2505
- issuedAtMs: z11.number().int().nonnegative(),
2506
- issuerId: z11.string().min(1),
2894
+ passRedemptionId: z12.string().min(1).optional(),
2895
+ payerUserId: z12.string().min(1),
2896
+ payeeUserId: z12.string().min(1),
2897
+ amountKobo: z12.number().int().nonnegative(),
2898
+ currency: z12.string().min(3).max(8),
2899
+ issuedAtMs: z12.number().int().nonnegative(),
2900
+ issuerId: z12.string().min(1),
2507
2901
  payload: ReceiptPayloadSchema,
2508
- issuerSig: HexString3(64)
2902
+ /** ASN.1 DER ECDSA P-256 signature, base64. */
2903
+ issuerSig: z12.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
2509
2904
  }).superRefine((v, ctx) => {
2510
2905
  if (v.channel === "cash") {
2511
2906
  if (!v.intentId) {
2512
2907
  ctx.addIssue({
2513
- code: z11.ZodIssueCode.custom,
2908
+ code: z12.ZodIssueCode.custom,
2514
2909
  message: "cash receipts require intentId",
2515
2910
  path: ["intentId"]
2516
2911
  });
2517
2912
  }
2518
2913
  if (v.passRedemptionId) {
2519
2914
  ctx.addIssue({
2520
- code: z11.ZodIssueCode.custom,
2915
+ code: z12.ZodIssueCode.custom,
2521
2916
  message: "cash receipts must not carry passRedemptionId",
2522
2917
  path: ["passRedemptionId"]
2523
2918
  });
@@ -2525,14 +2920,14 @@ var ReceiptSchema = z11.object({
2525
2920
  } else if (v.channel === "pass") {
2526
2921
  if (!v.passRedemptionId) {
2527
2922
  ctx.addIssue({
2528
- code: z11.ZodIssueCode.custom,
2923
+ code: z12.ZodIssueCode.custom,
2529
2924
  message: "pass receipts require passRedemptionId",
2530
2925
  path: ["passRedemptionId"]
2531
2926
  });
2532
2927
  }
2533
2928
  if (v.intentId) {
2534
2929
  ctx.addIssue({
2535
- code: z11.ZodIssueCode.custom,
2930
+ code: z12.ZodIssueCode.custom,
2536
2931
  message: "pass receipts must not carry intentId",
2537
2932
  path: ["intentId"]
2538
2933
  });
@@ -2571,19 +2966,20 @@ function buildReceipt(input) {
2571
2966
  return out;
2572
2967
  }
2573
2968
  function signReceipt(unsigned, issuerPrivateKey) {
2574
- const issuerSig = bytesToHex(
2575
- sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2969
+ const issuerSig = signIssuerP256(
2970
+ canonicalJSONBytes(unsigned),
2971
+ issuerPrivateKey
2576
2972
  );
2577
2973
  return { ...unsigned, issuerSig };
2578
2974
  }
2579
- function verifyReceipt(r, issuerPublicKey) {
2975
+ function verifyReceipt(r, issuerPublicKeySpkiB64) {
2580
2976
  try {
2581
2977
  const parsed = ReceiptSchema.parse(r);
2582
2978
  const { issuerSig, ...unsigned } = parsed;
2583
- return verify(
2979
+ return verifyIssuerP256(
2584
2980
  canonicalJSONBytes(unsigned),
2585
- hexToBytes(issuerSig),
2586
- issuerPublicKey
2981
+ issuerSig,
2982
+ issuerPublicKeySpkiB64
2587
2983
  );
2588
2984
  } catch {
2589
2985
  return false;
@@ -2833,23 +3229,23 @@ function init(opts) {
2833
3229
  }
2834
3230
 
2835
3231
  // src/accounts/client.ts
2836
- import { z as z12 } from "zod";
3232
+ import { z as z13 } from "zod";
2837
3233
  var ACCOUNT_TYPES = ["personal", "business", "partner"];
2838
3234
  var ACCOUNT_STATUSES = ["active", "suspended", "closed"];
2839
3235
  var MEMBERSHIP_ROLES = ["owner", "admin", "driver", "staff"];
2840
- var AccountSchema = z12.object({
2841
- accountId: z12.string().uuid(),
2842
- type: z12.enum(ACCOUNT_TYPES),
2843
- displayName: z12.string().min(1),
2844
- status: z12.enum(ACCOUNT_STATUSES),
2845
- ownerUserId: z12.string().uuid().nullable(),
2846
- createdAtMs: z12.number().int().nonnegative()
3236
+ var AccountSchema = z13.object({
3237
+ accountId: z13.string().uuid(),
3238
+ type: z13.enum(ACCOUNT_TYPES),
3239
+ displayName: z13.string().min(1),
3240
+ status: z13.enum(ACCOUNT_STATUSES),
3241
+ ownerUserId: z13.string().uuid().nullable(),
3242
+ createdAtMs: z13.number().int().nonnegative()
2847
3243
  });
2848
- var AccountMembershipSchema = z12.object({
2849
- accountId: z12.string().uuid(),
2850
- userId: z12.string().uuid(),
2851
- role: z12.enum(MEMBERSHIP_ROLES),
2852
- createdAtMs: z12.number().int().nonnegative()
3244
+ var AccountMembershipSchema = z13.object({
3245
+ accountId: z13.string().uuid(),
3246
+ userId: z13.string().uuid(),
3247
+ role: z13.enum(MEMBERSHIP_ROLES),
3248
+ createdAtMs: z13.number().int().nonnegative()
2853
3249
  });
2854
3250
  function createAccountsClient(opts) {
2855
3251
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
@@ -2882,9 +3278,9 @@ function createAccountsClient(opts) {
2882
3278
  }
2883
3279
  return parser(raw);
2884
3280
  }
2885
- const itemsSchema = z12.object({ items: z12.array(AccountSchema) });
2886
- const memberItemsSchema = z12.object({
2887
- items: z12.array(AccountMembershipSchema)
3281
+ const itemsSchema = z13.object({ items: z13.array(AccountSchema) });
3282
+ const memberItemsSchema = z13.object({
3283
+ items: z13.array(AccountMembershipSchema)
2888
3284
  });
2889
3285
  return {
2890
3286
  listMyAccounts: () => call(
@@ -2915,345 +3311,8 @@ function createAccountsClient(opts) {
2915
3311
  };
2916
3312
  }
2917
3313
 
2918
- // src/me-offline/client.ts
2919
- import { z as z13 } from "zod";
2920
- var Hex64 = z13.string().regex(/^[0-9a-f]{64}$/i);
2921
- var HexAny = z13.string().regex(/^[0-9a-f]+$/i);
2922
- var Sha256Hex = z13.string().regex(/^[0-9a-f]{64}$/i);
2923
- var Base64Std = z13.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
2924
- var RegisterDeviceKeyInputSchema = z13.object({
2925
- deviceId: z13.string().min(1).max(128),
2926
- publicKeyHex: Hex64
2927
- });
2928
- var AttestationSecurityLevelSchema = z13.enum([
2929
- "STRONGBOX",
2930
- "TEE",
2931
- "SECURE_ENCLAVE",
2932
- "SOFTWARE"
2933
- ]);
2934
- var DeviceKeyAlgSchema = z13.enum(["ed25519", "p256"]);
2935
- var RegisterDeviceKeyP256InputSchema = z13.object({
2936
- deviceId: z13.string().min(1).max(128),
2937
- /** P-256 SubjectPublicKeyInfo DER, base64. */
2938
- publicKeySpkiB64: Base64Std.min(64).max(4096),
2939
- /** Base64 of the server-issued enrollment challenge string. */
2940
- challengeB64: Base64Std.min(8).max(1024),
2941
- /** iOS App Attest payload or Android X.509 Key Attestation chain. */
2942
- attestationChainB64: z13.array(Base64Std.min(16).max(16384)).min(1).max(16),
2943
- securityLevel: AttestationSecurityLevelSchema
2944
- });
2945
- var P256EnrollmentChallengeInputSchema = z13.object({
2946
- deviceId: z13.string().min(1).max(128)
2947
- });
2948
- var P256EnrollmentChallengeResultSchema = z13.object({
2949
- challenge: z13.string().min(16),
2950
- expiresAtMs: z13.number().int().positive()
2951
- });
2952
- var DeviceKeyRecordSchema = z13.object({
2953
- id: z13.string().uuid(),
2954
- userId: z13.string().uuid(),
2955
- deviceId: z13.string(),
2956
- alg: DeviceKeyAlgSchema.default("ed25519"),
2957
- publicKeyHex: Hex64.nullable().default(null),
2958
- publicKeySpkiB64: Base64Std.nullable().default(null),
2959
- securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
2960
- hardwareBacked: z13.boolean().default(false),
2961
- attestedAtMs: z13.number().int().nonnegative().nullable().default(null),
2962
- createdAtMs: z13.number().int().nonnegative(),
2963
- revokedAtMs: z13.number().int().nonnegative().nullable()
2964
- });
2965
- var ConsumerOACSchema = z13.object({
2966
- oacId: z13.string().uuid(),
2967
- issuerId: z13.string().min(1).max(64),
2968
- userId: z13.string().uuid(),
2969
- deviceId: z13.string().min(1).max(128),
2970
- alg: z13.enum(["ed25519", "p256"]).optional(),
2971
- devicePubkeyHex: Hex64.optional(),
2972
- devicePubkeySpkiB64: Base64Std.min(64).max(4096).optional(),
2973
- perTxCapKobo: z13.number().int().positive(),
2974
- cumulativeCapKobo: z13.number().int().positive(),
2975
- currency: z13.string().length(3),
2976
- validFromMs: z13.number().int().nonnegative(),
2977
- validUntilMs: z13.number().int().nonnegative(),
2978
- counterSeed: z13.number().int().nonnegative(),
2979
- issuedAtMs: z13.number().int().nonnegative()
2980
- }).refine(
2981
- (o) => {
2982
- const alg = o.alg ?? "ed25519";
2983
- if (alg === "ed25519") {
2984
- return Boolean(o.devicePubkeyHex) && !o.devicePubkeySpkiB64;
2985
- }
2986
- return Boolean(o.devicePubkeySpkiB64) && !o.devicePubkeyHex;
2987
- },
2988
- { message: "OAC device pubkey shape must match alg" }
2989
- );
2990
- var SignedConsumerOACSchema = z13.object({
2991
- oac: ConsumerOACSchema,
2992
- issuerSig: HexAny,
2993
- issuerPublicKeyHex: Hex64
2994
- });
2995
- var OACRecordSchema = SignedConsumerOACSchema.extend({
2996
- currentOfflineSpentKobo: z13.number().int().nonnegative(),
2997
- status: z13.enum([
2998
- "active",
2999
- "superseded",
3000
- "expired",
3001
- "revoked",
3002
- "disabling",
3003
- "draining",
3004
- "closed"
3005
- ]),
3006
- supersededAtMs: z13.number().int().nonnegative().nullable(),
3007
- revokedAtMs: z13.number().int().nonnegative().nullable(),
3008
- holdId: z13.string().uuid().nullable().optional()
3009
- });
3010
- var IssueOACInputSchema = z13.object({
3011
- deviceId: z13.string().min(1).max(128),
3012
- perTxCapKobo: z13.number().int().positive().optional(),
3013
- cumulativeCapKobo: z13.number().int().positive().optional(),
3014
- ttlMs: z13.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
3015
- spendableOnlineKobo: z13.number().int().nonnegative().optional()
3016
- });
3017
- var EnableOfflineInputSchema = z13.object({
3018
- deviceId: z13.string().min(1).max(128),
3019
- amountKobo: z13.number().int().positive(),
3020
- perTxCapKobo: z13.number().int().positive().optional(),
3021
- ttlMs: z13.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
3022
- installId: z13.string().min(1).max(128),
3023
- partnerId: z13.string().min(1).max(64).optional()
3024
- });
3025
- var ProvisionOfflineAllowanceInputSchema = EnableOfflineInputSchema;
3026
- var DisableOfflineInputSchema = z13.object({
3027
- deviceId: z13.string().min(1).max(128),
3028
- installId: z13.string().min(1).max(128).optional(),
3029
- claims: z13.array(z13.unknown()).max(256).optional()
3030
- });
3031
- var OfflineHoldRecordSchema = z13.object({
3032
- holdId: z13.string().uuid(),
3033
- userId: z13.string().uuid(),
3034
- deviceId: z13.string(),
3035
- partnerId: z13.string(),
3036
- adapterKind: z13.string(),
3037
- externalHoldRef: z13.string().nullable(),
3038
- amountKobo: z13.number().int().nonnegative(),
3039
- capturedKobo: z13.number().int().nonnegative(),
3040
- releasedKobo: z13.number().int().nonnegative(),
3041
- remainingKobo: z13.number().int().nonnegative(),
3042
- currency: z13.string().length(3),
3043
- status: z13.enum([
3044
- "placing",
3045
- "active",
3046
- "disabling",
3047
- "draining",
3048
- "closed",
3049
- "revoked",
3050
- "failed"
3051
- ]),
3052
- installId: z13.string().nullable(),
3053
- drainDeadlineMs: z13.number().int().nonnegative(),
3054
- disableRequestedAtMs: z13.number().int().nonnegative().nullable(),
3055
- createdAtMs: z13.number().int().nonnegative(),
3056
- closedAtMs: z13.number().int().nonnegative().nullable(),
3057
- isTrusted: z13.boolean().optional()
3058
- });
3059
- var EnableOfflineResultSchema = z13.object({
3060
- hold: OfflineHoldRecordSchema,
3061
- oac: OACRecordSchema
3062
- });
3063
- var ProvisionOfflineAllowanceResultSchema = EnableOfflineResultSchema;
3064
- var DisableOfflineResultSchema = z13.object({
3065
- hold: OfflineHoldRecordSchema,
3066
- trusted: z13.boolean(),
3067
- settledClaims: z13.number().int().nonnegative()
3068
- });
3069
- var OfflineStatusResultSchema = z13.object({
3070
- hold: OfflineHoldRecordSchema.nullable(),
3071
- active: OACRecordSchema.nullable()
3072
- });
3073
- var OfflineStateResultSchema = z13.object({
3074
- active: OACRecordSchema.nullable()
3075
- });
3076
- var ClaimAlgSchema = z13.enum(["ed25519", "p256"]);
3077
- var ConsumerPaymentClaimSchema = z13.object({
3078
- /** Algorithm discriminator. Omit / 'ed25519' for V1 clients. */
3079
- alg: ClaimAlgSchema.optional(),
3080
- oacId: z13.string().uuid(),
3081
- encounterId: Sha256Hex.optional(),
3082
- payerUserId: z13.string().uuid(),
3083
- payeeUserId: z13.string().uuid(),
3084
- payerDeviceId: z13.string().min(1).max(128),
3085
- payerNonce: z13.string().min(8).max(128),
3086
- payeeNonce: z13.string().min(8).max(128),
3087
- amountKobo: z13.number().int().positive(),
3088
- currency: z13.string().length(3).default("NGN"),
3089
- occurredAtMs: z13.number().int().nonnegative(),
3090
- completedAtMs: z13.number().int().nonnegative().optional(),
3091
- contextId: z13.string().max(128).optional(),
3092
- // ed25519 path
3093
- payerPubkeyHex: Hex64.optional(),
3094
- payerSignature: HexAny.optional(),
3095
- payeePubkeyHex: Hex64.optional(),
3096
- payeeSignature: HexAny.optional(),
3097
- // p256 path
3098
- payerPubkeySpkiB64: z13.string().min(64).max(4096).optional(),
3099
- payerSignatureDerB64: z13.string().min(16).max(2048).optional(),
3100
- payeePubkeySpkiB64: z13.string().min(64).max(4096).optional(),
3101
- payeeSignatureDerB64: z13.string().min(16).max(2048).optional()
3102
- }).refine(
3103
- (c) => {
3104
- const alg = c.alg ?? "ed25519";
3105
- if (alg === "ed25519") {
3106
- return Boolean(c.payerPubkeyHex) && Boolean(c.payerSignature);
3107
- }
3108
- return Boolean(c.payerPubkeySpkiB64) && Boolean(c.payerSignatureDerB64);
3109
- },
3110
- {
3111
- message: "payer key/signature fields must match alg (ed25519: hex; p256: SPKI+DER b64)"
3112
- }
3113
- );
3114
- var ConsumerSettlementSchema = z13.object({
3115
- settlementId: z13.string().uuid(),
3116
- settlementKey: Sha256Hex,
3117
- encounterId: Sha256Hex,
3118
- oacId: z13.string().uuid(),
3119
- payerUserId: z13.string().uuid(),
3120
- payeeUserId: z13.string().uuid(),
3121
- amountKobo: z13.number().int().positive(),
3122
- currency: z13.string().length(3),
3123
- status: z13.enum(["SETTLED", "REVIEW"]),
3124
- reviewReason: z13.string().nullable(),
3125
- ledgerRef: z13.string().nullable(),
3126
- issuerSig: HexAny,
3127
- createdAtMs: z13.number().int().nonnegative()
3128
- });
3129
- var ConsumerSettleResultSchema = z13.object({
3130
- settlement: ConsumerSettlementSchema,
3131
- encounterId: Sha256Hex,
3132
- replayed: z13.boolean()
3133
- });
3134
- var RevokeDeviceKeyInputSchema = z13.object({
3135
- deviceId: z13.string().min(1).max(128),
3136
- /** Step-up token from /api/v1/auth/send/verify with purpose='offline_revoke'. */
3137
- sendAuthToken: z13.string().min(16)
3138
- });
3139
- function createMeOfflineClient(opts) {
3140
- const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
3141
- if (!fetchImpl) {
3142
- throw new Error("createMeOfflineClient: no fetch implementation available");
3143
- }
3144
- const baseUrl = opts.baseUrl.replace(/\/$/, "");
3145
- async function call(method, path, body, parser) {
3146
- const init2 = {
3147
- method,
3148
- headers: {
3149
- "content-type": "application/json",
3150
- accept: "application/json"
3151
- }
3152
- };
3153
- if (body !== void 0) init2.body = JSON.stringify(body);
3154
- const resp = await fetchImpl(`${baseUrl}${path}`, init2);
3155
- const text = await resp.text();
3156
- let raw = void 0;
3157
- if (text) {
3158
- try {
3159
- raw = JSON.parse(text);
3160
- } catch {
3161
- }
3162
- }
3163
- if (!resp.ok) {
3164
- const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
3165
- const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
3166
- throw new FlurApiError(resp.status, code, message, raw);
3167
- }
3168
- return parser(raw);
3169
- }
3170
- const deviceKeyItems = z13.object({ items: z13.array(DeviceKeyRecordSchema) });
3171
- return {
3172
- registerDeviceKey: (input) => call(
3173
- "POST",
3174
- "/v1/me/offline/keys",
3175
- RegisterDeviceKeyInputSchema.parse(input),
3176
- (raw) => DeviceKeyRecordSchema.parse(raw)
3177
- ),
3178
- issueP256EnrollmentChallenge: (input) => call(
3179
- "POST",
3180
- "/v1/me/offline/keys/p256/challenge",
3181
- P256EnrollmentChallengeInputSchema.parse(input),
3182
- (raw) => P256EnrollmentChallengeResultSchema.parse(raw)
3183
- ),
3184
- registerDeviceKeyP256: (input) => call(
3185
- "POST",
3186
- "/v1/me/offline/keys/p256",
3187
- RegisterDeviceKeyP256InputSchema.parse(input),
3188
- (raw) => DeviceKeyRecordSchema.parse(raw)
3189
- ),
3190
- listDeviceKeys: () => call(
3191
- "GET",
3192
- "/v1/me/offline/keys",
3193
- void 0,
3194
- (raw) => deviceKeyItems.parse(raw)
3195
- ),
3196
- revokeDeviceKey: (input) => call(
3197
- "POST",
3198
- "/v1/me/offline/keys/revoke",
3199
- RevokeDeviceKeyInputSchema.parse(input),
3200
- () => void 0
3201
- ),
3202
- provisionAllowance: (input) => call(
3203
- "POST",
3204
- "/v1/me/offline/allowance",
3205
- ProvisionOfflineAllowanceInputSchema.parse(input),
3206
- (raw) => ProvisionOfflineAllowanceResultSchema.parse(raw)
3207
- ),
3208
- enable: (input) => call(
3209
- "POST",
3210
- "/v1/me/offline/enable",
3211
- EnableOfflineInputSchema.parse(input),
3212
- (raw) => EnableOfflineResultSchema.parse(raw)
3213
- ),
3214
- refresh: (input) => call(
3215
- "POST",
3216
- "/v1/me/offline/refresh",
3217
- IssueOACInputSchema.parse(input),
3218
- (raw) => OACRecordSchema.parse(raw)
3219
- ),
3220
- disable: (input) => call(
3221
- "POST",
3222
- "/v1/me/offline/disable",
3223
- DisableOfflineInputSchema.parse(input),
3224
- (raw) => DisableOfflineResultSchema.parse(raw)
3225
- ),
3226
- getStatus: (deviceId) => {
3227
- const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
3228
- return call(
3229
- "GET",
3230
- `/v1/me/offline/status${qs}`,
3231
- void 0,
3232
- (raw) => OfflineStatusResultSchema.parse(raw)
3233
- );
3234
- },
3235
- getState: (deviceId) => {
3236
- const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
3237
- return call(
3238
- "GET",
3239
- `/v1/me/offline/state${qs}`,
3240
- void 0,
3241
- (raw) => OfflineStateResultSchema.parse(raw)
3242
- );
3243
- },
3244
- submitClaim: (claim) => call(
3245
- "POST",
3246
- "/v1/me/offline/claims",
3247
- ConsumerPaymentClaimSchema.parse(claim),
3248
- (raw) => ConsumerSettleResultSchema.parse(raw)
3249
- )
3250
- };
3251
- }
3252
-
3253
3314
  // src/me-offline/signer.ts
3254
- import { ed25519 as ed255192 } from "@noble/curves/ed25519";
3255
- import { p256 } from "@noble/curves/nist";
3256
- import { bytesToHex as bytesToHex5, hexToBytes as hexToBytes2 } from "@noble/hashes/utils";
3315
+ import { p256 as p2562 } from "@noble/curves/nist";
3257
3316
  var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
3258
3317
  function canonicalClaimSigningPayload(claim) {
3259
3318
  return {
@@ -3275,7 +3334,7 @@ function canonicalClaimSigningPayload(claim) {
3275
3334
  function canonicalClaimSigningBytes(claim) {
3276
3335
  return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
3277
3336
  }
3278
- function bytesToBase64(bytes) {
3337
+ function bytesToBase642(bytes) {
3279
3338
  if (typeof Buffer !== "undefined") {
3280
3339
  return Buffer.from(bytes).toString("base64");
3281
3340
  }
@@ -3283,7 +3342,7 @@ function bytesToBase64(bytes) {
3283
3342
  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3284
3343
  return btoa(bin);
3285
3344
  }
3286
- function base64ToBytes(b64) {
3345
+ function base64ToBytes2(b64) {
3287
3346
  if (typeof Buffer !== "undefined") {
3288
3347
  return new Uint8Array(Buffer.from(b64, "base64"));
3289
3348
  }
@@ -3292,7 +3351,7 @@ function base64ToBytes(b64) {
3292
3351
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
3293
3352
  return out;
3294
3353
  }
3295
- var P256_SPKI_HEADER = new Uint8Array([
3354
+ var P256_SPKI_HEADER2 = new Uint8Array([
3296
3355
  48,
3297
3356
  89,
3298
3357
  48,
@@ -3324,38 +3383,25 @@ function p256PublicKeyToSpkiB64(rawUncompressed) {
3324
3383
  if (rawUncompressed.length !== 65 || rawUncompressed[0] !== 4) {
3325
3384
  throw new Error("p256: expected 65-byte uncompressed point");
3326
3385
  }
3327
- const out = new Uint8Array(P256_SPKI_HEADER.length + rawUncompressed.length);
3328
- out.set(P256_SPKI_HEADER, 0);
3329
- out.set(rawUncompressed, P256_SPKI_HEADER.length);
3330
- return bytesToBase64(out);
3386
+ const out = new Uint8Array(P256_SPKI_HEADER2.length + rawUncompressed.length);
3387
+ out.set(P256_SPKI_HEADER2, 0);
3388
+ out.set(rawUncompressed, P256_SPKI_HEADER2.length);
3389
+ return bytesToBase642(out);
3331
3390
  }
3332
3391
  function p256SpkiB64ToPublicKey(spkiB64) {
3333
- const spki = base64ToBytes(spkiB64);
3334
- if (spki.length !== P256_SPKI_HEADER.length + 65) {
3392
+ const spki = base64ToBytes2(spkiB64);
3393
+ if (spki.length !== P256_SPKI_HEADER2.length + 65) {
3335
3394
  throw new Error("p256: invalid SPKI length");
3336
3395
  }
3337
- for (let i = 0; i < P256_SPKI_HEADER.length; i++) {
3338
- if (spki[i] !== P256_SPKI_HEADER[i]) {
3396
+ for (let i = 0; i < P256_SPKI_HEADER2.length; i++) {
3397
+ if (spki[i] !== P256_SPKI_HEADER2[i]) {
3339
3398
  throw new Error("p256: invalid SPKI header");
3340
3399
  }
3341
3400
  }
3342
- return spki.slice(P256_SPKI_HEADER.length);
3343
- }
3344
- function createSoftwareEd25519Signer(privateKey) {
3345
- const pub = ed255192.getPublicKey(privateKey);
3346
- return {
3347
- alg: "ed25519",
3348
- async getPublicKey() {
3349
- return { alg: "ed25519", publicKey: bytesToHex5(pub) };
3350
- },
3351
- async sign(bytes) {
3352
- const sig = ed255192.sign(bytes, privateKey);
3353
- return { alg: "ed25519", signature: bytesToHex5(sig) };
3354
- }
3355
- };
3401
+ return spki.slice(P256_SPKI_HEADER2.length);
3356
3402
  }
3357
3403
  function createSoftwareP256Signer(privateKey) {
3358
- const raw = p256.getPublicKey(privateKey, false);
3404
+ const raw = p2562.getPublicKey(privateKey, false);
3359
3405
  const spkiB64 = p256PublicKeyToSpkiB64(raw);
3360
3406
  return {
3361
3407
  alg: "p256",
@@ -3363,35 +3409,87 @@ function createSoftwareP256Signer(privateKey) {
3363
3409
  return { alg: "p256", publicKey: spkiB64 };
3364
3410
  },
3365
3411
  async sign(bytes) {
3366
- const sig = p256.sign(bytes, privateKey, { prehash: true });
3412
+ const sig = p2562.sign(bytes, privateKey, { prehash: true });
3367
3413
  const der = sig.toBytes("der");
3368
- return { alg: "p256", signature: bytesToBase64(der) };
3414
+ return { alg: "p256", signature: bytesToBase642(der) };
3369
3415
  }
3370
3416
  };
3371
3417
  }
3372
3418
  function verifyClaimSignature(input) {
3373
3419
  try {
3374
- if (input.alg === "ed25519") {
3375
- return ed255192.verify(
3376
- hexToBytes2(input.signature),
3377
- input.bytes,
3378
- hexToBytes2(input.publicKey)
3379
- );
3380
- }
3381
- if (input.alg === "p256") {
3382
- const sigDer = base64ToBytes(input.signature);
3383
- const pub = p256SpkiB64ToPublicKey(input.publicKey);
3384
- return p256.verify(sigDer, input.bytes, pub, {
3385
- prehash: true,
3386
- format: "der"
3387
- });
3388
- }
3389
- return false;
3420
+ if (input.alg !== "p256") return false;
3421
+ const sigDer = base64ToBytes2(input.signature);
3422
+ const pub = p256SpkiB64ToPublicKey(input.publicKey);
3423
+ return p2562.verify(sigDer, input.bytes, pub, {
3424
+ prehash: true,
3425
+ format: "der"
3426
+ });
3390
3427
  } catch {
3391
3428
  return false;
3392
3429
  }
3393
3430
  }
3394
3431
 
3432
+ // src/me-offline/sms.ts
3433
+ var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3434
+ var TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3435
+ function encodeOfflineClaimSmsMessage(claim) {
3436
+ const parsed = ConsumerPaymentClaimSchema.parse(claim);
3437
+ const json = JSON.stringify(parsed);
3438
+ return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf8(json)}`;
3439
+ }
3440
+ function decodeOfflineClaimSmsMessage(message) {
3441
+ const token = extractOfflineClaimSmsToken(message);
3442
+ if (!token) {
3443
+ throw new Error("offline claim SMS token not found");
3444
+ }
3445
+ const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3446
+ let raw;
3447
+ try {
3448
+ raw = JSON.parse(base64UrlDecodeUtf8(encoded));
3449
+ } catch {
3450
+ throw new Error("offline claim SMS token is malformed");
3451
+ }
3452
+ const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
3453
+ if (!parsed.success) {
3454
+ throw new Error("offline claim SMS token is invalid");
3455
+ }
3456
+ return parsed.data;
3457
+ }
3458
+ function extractOfflineClaimSmsToken(message) {
3459
+ const trimmed = message.trim();
3460
+ if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
3461
+ return trimmed.split(/\s+/, 1)[0] ?? null;
3462
+ }
3463
+ return TOKEN_RE.exec(message)?.[1] ?? null;
3464
+ }
3465
+ function base64UrlEncodeUtf8(input) {
3466
+ const bytes = new TextEncoder().encode(input);
3467
+ let binary = "";
3468
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3469
+ const base64 = typeof btoa === "function" ? btoa(binary) : typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : void 0;
3470
+ if (!base64) throw new Error("base64 encoder unavailable");
3471
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
3472
+ }
3473
+ function base64UrlDecodeUtf8(input) {
3474
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
3475
+ const padded = base64.padEnd(
3476
+ base64.length + (4 - base64.length % 4) % 4,
3477
+ "="
3478
+ );
3479
+ if (typeof atob === "function") {
3480
+ const binary = atob(padded);
3481
+ const bytes = new Uint8Array(binary.length);
3482
+ for (let index = 0; index < binary.length; index++) {
3483
+ bytes[index] = binary.charCodeAt(index);
3484
+ }
3485
+ return new TextDecoder().decode(bytes);
3486
+ }
3487
+ if (typeof Buffer !== "undefined") {
3488
+ return Buffer.from(padded, "base64").toString("utf8");
3489
+ }
3490
+ throw new Error("base64 decoder unavailable");
3491
+ }
3492
+
3395
3493
  // src/partner-funding/client.ts
3396
3494
  import { z as z14 } from "zod";
3397
3495
  var MinorString = z14.string().regex(/^-?\d+$/);
@@ -3767,14 +3865,15 @@ function buildArtifactBody(input) {
3767
3865
  return { ...header, data: input.data };
3768
3866
  }
3769
3867
  function signArtifact(body, privateKey) {
3770
- const sig = bytesToHex(sign(canonicalJSONBytes(body), privateKey));
3868
+ const sig = signIssuerP256(canonicalJSONBytes(body), privateKey);
3771
3869
  return { body, sig };
3772
3870
  }
3773
3871
  function encodeArtifactUri(signed) {
3774
3872
  const bodyBytes = canonicalJSONBytes(signed.body);
3775
3873
  const bodyB64 = base64UrlEncode(bodyBytes);
3776
- const sigB64 = base64UrlEncode(hexToBytes(signed.sig));
3777
- return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64}`;
3874
+ const sigBytes = base64UrlDecode(signed.sig);
3875
+ const sigB64Url = base64UrlEncode(sigBytes);
3876
+ return `${FLUR_ARTIFACT_URI_PREFIX}${signed.body.t}/${bodyB64}.${sigB64Url}`;
3778
3877
  }
3779
3878
  function decodeArtifactUri(uri) {
3780
3879
  if (!uri.startsWith(FLUR_ARTIFACT_URI_PREFIX)) {
@@ -3805,9 +3904,9 @@ function decodeArtifactUri(uri) {
3805
3904
  }
3806
3905
  const bodyBytes = base64UrlDecode(payload.slice(0, dot));
3807
3906
  const sigBytes = base64UrlDecode(payload.slice(dot + 1));
3808
- if (sigBytes.length !== 64) {
3907
+ if (sigBytes.length < 64 || sigBytes.length > 80) {
3809
3908
  throw new FlurArtifactError(
3810
- `Signature must be 64 bytes, got ${sigBytes.length}`,
3909
+ `Signature length out of range: ${sigBytes.length}`,
3811
3910
  "INVALID_SIGNATURE"
3812
3911
  );
3813
3912
  }
@@ -3834,17 +3933,26 @@ function decodeArtifactUri(uri) {
3834
3933
  type,
3835
3934
  bodyBytes,
3836
3935
  body: bodyJson,
3837
- sig: bytesToHex(sigBytes)
3936
+ // Encode as standard base64 (not url-safe) for sig field consistency.
3937
+ sig: encodeStdBase64(sigBytes)
3838
3938
  };
3839
3939
  }
3840
- function verifyArtifactSignature(decoded, publicKey, options = {}) {
3940
+ function encodeStdBase64(bytes) {
3941
+ if (typeof Buffer !== "undefined") {
3942
+ return Buffer.from(bytes).toString("base64");
3943
+ }
3944
+ let bin = "";
3945
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
3946
+ return typeof btoa === "function" ? btoa(bin) : "";
3947
+ }
3948
+ function verifyArtifactSignature(decoded, publicKeySpkiB64, options = {}) {
3841
3949
  if (options.enforceExpiry !== false && decoded.body.exp !== void 0) {
3842
3950
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
3843
3951
  if (decoded.body.exp < now) {
3844
3952
  throw new FlurArtifactError("Artifact has expired", "EXPIRED");
3845
3953
  }
3846
3954
  }
3847
- return verify(decoded.bodyBytes, hexToBytes(decoded.sig), publicKey);
3955
+ return verifyIssuerP256(decoded.bodyBytes, decoded.sig, publicKeySpkiB64);
3848
3956
  }
3849
3957
 
3850
3958
  // src/artifacts/types.ts
@@ -3863,7 +3971,7 @@ var ARTIFACT_TYPES = {
3863
3971
  PASS: "pass",
3864
3972
  IDENTITY: "identity"
3865
3973
  };
3866
- var HexString4 = (length) => z16.string().regex(
3974
+ var HexString = (length) => z16.string().regex(
3867
3975
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
3868
3976
  `expected ${length}-byte hex string`
3869
3977
  );
@@ -3881,7 +3989,7 @@ var ReceiptArtifactSchema = z16.object({
3881
3989
  settledAtMs: z16.number().int().positive(),
3882
3990
  ledgerTxnId: z16.string().min(1).max(64).optional(),
3883
3991
  memo: z16.string().max(140).optional(),
3884
- hashChainPrev: HexString4(32).optional()
3992
+ hashChainPrev: HexString(32).optional()
3885
3993
  });
3886
3994
  var ShortId = z16.string().min(1).max(64);
3887
3995
  var PositiveInt = z16.number().int().positive();
@@ -3963,7 +4071,7 @@ var StatementArtifactSchema = z16.object({
3963
4071
  closingBalanceKobo: z16.number().int(),
3964
4072
  transactionCount: NonNegativeInt,
3965
4073
  currency: Currency2,
3966
- hashChainPrev: HexString4(32).optional()
4074
+ hashChainPrev: HexString(32).optional()
3967
4075
  }).refine((v) => v.periodEndMs > v.periodStartMs, {
3968
4076
  message: "periodEndMs must be greater than periodStartMs",
3969
4077
  path: ["periodEndMs"]
@@ -3996,7 +4104,7 @@ var IdentityArtifactSchema = z16.object({
3996
4104
  "kyc_tier",
3997
4105
  "age_band"
3998
4106
  ]),
3999
- claimValueHash: HexString4(32),
4107
+ claimValueHash: HexString(32),
4000
4108
  attestedAtMs: PositiveInt
4001
4109
  });
4002
4110
  var ARTIFACT_BODY_SCHEMAS = {
@@ -4060,7 +4168,7 @@ function createArtifactUri(input) {
4060
4168
  const signed = signArtifact(body, input.privateKey);
4061
4169
  return { uri: encodeArtifactUri(signed), signed };
4062
4170
  }
4063
- function verifyArtifactUri(uri, publicKey, options = {}) {
4171
+ function verifyArtifactUri(uri, publicKeySpkiB64, options = {}) {
4064
4172
  const decoded = decodeArtifactUri(uri);
4065
4173
  if (!isKnownArtifactType(decoded.type)) {
4066
4174
  throw new FlurArtifactError(
@@ -4082,7 +4190,7 @@ function verifyArtifactUri(uri, publicKey, options = {}) {
4082
4190
  "INVALID_BODY"
4083
4191
  );
4084
4192
  }
4085
- const ok = verifyArtifactSignature(decoded, publicKey, options);
4193
+ const ok = verifyArtifactSignature(decoded, publicKeySpkiB64, options);
4086
4194
  if (!ok) {
4087
4195
  throw new FlurArtifactError(
4088
4196
  "Artifact signature verification failed",
@@ -4158,6 +4266,7 @@ export {
4158
4266
  HARDENED_ARTIFACT_TYPES,
4159
4267
  IdentityArtifactSchema,
4160
4268
  IngestFundingResultSchema,
4269
+ IssueAccountOacInputSchema,
4161
4270
  IssueOACInputSchema,
4162
4271
  LedgerJournalEntryArtifactSchema,
4163
4272
  ListPayoutDestinationsResultSchema,
@@ -4175,6 +4284,7 @@ export {
4175
4284
  OAC_DEFAULT_CUMULATIVE_KOBO,
4176
4285
  OAC_DEFAULT_PER_TX_KOBO,
4177
4286
  OAC_DEFAULT_VALIDITY_MS,
4287
+ OFFLINE_CLAIM_SMS_PREFIX,
4178
4288
  OfflineClaimArtifactSchema,
4179
4289
  OfflineHoldRecordSchema,
4180
4290
  OfflinePaymentAuthorizationArtifactSchema,
@@ -4270,20 +4380,21 @@ export {
4270
4380
  createPassesClient,
4271
4381
  createReceiptArtifactUri,
4272
4382
  createReceiptsClient,
4273
- createSoftwareEd25519Signer,
4274
4383
  createSoftwareP256Signer,
4275
4384
  decodeArtifactUri,
4276
4385
  decodeAuthorizationQR,
4277
4386
  decodeBase45,
4387
+ decodeOfflineClaimSmsMessage,
4278
4388
  decodePaymentRequestQR,
4279
4389
  encodeArtifactUri,
4280
4390
  encodeAuthorizationQR,
4281
4391
  encodeBase45,
4282
4392
  encodeNQR,
4393
+ encodeOfflineClaimSmsMessage,
4283
4394
  encodePaymentRequestQR,
4395
+ extractOfflineClaimSmsToken,
4284
4396
  formatAmount,
4285
4397
  generateDynamicQR,
4286
- generateKeyPair,
4287
4398
  generateStaticQR,
4288
4399
  init,
4289
4400
  isHardenedArtifactType,
@@ -4294,13 +4405,10 @@ export {
4294
4405
  parseAmountInput,
4295
4406
  parseNQR,
4296
4407
  parseQR,
4297
- publicKeyFromPrivate,
4298
4408
  readTLV,
4299
4409
  routingHint,
4300
- sign,
4301
4410
  signArtifact,
4302
4411
  signAuthorization,
4303
- signCanonical,
4304
4412
  signOAC,
4305
4413
  signPartnerRequest,
4306
4414
  signPass,
@@ -4308,11 +4416,9 @@ export {
4308
4416
  signReceipt,
4309
4417
  signRedemption,
4310
4418
  signRequestHMAC,
4311
- verify,
4312
4419
  verifyArtifactSignature,
4313
4420
  verifyArtifactUri,
4314
4421
  verifyAuthorization,
4315
- verifyCanonical,
4316
4422
  verifyClaimSignature,
4317
4423
  verifyOAC,
4318
4424
  verifyPass,