@me.jango/signa 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,5 +1,50 @@
1
1
  import { Address, Hex } from 'viem';
2
2
 
3
+ /** Why a payment reverted, as a stable code apps and agents branch on. */
4
+ type PayFailureReason = "TERMINAL_UNDER_MIN" | "BUYBACK_SLIPPAGE_EXCEEDED" | "BUYBACK_DERIVED_FLOOR_NOT_MET" | "BUYBACK_SWAP_AMOUNT_TOO_HIGH" | "ROUTER_SLIPPAGE_EXCEEDED" | "ROUTER_NO_ROUTE" | "TOKEN_NOT_ACCEPTED" | "PAYMENTS_PAUSED" | "NO_RULESET" | "ALLOWANCE_TOO_LOW" | "ALLOWANCE_EXPIRED" | "BALANCE_TOO_LOW" | "UNEXPECTED_ETH" | "NFT_SOLD_OUT" | "NFT_UNAVAILABLE" | "NFT_PRICE_ABOVE_PAYMENT" | "NFT_OVERSPENDING";
5
+ /**
6
+ * The setting a payer can relax so a retry clears the failing check.
7
+ * `minimum` is the payer's token minimum: the terminal's `minReturnedTokens`
8
+ * (`TERMINAL_UNDER_MIN`) or the buyback hook's explicit minimum in the pay
9
+ * metadata (`BUYBACK_SLIPPAGE_EXCEEDED`). `prepareUserPay` floors both by its
10
+ * `slippageBps`, so the fix is preparing again with the larger tolerance
11
+ * {@link payRetrySlippageBps} gives; lowering only `minReturnedTokens` leaves
12
+ * the hook's minimum where it was. Buyback hooks before 1.4 raise the same
13
+ * error for a floor they derive from their own price oracle, which no
14
+ * tolerance moves: the fresh preparation's simulation shows that. The
15
+ * router's conversion minimum is never adjustable: the router can derive it
16
+ * itself.
17
+ */
18
+ type PayFailureAdjustable = "minimum";
19
+ /**
20
+ * A decoded payment revert. `received` and `minimum` are set when the
21
+ * payment fell short of a minimum, in that check's units (for the router,
22
+ * the token it converts into), and `adjustable` names the setting a retry
23
+ * can relax, if any.
24
+ */
25
+ interface PayFailure$1 {
26
+ reason: PayFailureReason;
27
+ /** What the payment would have settled, in the minimum's units. */
28
+ received?: bigint;
29
+ /** The minimum it fell short of. */
30
+ minimum?: bigint;
31
+ /**
32
+ * Set with `received`: true when the failing check settled nothing, so a
33
+ * lower minimum is no fix and `message` does not suggest one. For the
34
+ * buyback hook that is the part it checked, not always the whole payment.
35
+ * Absent means unknown, not a fill.
36
+ */
37
+ zeroFill?: boolean;
38
+ /**
39
+ * Set only with a nonzero `received` below `minimum`: the setting the payer
40
+ * can relax so a retry clears the failing check. Absent when no setting
41
+ * the payer controls would help; never relax anything without it.
42
+ */
43
+ adjustable?: PayFailureAdjustable;
44
+ /** One plain sentence to show the payer. */
45
+ message: string;
46
+ }
47
+
3
48
  /** Browser-safe primitives shared by the existing signed-request client and server. */
4
49
  type BotScope = "read" | "plan" | "relay";
5
50
 
@@ -129,6 +174,145 @@ declare function buildBotProofTypedData(audience: string, proof: BotProof): {
129
174
  };
130
175
  };
131
176
 
177
+ /** Identifies one immutable effect, including the exact serialized transaction signature. */
178
+ type TransactionApprovalBinding = {
179
+ accountId: string;
180
+ principalId: string;
181
+ planId: string;
182
+ commitment: Hex;
183
+ stepIndex: number;
184
+ transactionHash: Hex;
185
+ };
186
+ type TransactionApprovalClaims = TransactionApprovalBinding & {
187
+ issuedAt: number;
188
+ expiresAt: number;
189
+ nonce: Hex;
190
+ };
191
+ type TransactionApproval = TransactionApprovalClaims & {
192
+ signature: Hex;
193
+ };
194
+ type SponsorshipApprovalBinding = {
195
+ accountId: string;
196
+ principalId: string;
197
+ sponsorshipId: string;
198
+ commitment: Hex;
199
+ submissionHash: Hex;
200
+ };
201
+ type SponsorshipApprovalClaims = SponsorshipApprovalBinding & {
202
+ issuedAt: number;
203
+ expiresAt: number;
204
+ nonce: Hex;
205
+ };
206
+ type SponsorshipApproval = SponsorshipApprovalClaims & {
207
+ signature: Hex;
208
+ };
209
+ /** Browser-safe owner-wallet signing document; use a fresh cryptographically random nonce. */
210
+ declare function buildTransactionApprovalTypedData(audience: string, input: TransactionApprovalClaims): {
211
+ domain: {
212
+ name: string;
213
+ version: string;
214
+ chainId: number;
215
+ salt: `0x${string}`;
216
+ };
217
+ types: {
218
+ readonly CenterTransactionApproval: readonly [{
219
+ readonly name: "audience";
220
+ readonly type: "string";
221
+ }, {
222
+ readonly name: "accountId";
223
+ readonly type: "string";
224
+ }, {
225
+ readonly name: "principalId";
226
+ readonly type: "string";
227
+ }, {
228
+ readonly name: "planId";
229
+ readonly type: "string";
230
+ }, {
231
+ readonly name: "commitment";
232
+ readonly type: "bytes32";
233
+ }, {
234
+ readonly name: "stepIndex";
235
+ readonly type: "uint32";
236
+ }, {
237
+ readonly name: "transactionHash";
238
+ readonly type: "bytes32";
239
+ }, {
240
+ readonly name: "issuedAt";
241
+ readonly type: "uint64";
242
+ }, {
243
+ readonly name: "expiresAt";
244
+ readonly type: "uint64";
245
+ }, {
246
+ readonly name: "nonce";
247
+ readonly type: "bytes32";
248
+ }];
249
+ };
250
+ primaryType: "CenterTransactionApproval";
251
+ message: {
252
+ audience: string;
253
+ issuedAt: bigint;
254
+ expiresAt: bigint;
255
+ accountId: string;
256
+ principalId: string;
257
+ planId: string;
258
+ commitment: Hex;
259
+ stepIndex: number;
260
+ transactionHash: Hex;
261
+ nonce: Hex;
262
+ };
263
+ };
264
+ /** Approves publishing exact signed forward requests, independently of direct transaction relay. */
265
+ declare function buildSponsorshipApprovalTypedData(audience: string, input: SponsorshipApprovalClaims): {
266
+ domain: {
267
+ name: string;
268
+ version: string;
269
+ chainId: number;
270
+ salt: `0x${string}`;
271
+ };
272
+ types: {
273
+ readonly CenterSponsorshipApproval: readonly [{
274
+ readonly name: "audience";
275
+ readonly type: "string";
276
+ }, {
277
+ readonly name: "accountId";
278
+ readonly type: "string";
279
+ }, {
280
+ readonly name: "principalId";
281
+ readonly type: "string";
282
+ }, {
283
+ readonly name: "sponsorshipId";
284
+ readonly type: "string";
285
+ }, {
286
+ readonly name: "commitment";
287
+ readonly type: "bytes32";
288
+ }, {
289
+ readonly name: "submissionHash";
290
+ readonly type: "bytes32";
291
+ }, {
292
+ readonly name: "issuedAt";
293
+ readonly type: "uint64";
294
+ }, {
295
+ readonly name: "expiresAt";
296
+ readonly type: "uint64";
297
+ }, {
298
+ readonly name: "nonce";
299
+ readonly type: "bytes32";
300
+ }];
301
+ };
302
+ primaryType: "CenterSponsorshipApproval";
303
+ message: {
304
+ audience: string;
305
+ issuedAt: bigint;
306
+ expiresAt: bigint;
307
+ nonce: `0x${string}`;
308
+ accountId: string;
309
+ principalId: string;
310
+ sponsorshipId: string;
311
+ commitment: Hex;
312
+ submissionHash: Hex;
313
+ };
314
+ };
315
+
132
316
  interface RestBlockEvidence {
133
317
  chainId: number;
134
318
  blockNumber: string;
@@ -286,145 +470,6 @@ interface UserOperationObservation {
286
470
  payFailure?: PayFailure;
287
471
  }
288
472
 
289
- /** Identifies one immutable effect, including the exact serialized transaction signature. */
290
- type TransactionApprovalBinding = {
291
- accountId: string;
292
- principalId: string;
293
- planId: string;
294
- commitment: Hex;
295
- stepIndex: number;
296
- transactionHash: Hex;
297
- };
298
- type TransactionApprovalClaims = TransactionApprovalBinding & {
299
- issuedAt: number;
300
- expiresAt: number;
301
- nonce: Hex;
302
- };
303
- type TransactionApproval = TransactionApprovalClaims & {
304
- signature: Hex;
305
- };
306
- type SponsorshipApprovalBinding = {
307
- accountId: string;
308
- principalId: string;
309
- sponsorshipId: string;
310
- commitment: Hex;
311
- submissionHash: Hex;
312
- };
313
- type SponsorshipApprovalClaims = SponsorshipApprovalBinding & {
314
- issuedAt: number;
315
- expiresAt: number;
316
- nonce: Hex;
317
- };
318
- type SponsorshipApproval = SponsorshipApprovalClaims & {
319
- signature: Hex;
320
- };
321
- /** Browser-safe owner-wallet signing document; use a fresh cryptographically random nonce. */
322
- declare function buildTransactionApprovalTypedData(audience: string, input: TransactionApprovalClaims): {
323
- domain: {
324
- name: string;
325
- version: string;
326
- chainId: number;
327
- salt: `0x${string}`;
328
- };
329
- types: {
330
- readonly CenterTransactionApproval: readonly [{
331
- readonly name: "audience";
332
- readonly type: "string";
333
- }, {
334
- readonly name: "accountId";
335
- readonly type: "string";
336
- }, {
337
- readonly name: "principalId";
338
- readonly type: "string";
339
- }, {
340
- readonly name: "planId";
341
- readonly type: "string";
342
- }, {
343
- readonly name: "commitment";
344
- readonly type: "bytes32";
345
- }, {
346
- readonly name: "stepIndex";
347
- readonly type: "uint32";
348
- }, {
349
- readonly name: "transactionHash";
350
- readonly type: "bytes32";
351
- }, {
352
- readonly name: "issuedAt";
353
- readonly type: "uint64";
354
- }, {
355
- readonly name: "expiresAt";
356
- readonly type: "uint64";
357
- }, {
358
- readonly name: "nonce";
359
- readonly type: "bytes32";
360
- }];
361
- };
362
- primaryType: "CenterTransactionApproval";
363
- message: {
364
- audience: string;
365
- issuedAt: bigint;
366
- expiresAt: bigint;
367
- accountId: string;
368
- principalId: string;
369
- planId: string;
370
- commitment: Hex;
371
- stepIndex: number;
372
- transactionHash: Hex;
373
- nonce: Hex;
374
- };
375
- };
376
- /** Approves publishing exact signed forward requests, independently of direct transaction relay. */
377
- declare function buildSponsorshipApprovalTypedData(audience: string, input: SponsorshipApprovalClaims): {
378
- domain: {
379
- name: string;
380
- version: string;
381
- chainId: number;
382
- salt: `0x${string}`;
383
- };
384
- types: {
385
- readonly CenterSponsorshipApproval: readonly [{
386
- readonly name: "audience";
387
- readonly type: "string";
388
- }, {
389
- readonly name: "accountId";
390
- readonly type: "string";
391
- }, {
392
- readonly name: "principalId";
393
- readonly type: "string";
394
- }, {
395
- readonly name: "sponsorshipId";
396
- readonly type: "string";
397
- }, {
398
- readonly name: "commitment";
399
- readonly type: "bytes32";
400
- }, {
401
- readonly name: "submissionHash";
402
- readonly type: "bytes32";
403
- }, {
404
- readonly name: "issuedAt";
405
- readonly type: "uint64";
406
- }, {
407
- readonly name: "expiresAt";
408
- readonly type: "uint64";
409
- }, {
410
- readonly name: "nonce";
411
- readonly type: "bytes32";
412
- }];
413
- };
414
- primaryType: "CenterSponsorshipApproval";
415
- message: {
416
- audience: string;
417
- issuedAt: bigint;
418
- expiresAt: bigint;
419
- nonce: `0x${string}`;
420
- accountId: string;
421
- principalId: string;
422
- sponsorshipId: string;
423
- commitment: Hex;
424
- submissionHash: Hex;
425
- };
426
- };
427
-
428
473
  interface Safe7579OwnerSigningInput {
429
474
  operation: UserOperationV07;
430
475
  chainId: number;
@@ -1038,7 +1083,8 @@ interface UserOperationRecord {
1038
1083
  createdAt: number;
1039
1084
  expiresAt: number;
1040
1085
  revision: number;
1041
- state: 'prepared' | 'submitting' | 'submission_unknown' | UserOperationObservation['state'];
1086
+ /** `retired`: approved, never sent, and refused for good; nothing claims or resends it. */
1087
+ state: 'prepared' | 'submitting' | 'submission_unknown' | 'retired' | UserOperationObservation['state'];
1042
1088
  submission?: {
1043
1089
  key: string;
1044
1090
  commitment: Hex;
@@ -1047,6 +1093,8 @@ interface UserOperationRecord {
1047
1093
  sessionObservationHash?: Hex;
1048
1094
  };
1049
1095
  observation?: UserOperationObservation;
1096
+ /** When the chain settled a retired operation: it never ran and never can, or Signa's own operation took its nonce. */
1097
+ retiredSettledAt?: number;
1050
1098
  }
1051
1099
 
1052
1100
  interface WalletTypedData {
@@ -1165,6 +1213,8 @@ declare class SmartAccountClient {
1165
1213
  }>;
1166
1214
  submitUserOperation(operationId: string, signature: Hex, key?: string): Promise<PreparedUserOperation>;
1167
1215
  /** With `wait`, the answer is held (up to 20 s) until the operation moves past the revision seen. */
1216
+ /** Retires an operation nothing has sent, so it is never claimed or sent again; one already claimed comes back with its submission. */
1217
+ retireUserOperation(operationId: string): Promise<PreparedUserOperation>;
1168
1218
  userOperation(operationId: string, wait?: {
1169
1219
  seconds: number;
1170
1220
  since: number;
@@ -1223,14 +1273,14 @@ type ClientOptions = {
1223
1273
  maxResponseBytes?: number;
1224
1274
  /** Reads a refused request's served payment reason; without it, errors carry none. `CenterClient`
1225
1275
  * and the wallet client use `payFailureOf`; the served pages, which never show one, leave it out. */
1226
- payFailure?: (served: unknown) => PayFailure | undefined;
1276
+ payFailure?: (served: unknown) => PayFailure$1 | undefined;
1227
1277
  };
1228
1278
  declare class RestClientError extends Error {
1229
1279
  readonly code: string;
1230
1280
  readonly status?: number | undefined;
1231
1281
  /** Why this refused payment attempt would revert, when its calls say. Advisory and for this attempt only; a changed minimum needs a fresh preparation and review. */
1232
- readonly payFailure?: PayFailure;
1233
- constructor(code: string, message: string, status?: number | undefined, payFailure?: PayFailure);
1282
+ readonly payFailure?: PayFailure$1;
1283
+ constructor(code: string, message: string, status?: number | undefined, payFailure?: PayFailure$1);
1234
1284
  }
1235
1285
  type PreparedRequest = {
1236
1286
  url: string;
@@ -1454,8 +1504,9 @@ interface CenterWalletPaymentInput {
1454
1504
  operation: PreparedUserOperation;
1455
1505
  expectedPayment: CenterWalletExpectedPayment;
1456
1506
  }
1457
- /** `expired`: published but never included before its signed validity ended; it cannot execute. */
1458
- type CenterWalletPaymentState = 'reviewing' | 'approved' | 'submitting' | 'pending' | 'confirming' | 'paid' | 'reverted' | 'cancelled' | 'unknown' | 'expired';
1507
+ /** `expired`: published but never included before its signed validity ended; it cannot execute.
1508
+ * `refused`: Signa refused to send it and retired the approval; Signa never sends it, and a fresh payment takes its nonce. */
1509
+ type CenterWalletPaymentState = 'reviewing' | 'approved' | 'submitting' | 'pending' | 'confirming' | 'paid' | 'reverted' | 'cancelled' | 'unknown' | 'expired' | 'refused';
1459
1510
  interface CenterWalletPaymentStatus {
1460
1511
  status: CenterWalletPaymentState;
1461
1512
  accountId: string;
@@ -1469,8 +1520,10 @@ interface CenterWalletPaymentStatus {
1469
1520
  /** An observed outer transaction identifier, never an alias for operationHash. */
1470
1521
  transactionHash?: Hex;
1471
1522
  observation?: UserOperationObservation;
1472
- /** Why this payment reverted, when its trace says; shown only while it stands reverted. Advisory: a changed minimum needs a fresh preparation and review. */
1473
- payFailure?: PayFailure;
1523
+ /** Why this payment reverted, when its trace says; shown only while it stands reverted. The SDK's own
1524
+ * `PayFailure` (bigint amounts, with `zeroFill` and `adjustable` for a shortfall); `observation.payFailure`
1525
+ * keeps the served form. Advisory: a changed minimum needs a fresh preparation and review. */
1526
+ payFailure?: PayFailure$1;
1474
1527
  }
1475
1528
 
1476
1529
  interface CenterWalletStorage {
@@ -1532,6 +1585,7 @@ declare function createCenterWalletClient(options: CenterWalletClientOptions): R
1532
1585
  refreshPayment: (options?: {
1533
1586
  waitSeconds?: number;
1534
1587
  }) => Promise<CenterWalletPaymentStatus>;
1588
+ retirePayment: () => Promise<CenterWalletPaymentStatus>;
1535
1589
  pendingPayment: () => CenterWalletPaymentStatus | null;
1536
1590
  clearPayment: () => void;
1537
1591
  }>;
@@ -1550,4 +1604,4 @@ type SponsoredCall = {
1550
1604
  declare function sponsoredCallsCommitment(calls: readonly SponsoredCall[]): Hex;
1551
1605
 
1552
1606
  export { RestClientError, assertReviewedOperation, createCenterWalletClient, ownerOperationSignature, ownerOperationSigning, sponsoredCallsCommitment, userOperationMaximumCost };
1553
- export type { CenterWalletClientOptions, CenterWalletConnection, CenterWalletExpectedPayment, CenterWalletPaymentInput, CenterWalletPaymentState, CenterWalletPaymentStatus, CenterWalletPreparedConnection, CenterWalletStorage, PayFailure, PreparedRequest, PreparedUserOperation, SmartWalletPlan, SponsoredCall };
1607
+ export type { CenterWalletClientOptions, CenterWalletConnection, CenterWalletExpectedPayment, CenterWalletPaymentInput, CenterWalletPaymentState, CenterWalletPaymentStatus, CenterWalletPreparedConnection, CenterWalletStorage, PayFailure$1 as PayFailure, PreparedRequest, PreparedUserOperation, SmartWalletPlan, SponsoredCall };
package/index.js CHANGED
@@ -950,6 +950,10 @@ var SmartAccountClient = class {
950
950
  );
951
951
  }
952
952
  /** With `wait`, the answer is held (up to 20 s) until the operation moves past the revision seen. */
953
+ /** Retires an operation nothing has sent, so it is never claimed or sent again; one already claimed comes back with its submission. */
954
+ retireUserOperation(operationId) {
955
+ return this.post(`/user-operations/${id(operationId)}/retirement`, {});
956
+ }
953
957
  userOperation(operationId, wait) {
954
958
  if (wait && (!Number.isInteger(wait.seconds) || wait.seconds < 1 || wait.seconds > 20 || !Number.isInteger(wait.since) || wait.since < 0))
955
959
  invalid4("Wait 1\u201320 seconds past a revision already seen.");
@@ -1393,14 +1397,7 @@ function payFailureOf(value) {
1393
1397
  if (Object.keys(rest).length || typeof reason !== "string" || !Object.hasOwn(payFailureReverts, reason) || typeof message2 !== "string" || !amount(received) || !amount(minimum)) return void 0;
1394
1398
  const settled = received === void 0 ? void 0 : BigInt(received);
1395
1399
  const floor = minimum === void 0 ? void 0 : BigInt(minimum);
1396
- const canonical = sdkPayFailures(reason, settled, floor).some((failure) => failure && failure.reason === reason && failure.message === message2 && failure.received === settled && failure.minimum === floor);
1397
- if (!canonical) return void 0;
1398
- return {
1399
- reason,
1400
- message: message2,
1401
- ...received === void 0 ? {} : { received },
1402
- ...minimum === void 0 ? {} : { minimum }
1403
- };
1400
+ return sdkPayFailures(reason, settled, floor).find((failure) => failure && failure.reason === reason && failure.message === message2 && failure.received === settled && failure.minimum === floor) ?? void 0;
1404
1401
  }
1405
1402
 
1406
1403
  // src/rest/client/center.ts
@@ -1854,6 +1851,10 @@ function parseBotRegistration(value) {
1854
1851
  // src/rest/client/walletPayments.ts
1855
1852
  import { sha256 as sha2564, stringToHex as stringToHex6 } from "viem";
1856
1853
 
1854
+ // src/rest/client/paymentStates.ts
1855
+ var settledPaymentStates = ["paid", "reverted", "cancelled", "expired", "refused"];
1856
+ var isSettledPayment = (status) => settledPaymentStates.includes(status);
1857
+
1857
1858
  // src/rest/smartAccounts/passkeySignatures.ts
1858
1859
  import {
1859
1860
  concatHex as concatHex5,
@@ -2166,8 +2167,8 @@ var maximumBytes = 1048576;
2166
2167
  var reviewPath = "/api/v1/wallet/payment-reviews";
2167
2168
  var reviewIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
2168
2169
  var paymentFields = ["kind", "chainId", "account", "token", "terminal", "projectId", "amount", "beneficiary", "minimumReturnedTokens", "memo", "metadata"];
2169
- var states = ["reviewing", "approved", "submitting", "pending", "confirming", "paid", "reverted", "cancelled", "unknown", "expired"];
2170
- var terminal = (status) => ["paid", "reverted", "cancelled", "expired"].includes(status);
2170
+ var states = ["reviewing", "approved", "submitting", "pending", "confirming", "paid", "reverted", "cancelled", "unknown", "expired", "refused"];
2171
+ var terminal = isSettledPayment;
2171
2172
  var hex32 = (value) => typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
2172
2173
  var uuid2 = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
2173
2174
  var integer2 = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
@@ -2310,7 +2311,7 @@ function createCenterWalletPaymentClient(options) {
2310
2311
  if (!("ownerProfile" in signing)) mismatch();
2311
2312
  const expectedSigning = { digest: signing.digest, signedData: signing.signedData, validAfter: signing.validAfter, validUntil: signing.validUntil };
2312
2313
  const payment = checkInput(saved, saved.grant, saved.startedAtMs);
2313
- if (v.version !== "center-wallet-payment-review-v1" || !uuid2(v.id) || saved.review && v.id !== saved.review.id || v.state !== saved.state || v.issuer !== issuer || v.accountId !== saved.grant.accountId || app.origin !== saved.grant.origin || app.callbackUri !== callbackUri || app.grantId !== saved.grant.id || app.grantIncarnation !== saved.grant.incarnation || v.planId !== saved.plan.id || !same3(v.planCommitment, saved.plan.commitment) || v.operationId !== saved.operation.id || !same3(v.operationCommitment, saved.operation.commitment) || !same3(v.operationHash, saved.operation.operationHash) || !equal(v.stepIndexes, saved.operation.stepIndexes) || !equal(normalizeUserOperation(v.operation), normalizeUserOperation(saved.operation.operation)) || v.chainId !== 8453 || !same3(v.entryPoint, saved.operation.entryPoint) || !same3(v.safe7579, signing.typedData.domain.verifyingContract) || !equal(v.signing, expectedSigning) || !equal(comparablePayment(v.payment), comparablePayment(payment)) || !integer2(v.createdAtMs) || v.createdAtMs < saved.startedAtMs - 3e4 || v.createdAtMs > now() + 3e4 || !integer2(v.expiresAtMs) || v.expiresAtMs <= v.createdAtMs || v.expiresAtMs > saved.operation.expiresAt || v.expiresAtMs > saved.grant.expiresAt * 1e3 || v.expiresAtMs > v.createdAtMs + 3e5 || saved.review && (v.createdAtMs !== saved.review.createdAtMs || v.expiresAtMs !== saved.review.expiresAtMs) || !["pending", "approved", "cancelled"].includes(String(v.status)) || !["prepared", "submitting", "submission_unknown", "pending", "unknown", "confirming", "confirmed", "reverted", "expired"].includes(String(v.operationState))) mismatch();
2314
+ if (v.version !== "center-wallet-payment-review-v1" || !uuid2(v.id) || saved.review && v.id !== saved.review.id || v.state !== saved.state || v.issuer !== issuer || v.accountId !== saved.grant.accountId || app.origin !== saved.grant.origin || app.callbackUri !== callbackUri || app.grantId !== saved.grant.id || app.grantIncarnation !== saved.grant.incarnation || v.planId !== saved.plan.id || !same3(v.planCommitment, saved.plan.commitment) || v.operationId !== saved.operation.id || !same3(v.operationCommitment, saved.operation.commitment) || !same3(v.operationHash, saved.operation.operationHash) || !equal(v.stepIndexes, saved.operation.stepIndexes) || !equal(normalizeUserOperation(v.operation), normalizeUserOperation(saved.operation.operation)) || v.chainId !== 8453 || !same3(v.entryPoint, saved.operation.entryPoint) || !same3(v.safe7579, signing.typedData.domain.verifyingContract) || !equal(v.signing, expectedSigning) || !equal(comparablePayment(v.payment), comparablePayment(payment)) || !integer2(v.createdAtMs) || v.createdAtMs < saved.startedAtMs - 3e4 || v.createdAtMs > now() + 3e4 || !integer2(v.expiresAtMs) || v.expiresAtMs <= v.createdAtMs || v.expiresAtMs > saved.operation.expiresAt || v.expiresAtMs > saved.grant.expiresAt * 1e3 || v.expiresAtMs > v.createdAtMs + 3e5 || saved.review && (v.createdAtMs !== saved.review.createdAtMs || v.expiresAtMs !== saved.review.expiresAtMs) || !["pending", "approved", "cancelled"].includes(String(v.status)) || !["prepared", "submitting", "submission_unknown", "pending", "unknown", "confirming", "confirmed", "reverted", "expired", "retired"].includes(String(v.operationState))) mismatch();
2314
2315
  if (v.status === "approved" ? !integer2(v.approvedAtMs) || v.cancelledAtMs !== null : v.approvedAtMs !== null) mismatch();
2315
2316
  if (v.status === "cancelled" ? !integer2(v.cancelledAtMs) : v.cancelledAtMs !== null) mismatch();
2316
2317
  for (const at of [v.approvedAtMs, v.cancelledAtMs])
@@ -2348,10 +2349,11 @@ function createCenterWalletPaymentClient(options) {
2348
2349
  return value;
2349
2350
  }
2350
2351
  function status(value) {
2351
- const payFailure = value.status === "reverted" && value.observation?.state === "reverted" ? payFailureOf(value.observation.payFailure) : void 0;
2352
+ const served = value.status === "reverted" && value.observation?.state === "reverted" ? value.observation.payFailure : void 0;
2353
+ const payFailure = payFailureOf(served);
2352
2354
  const { payFailure: _served, ...observed } = value.observation ?? {};
2353
- const observation = value.observation && { ...observed, ...payFailure ? { payFailure } : {} };
2354
- return snapshot2({
2355
+ const observation = value.observation && { ...observed, ...payFailure ? { payFailure: served } : {} };
2356
+ const copy = snapshot2({
2355
2357
  status: value.status,
2356
2358
  accountId: value.grant.accountId,
2357
2359
  chainId: 8453,
@@ -2364,9 +2366,9 @@ function createCenterWalletPaymentClient(options) {
2364
2366
  ...observation ? {
2365
2367
  observation,
2366
2368
  ...hex32(observation.transactionHash) ? { transactionHash: observation.transactionHash } : {}
2367
- } : {},
2368
- ...payFailure ? { payFailure } : {}
2369
+ } : {}
2369
2370
  });
2371
+ return payFailure ? { ...copy, payFailure } : copy;
2370
2372
  }
2371
2373
  function receiveView(input, saved, appView) {
2372
2374
  const reviewed = checkView(input, saved.value, appView);
@@ -2456,6 +2458,7 @@ function createCenterWalletPaymentClient(options) {
2456
2458
  if (operation.state === "confirmed" && observation?.state === "confirmed" && canonical && receipt?.status === "success" && observation.semantic?.status === "verified") result = "paid";
2457
2459
  if (operation.state === "reverted" && observation?.state === "reverted" && canonical && (receipt?.status === "reverted" || observation.semantic?.status === "failed")) result = "reverted";
2458
2460
  if (operation.state === "expired" && observation?.state === "expired" && observation.transactionHash === void 0 && !receipt) result = "expired";
2461
+ if (operation.state === "retired" && !operation.submission && !observation) result = "refused";
2459
2462
  return save({ ...saved.value, status: result, ...observation ? { observation } : {} }, saved.encoded);
2460
2463
  } catch (error) {
2461
2464
  if (error instanceof RestClientError && ["WALLET_PAYMENT_STORAGE_UNAVAILABLE", "WALLET_PAYMENT_CHANGED"].includes(error.code)) throw error;
@@ -2474,10 +2477,19 @@ function createCenterWalletPaymentClient(options) {
2474
2477
  const result = await client.smartAccounts().submitUserOperation(saved.value.operation.id, saved.value.approval.signature, saved.value.submissionKey);
2475
2478
  return status(receiveOperation(result, saved).value);
2476
2479
  } catch (error) {
2477
- save({ ...saved.value, status: "unknown" }, saved.encoded);
2480
+ saved = save({ ...saved.value, status: "unknown" }, saved.encoded);
2481
+ if (error instanceof RestClientError && error.status !== void 0 && error.status >= 400) await retire(saved).catch(() => void 0);
2478
2482
  throw error;
2479
2483
  }
2480
2484
  }
2485
+ function retire(saved) {
2486
+ return live(saved.value).client.smartAccounts().retireUserOperation(saved.value.operation.id).then((result) => receiveOperation(result, saved));
2487
+ }
2488
+ async function retirePayment() {
2489
+ const saved = pending();
2490
+ if (!saved.value.approval || terminal(saved.value.status)) fail3("WALLET_PAYMENT_NOT_APPROVED", "Only an approved payment that has not settled can be retired.");
2491
+ return status((await retire(saved)).value);
2492
+ }
2481
2493
  const revisions = /* @__PURE__ */ new Map();
2482
2494
  async function refreshPayment(options2 = {}) {
2483
2495
  if (options2.waitSeconds !== void 0 && (!Number.isInteger(options2.waitSeconds) || options2.waitSeconds < 1 || options2.waitSeconds > 20))
@@ -2496,7 +2508,7 @@ function createCenterWalletPaymentClient(options) {
2496
2508
  client.smartAccounts().userOperation(saved.value.operation.id)
2497
2509
  ]);
2498
2510
  const reviewed = receiveView(value2, saved, true);
2499
- if (!snapshot2(operation).submission) return status(reviewed.value);
2511
+ if (!snapshot2(operation).submission && operation.state !== "retired") return status(reviewed.value);
2500
2512
  return status(receiveOperation(operation, save({ ...reviewed.value, submissionStarted: true, status: "submitting" }, reviewed.encoded)).value);
2501
2513
  }
2502
2514
  const value = await client.request({ requestTarget: reviewPath + "/" + saved.value.review.id });
@@ -2528,8 +2540,9 @@ function createCenterWalletPaymentClient(options) {
2528
2540
  try {
2529
2541
  const history = existing === null ? [] : JSON.parse(existing);
2530
2542
  if (!Array.isArray(history)) throw new Error();
2543
+ const { payFailure: _decoded, ...archived } = status(saved.value);
2531
2544
  const receipt = {
2532
- ...status(saved.value),
2545
+ ...archived,
2533
2546
  ...!settled ? { status: "unknown", archiveReason: unsignedWindowElapsed ? "no-local-submission-recorded" : "grant-expired" } : {},
2534
2547
  issuer,
2535
2548
  audience,
@@ -2559,7 +2572,7 @@ function createCenterWalletPaymentClient(options) {
2559
2572
  fail3("WALLET_PAYMENT_STORAGE_UNAVAILABLE", "The original payment must be archived before clearing this tab\u2019s pending record.");
2560
2573
  }
2561
2574
  }
2562
- return Object.freeze({ preparePayment, reviewUrl, completePayment, submitPayment, refreshPayment, pendingPayment, clearPayment });
2575
+ return Object.freeze({ preparePayment, reviewUrl, completePayment, submitPayment, refreshPayment, retirePayment, pendingPayment, clearPayment });
2563
2576
  }
2564
2577
 
2565
2578
  // src/rest/wallet/grantLifetime.ts
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@me.jango/signa",
3
- "version": "0.3.8",
4
- "description": "Connect a Juicebox Center account to your app and review passkey payments",
3
+ "version": "0.4.0",
4
+ "description": "Sign in to an app with a Signa passkey account or an external wallet, and review passkey payments",
5
5
  "repository": { "type": "git", "url": "https://github.com/mejango/signa.git", "directory": "wallet-client" },
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "main": "./index.js",
9
9
  "types": "./index.d.ts",
10
- "exports": { ".": { "types": "./index.d.ts", "import": "./index.js" } },
11
- "files": ["index.js", "index.d.ts", "README.md"],
10
+ "exports": {
11
+ ".": { "types": "./index.d.ts", "import": "./index.js" },
12
+ "./core": { "types": "./core/index.d.ts", "import": "./core/index.js" },
13
+ "./wagmi": { "types": "./wagmi/index.d.ts", "import": "./wagmi/index.js" },
14
+ "./react": { "types": "./react/index.d.ts", "import": "./react/index.js" }
15
+ },
16
+ "files": ["index.js", "index.d.ts", "core/", "wagmi/", "react/", "README.md"],
12
17
  "publishConfig": { "access": "public" },
13
18
  "engines": { "node": ">=22" },
14
- "dependencies": { "viem": "^2.37.5" }
19
+ "dependencies": { "viem": "^2.37.5" },
20
+ "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "wagmi": "^2.10.2 || ^3.0.0" },
21
+ "peerDependenciesMeta": { "react": { "optional": true }, "wagmi": { "optional": true } }
15
22
  }
@@ -0,0 +1,34 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { ConnectController, ConnectOption } from '../core/index.js';
4
+
5
+ type JBConnectModalProps = {
6
+ open: boolean;
7
+ controller: ConnectController;
8
+ /** Called for Escape, the backdrop and the cancel button, after the controller is cancelled. */
9
+ onClose(): void;
10
+ /** Which option is the passkey account; it renders as the primary button. */
11
+ passkeyId?: string;
12
+ /** The primary button's text. Defaults to the platform's prompt: Touch ID, Face ID, Windows Hello, or a passkey. */
13
+ passkeyLabel?: string;
14
+ title?: ReactNode;
15
+ /** Renders a pairing URI (a QR code, a deep link) once an option publishes one. */
16
+ renderHandoff?(uri: string, option: ConnectOption): ReactNode;
17
+ /** A mark for an option without an icon. Defaults to the option's initial. */
18
+ renderIcon?(option: ConnectOption): ReactNode;
19
+ /** Extra footer actions, laid out on the cancel button's baseline. */
20
+ footer?: ReactNode;
21
+ /** App content shown under the wallet tiles while idle, such as mobile wallet links. */
22
+ children?: ReactNode;
23
+ className?: string;
24
+ };
25
+ declare function JBConnectModal(props: JBConnectModalProps): react.JSX.Element;
26
+
27
+ /** Themed through custom properties so a Tailwind app and a plain-CSS app both look native. */
28
+ declare const connectModalCss = "\n.jb-connect{--jb-connect-bg:#fff;--jb-connect-fg:#1a1a1a;--jb-connect-muted:#6b6b6b;--jb-connect-line:#dcdcdc;--jb-connect-accent:#1a1a1a;--jb-connect-accent-fg:#fff;--jb-connect-radius:16px;--jb-connect-tile:44px;--jb-connect-font:inherit;--jb-connect-pad:32px;--jb-connect-backdrop:rgba(0,0,0,.45);--jb-connect-width:440px;\n box-sizing:border-box;width:min(var(--jb-connect-width),calc(100vw - 24px));max-height:calc(100dvh - 32px);padding:var(--jb-connect-pad);margin:auto;border:1px solid var(--jb-connect-line);border-radius:var(--jb-connect-radius);background:var(--jb-connect-bg);color:var(--jb-connect-fg);font:16px/1.4 var(--jb-connect-font);overscroll-behavior:contain}\n.jb-connect::backdrop{background:var(--jb-connect-backdrop)}\n.jb-connect *{box-sizing:border-box}\n.jb-connect h2{margin:0 0 20px;font-size:28px;font-weight:600;line-height:1.1}\n.jb-connect button{font:inherit;color:inherit;cursor:pointer}\n.jb-connect button:disabled{cursor:default;opacity:.55}\n.jb-connect .jb-connect-primary{display:block;width:100%;min-height:50px;padding:14px 20px;border:0;border-radius:calc(var(--jb-connect-radius) / 2);background:var(--jb-connect-accent);color:var(--jb-connect-accent-fg);font-weight:600}\n.jb-connect .jb-connect-powered{margin:8px 0 0;font-size:13px;color:var(--jb-connect-muted);text-align:center}\n.jb-connect .jb-connect-frame{display:block;width:calc(100% + 2 * var(--jb-connect-pad));height:14rem;margin:0 calc(-1 * var(--jb-connect-pad));border:1px solid var(--jb-connect-line);border-width:1px 0;background:var(--jb-connect-bg);transition:height .2s ease}\n@media (prefers-reduced-motion:reduce){.jb-connect .jb-connect-frame{transition:none}}\n.jb-connect .jb-connect-divider{margin:24px 0 10px;font-size:13px;color:var(--jb-connect-muted)}\n.jb-connect .jb-connect-tiles{display:flex;gap:10px;min-height:var(--jb-connect-tile);padding:4px;margin:-4px;overflow-x:auto}\n.jb-connect .jb-connect-tile{display:grid;place-items:center;flex:0 0 var(--jb-connect-tile);width:var(--jb-connect-tile);height:var(--jb-connect-tile);padding:8px;border:1px solid var(--jb-connect-line);border-radius:9px;background:var(--jb-connect-bg)}\n.jb-connect .jb-connect-tile img,.jb-connect .jb-connect-tile svg{width:22px;height:22px;object-fit:contain}\n.jb-connect .jb-connect-tile span{font-size:12px;font-weight:600;line-height:1}\n.jb-connect .jb-connect-status,.jb-connect .jb-connect-error{margin:16px 0 0;font-size:13px}\n.jb-connect .jb-connect-status{color:var(--jb-connect-muted)}\n.jb-connect .jb-connect-error{padding:12px 15px;border-left:3px solid #c8755e;background:#fae9e4;color:#873b2e;overflow-wrap:anywhere}\n.jb-connect .jb-connect-handoff{margin-top:16px}\n.jb-connect .jb-connect-footer{display:flex;align-items:baseline;justify-content:flex-end;gap:16px;margin-top:20px}\n.jb-connect .jb-connect-text{min-height:auto;padding:4px 0;margin:-4px 0;border:0;background:none;font-size:14px;text-decoration:underline;text-underline-offset:3px}\n@media (max-width:540px){.jb-connect{--jb-connect-pad:20px;padding:24px var(--jb-connect-pad)}.jb-connect h2{font-size:24px}}\n";
29
+
30
+ /** Names the prompt the person is about to see; "passkey" is the fallback when the platform is unknown. */
31
+ declare function passkeyLabel(userAgent: string): string;
32
+
33
+ export { JBConnectModal, connectModalCss, passkeyLabel };
34
+ export type { JBConnectModalProps };