@stacknet/keyutils 0.4.8 → 0.6.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.
@@ -72,14 +72,17 @@ interface DirectTransferResult {
72
72
  transferredAt: number;
73
73
  }
74
74
  type FilingStatus = 'submitted' | 'validated' | 'paid' | 'failed';
75
+ type PayoutMethod = 'usdc' | 'stripe';
75
76
  interface FilingResult {
76
77
  keyId: string;
78
+ filingId: string;
77
79
  fileHash: string;
78
80
  txSignature?: string;
79
81
  paperAmount: number;
80
82
  status: FilingStatus;
81
83
  beneficiary: string;
82
84
  filedAt: string;
85
+ payoutMethod?: PayoutMethod;
83
86
  }
84
87
  interface FilingHistoryEntry extends FilingResult {
85
88
  proofPda?: string;
@@ -89,6 +92,124 @@ interface FilingHistoryEntry extends FilingResult {
89
92
  type EligibleKey = NodeKeyInfo & {
90
93
  paperWork: string;
91
94
  };
95
+ type PayoutAccountMethod = 'stripe' | 'solana';
96
+ /**
97
+ * The user's saved payout config in the context of one stack.
98
+ *
99
+ * Stripe Connect is per-(user, stack) — each stack runs its own platform
100
+ * under its own secret, so a given user has independent connected accounts
101
+ * across stacks. The `stripe` field reflects the connection for the SCOPE
102
+ * stack only; query other stacks via `useUserStripeStacks()` or by passing
103
+ * a different `stackId` to `usePayoutAccount`.
104
+ *
105
+ * The Solana wallet is global (one verified address services every stack).
106
+ *
107
+ * `method` records the user's preferred rail. The active filing flow
108
+ * verifies the chosen rail is configured for the key's originating stack
109
+ * and falls back to a clear error if not.
110
+ */
111
+ interface PayoutAccount {
112
+ /** Which method is currently configured. `null` when nothing is set up. */
113
+ method: PayoutAccountMethod | null;
114
+ stripe?: {
115
+ /** acct_*. Masked for display. */
116
+ accountIdMasked: string;
117
+ /** Stripe-reported flags from `accounts.retrieve`. */
118
+ chargesEnabled: boolean;
119
+ payoutsEnabled: boolean;
120
+ detailsSubmitted: boolean;
121
+ /** When non-null, the user needs to finish onboarding at this URL. */
122
+ pendingOnboardingUrl?: string | null;
123
+ /** Stack whose Stripe platform credentials are driving this connection. */
124
+ stackId: string;
125
+ updatedAt: string;
126
+ };
127
+ solana?: {
128
+ addressMasked: string;
129
+ /** Full address — safe to expose since the proof of control is on the
130
+ * server (we verified the signature). The client uses this to display. */
131
+ address: string;
132
+ verifiedAt: string;
133
+ };
134
+ }
135
+ /**
136
+ * One stack the user has a Stripe Connect account on. Returned by
137
+ * `useUserStripeStacks()` for the multi-stack settings view.
138
+ */
139
+ interface StripeStackPayout {
140
+ stackId: string;
141
+ accountIdMasked: string;
142
+ chargesEnabled: boolean;
143
+ payoutsEnabled: boolean;
144
+ detailsSubmitted: boolean;
145
+ pendingOnboardingUrl?: string | null;
146
+ updatedAt: string;
147
+ }
148
+ interface CryptoNonceResponse {
149
+ nonce: string;
150
+ /** Canonical message the wallet must sign — includes the nonce. */
151
+ message: string;
152
+ expiresAt: string;
153
+ }
154
+ interface StripeConnectStartResponse {
155
+ accountIdMasked: string;
156
+ /** Hosted Stripe AccountLink URL — redirect the user here. */
157
+ onboardingUrl: string;
158
+ }
159
+ /**
160
+ * Per-stack payout configuration, as resolved by stacknet from the stack's
161
+ * config + secrets. Drives the payout-method picker and locks the destination.
162
+ */
163
+ interface StackPayoutCapabilities {
164
+ stackId: string;
165
+ stackName: string;
166
+ stripe: {
167
+ enabled: boolean;
168
+ /** Human-readable reason when disabled (rendered as "disabled by {stackName} stack" UX). */
169
+ disabledReason?: string;
170
+ /** Masked / display-only descriptor — never the raw account id. */
171
+ destinationLabel?: string;
172
+ };
173
+ usdc: {
174
+ enabled: boolean;
175
+ disabledReason?: string;
176
+ /** Resolved on-chain payout address from the stack config. */
177
+ defaultDestination: string;
178
+ };
179
+ }
180
+ type FilingTrackerStep = 'sent' | 'routed' | 'confirmed' | 'finalized' | 'filed';
181
+ type FilingTrackerStatus = 'pending' | 'current' | 'done' | 'failed';
182
+ interface FilingTrackerState {
183
+ id: FilingTrackerStep;
184
+ label: string;
185
+ status: FilingTrackerStatus;
186
+ timestampMs: number | null;
187
+ detail: string | null;
188
+ approvalsCount?: number;
189
+ approvalsThreshold?: number;
190
+ /** Optional outbound link (Solscan / Stripe dashboard). */
191
+ externalUrl?: string;
192
+ }
193
+ interface FilingDetail extends FilingResult {
194
+ payoutMethod: PayoutMethod;
195
+ /** Server-supplied; not editable from the client. */
196
+ memo: string;
197
+ /** Server-supplied; not editable from the client. USDC raw units (string to avoid bigint loss). */
198
+ amountUsdc: string;
199
+ tokensUsed: string;
200
+ tracker: FilingTrackerState[];
201
+ proofPda?: string;
202
+ intentHashHex?: string;
203
+ stripePaymentIntentId?: string;
204
+ failureReason?: string;
205
+ /**
206
+ * The Solana wallet recorded as the intent's beneficiary on-chain. For
207
+ * usdc filings this matches `beneficiary`; for stripe filings the
208
+ * `beneficiary` is the off-chain Stripe account id and this is the
209
+ * wallet the resolver uses to look up the credentials mapping.
210
+ */
211
+ onchainBeneficiary?: string;
212
+ }
92
213
  type Platform = 'ios' | 'android' | 'windows' | 'mac' | 'linux';
93
214
  interface PlatformDownload {
94
215
  platform: Platform;
@@ -125,4 +246,4 @@ interface KeyUtilsCallbacks {
125
246
  onFilingError?: (error: Error) => void;
126
247
  }
127
248
 
128
- export type { DirectTransferResult, EligibleKey, FilingHistoryEntry, FilingResult, FilingStatus, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, Platform, PlatformDownload, PricingInfo };
249
+ export type { CryptoNonceResponse, DirectTransferResult, EligibleKey, FilingDetail, FilingHistoryEntry, FilingResult, FilingStatus, FilingTrackerState, FilingTrackerStatus, FilingTrackerStep, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, PayoutAccount, PayoutAccountMethod, PayoutMethod, Platform, PlatformDownload, PricingInfo, StackPayoutCapabilities, StripeConnectStartResponse, StripeStackPayout };
@@ -72,14 +72,17 @@ interface DirectTransferResult {
72
72
  transferredAt: number;
73
73
  }
74
74
  type FilingStatus = 'submitted' | 'validated' | 'paid' | 'failed';
75
+ type PayoutMethod = 'usdc' | 'stripe';
75
76
  interface FilingResult {
76
77
  keyId: string;
78
+ filingId: string;
77
79
  fileHash: string;
78
80
  txSignature?: string;
79
81
  paperAmount: number;
80
82
  status: FilingStatus;
81
83
  beneficiary: string;
82
84
  filedAt: string;
85
+ payoutMethod?: PayoutMethod;
83
86
  }
84
87
  interface FilingHistoryEntry extends FilingResult {
85
88
  proofPda?: string;
@@ -89,6 +92,124 @@ interface FilingHistoryEntry extends FilingResult {
89
92
  type EligibleKey = NodeKeyInfo & {
90
93
  paperWork: string;
91
94
  };
95
+ type PayoutAccountMethod = 'stripe' | 'solana';
96
+ /**
97
+ * The user's saved payout config in the context of one stack.
98
+ *
99
+ * Stripe Connect is per-(user, stack) — each stack runs its own platform
100
+ * under its own secret, so a given user has independent connected accounts
101
+ * across stacks. The `stripe` field reflects the connection for the SCOPE
102
+ * stack only; query other stacks via `useUserStripeStacks()` or by passing
103
+ * a different `stackId` to `usePayoutAccount`.
104
+ *
105
+ * The Solana wallet is global (one verified address services every stack).
106
+ *
107
+ * `method` records the user's preferred rail. The active filing flow
108
+ * verifies the chosen rail is configured for the key's originating stack
109
+ * and falls back to a clear error if not.
110
+ */
111
+ interface PayoutAccount {
112
+ /** Which method is currently configured. `null` when nothing is set up. */
113
+ method: PayoutAccountMethod | null;
114
+ stripe?: {
115
+ /** acct_*. Masked for display. */
116
+ accountIdMasked: string;
117
+ /** Stripe-reported flags from `accounts.retrieve`. */
118
+ chargesEnabled: boolean;
119
+ payoutsEnabled: boolean;
120
+ detailsSubmitted: boolean;
121
+ /** When non-null, the user needs to finish onboarding at this URL. */
122
+ pendingOnboardingUrl?: string | null;
123
+ /** Stack whose Stripe platform credentials are driving this connection. */
124
+ stackId: string;
125
+ updatedAt: string;
126
+ };
127
+ solana?: {
128
+ addressMasked: string;
129
+ /** Full address — safe to expose since the proof of control is on the
130
+ * server (we verified the signature). The client uses this to display. */
131
+ address: string;
132
+ verifiedAt: string;
133
+ };
134
+ }
135
+ /**
136
+ * One stack the user has a Stripe Connect account on. Returned by
137
+ * `useUserStripeStacks()` for the multi-stack settings view.
138
+ */
139
+ interface StripeStackPayout {
140
+ stackId: string;
141
+ accountIdMasked: string;
142
+ chargesEnabled: boolean;
143
+ payoutsEnabled: boolean;
144
+ detailsSubmitted: boolean;
145
+ pendingOnboardingUrl?: string | null;
146
+ updatedAt: string;
147
+ }
148
+ interface CryptoNonceResponse {
149
+ nonce: string;
150
+ /** Canonical message the wallet must sign — includes the nonce. */
151
+ message: string;
152
+ expiresAt: string;
153
+ }
154
+ interface StripeConnectStartResponse {
155
+ accountIdMasked: string;
156
+ /** Hosted Stripe AccountLink URL — redirect the user here. */
157
+ onboardingUrl: string;
158
+ }
159
+ /**
160
+ * Per-stack payout configuration, as resolved by stacknet from the stack's
161
+ * config + secrets. Drives the payout-method picker and locks the destination.
162
+ */
163
+ interface StackPayoutCapabilities {
164
+ stackId: string;
165
+ stackName: string;
166
+ stripe: {
167
+ enabled: boolean;
168
+ /** Human-readable reason when disabled (rendered as "disabled by {stackName} stack" UX). */
169
+ disabledReason?: string;
170
+ /** Masked / display-only descriptor — never the raw account id. */
171
+ destinationLabel?: string;
172
+ };
173
+ usdc: {
174
+ enabled: boolean;
175
+ disabledReason?: string;
176
+ /** Resolved on-chain payout address from the stack config. */
177
+ defaultDestination: string;
178
+ };
179
+ }
180
+ type FilingTrackerStep = 'sent' | 'routed' | 'confirmed' | 'finalized' | 'filed';
181
+ type FilingTrackerStatus = 'pending' | 'current' | 'done' | 'failed';
182
+ interface FilingTrackerState {
183
+ id: FilingTrackerStep;
184
+ label: string;
185
+ status: FilingTrackerStatus;
186
+ timestampMs: number | null;
187
+ detail: string | null;
188
+ approvalsCount?: number;
189
+ approvalsThreshold?: number;
190
+ /** Optional outbound link (Solscan / Stripe dashboard). */
191
+ externalUrl?: string;
192
+ }
193
+ interface FilingDetail extends FilingResult {
194
+ payoutMethod: PayoutMethod;
195
+ /** Server-supplied; not editable from the client. */
196
+ memo: string;
197
+ /** Server-supplied; not editable from the client. USDC raw units (string to avoid bigint loss). */
198
+ amountUsdc: string;
199
+ tokensUsed: string;
200
+ tracker: FilingTrackerState[];
201
+ proofPda?: string;
202
+ intentHashHex?: string;
203
+ stripePaymentIntentId?: string;
204
+ failureReason?: string;
205
+ /**
206
+ * The Solana wallet recorded as the intent's beneficiary on-chain. For
207
+ * usdc filings this matches `beneficiary`; for stripe filings the
208
+ * `beneficiary` is the off-chain Stripe account id and this is the
209
+ * wallet the resolver uses to look up the credentials mapping.
210
+ */
211
+ onchainBeneficiary?: string;
212
+ }
92
213
  type Platform = 'ios' | 'android' | 'windows' | 'mac' | 'linux';
93
214
  interface PlatformDownload {
94
215
  platform: Platform;
@@ -125,4 +246,4 @@ interface KeyUtilsCallbacks {
125
246
  onFilingError?: (error: Error) => void;
126
247
  }
127
248
 
128
- export type { DirectTransferResult, EligibleKey, FilingHistoryEntry, FilingResult, FilingStatus, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, Platform, PlatformDownload, PricingInfo };
249
+ export type { CryptoNonceResponse, DirectTransferResult, EligibleKey, FilingDetail, FilingHistoryEntry, FilingResult, FilingStatus, FilingTrackerState, FilingTrackerStatus, FilingTrackerStep, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, PayoutAccount, PayoutAccountMethod, PayoutMethod, Platform, PlatformDownload, PricingInfo, StackPayoutCapabilities, StripeConnectStartResponse, StripeStackPayout };
@@ -1 +1 @@
1
- 'use strict';var clsx=require('clsx'),tailwindMerge=require('tailwind-merge');function i(){if(typeof document>"u")return {};let r=document.cookie.match(/(?:^|;\s*)__csrf=([^;]*)/);return r?{"x-csrf-token":r[1]}:{}}var s=500;function o(r,e){if(typeof r!="string"||r.length===0)return e;let t=r.replace(/[\u0000-\u001F\u007F]/g," ");return t.length>500?t.slice(0,500)+"\u2026":t}async function u(r,e){try{let t=await r.json();if(t&&typeof t=="object"&&"error"in t)return o(t.error,e)}catch{}return e}function f(r,e="#"){if(!r||typeof r!="string")return e;let t=r.trim();if(t===""||t==="#")return e;if(t.startsWith("/")||t.startsWith("./")||t.startsWith("../"))return t;try{let n=new URL(t);if(n.protocol==="http:"||n.protocol==="https:")return n.toString()}catch{}return e}function _(...r){return tailwindMerge.twMerge(clsx.clsx(r))}function x(r){return r==null?"0":r>=1e9?`${(r/1e9).toFixed(1)}B`:r>=1e6?`${(r/1e6).toFixed(1)}M`:r>=1e3?`${(r/1e3).toFixed(1)}K`:r.toLocaleString()}function h(r,e){return e<=0?0:Math.min(100,Math.round(r/e*100))}function y(r){return r>=70?"bg-green-500":r>=30?"bg-yellow-500":"bg-red-500"}function R(r){if(r==null||r==="")return "";let e=typeof r=="string"&&/^\d+$/.test(r)?Number(r):r,t=new Date(e);return isNaN(t.getTime())?"":t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"})}function E(){if(typeof navigator>"u")return "mac";let r=navigator.userAgent.toLowerCase();return /iphone|ipad|ipod/.test(r)?"ios":/android/.test(r)?"android":/win/.test(r)?"windows":/linux/.test(r)?"linux":"mac"}exports.MAX_ERROR_LEN=s;exports.calculateBalancePercentage=h;exports.clampErrorMessage=o;exports.cn=_;exports.csrfHeaders=i;exports.detectPlatform=E;exports.formatDate=R;exports.formatTokens=x;exports.getBalanceColor=y;exports.readErrorMessage=u;exports.safeUrl=f;
1
+ 'use strict';var clsx=require('clsx'),tailwindMerge=require('tailwind-merge');var d=false;function _(){if(typeof document>"u")return {};let e=document.cookie.match(/(?:^|;\s*)__csrf=([^;]*)/);if(!e)return d||(d=true,console.warn("[keyutils] __csrf cookie not found; mutations will be sent without a CSRF token")),{};let n=e[1];try{n=decodeURIComponent(n);}catch{}return {"x-csrf-token":n}}var b=500;function g(e,n){if(typeof e!="string"||e.length===0)return n;let t=e.replace(/[\u0000-\u001F\u007F]/g," ");return t.length>500?t.slice(0,500)+"\u2026":t}async function R(e,n){try{let t=await e.json();if(t&&typeof t=="object"&&"error"in t)return g(t.error,n)}catch{}return n}function F(e,n="#"){if(!e||typeof e!="string")return n;let t=e.trim();if(t===""||t==="#")return n;if(t.startsWith("/")||t.startsWith("./")||t.startsWith("../"))return t;try{let o=new URL(t);if(o.protocol==="http:"||o.protocol==="https:")return o.toString()}catch{}return n}var c={sent:"sent",routed:"routed",confirmed:"confirmed",finalized:"finalized",filed:"filed"},u={sent:{pending:"sent",progressing:"sending",done:"sent"},routed:{pending:"routed",progressing:"routing",done:"routed"},confirmed:{pending:"confirmed",progressing:"confirming",done:"confirmed"},finalized:{pending:"finalized",progressing:"finalizing",done:"finalized"},filed:{pending:"filed",progressing:"filing",done:"filed"}},k=["sent","routed","confirmed","finalized","filed"];function T(e){let n=new Map;(e??[]).forEach(r=>n.set(r.id,r));let t=k.map(r=>{let i=n.get(r);return i?{...i,label:i.label||c[r]}:{id:r,label:c[r],status:"pending",timestampMs:null,detail:null}}),o=t.reduce((r,i,a)=>i.status==="done"||i.status==="failed"?a:r,-1);if(o>0)for(let r=0;r<o;r++)t[r].status!=="failed"&&(t[r]={...t[r],status:"done"});if(!t.some(r=>r.status==="failed")){let r=t.findIndex(i=>i.status!=="done");if(r>=0){let i=t[r];i.status!=="current"&&(t[r]={...i,status:"current"});}}return t}function p(e){return e.find(n=>n.status==="current")??null}function m(e){return e.length>0&&e.every(n=>n.status==="done")}function x(e){return e.some(n=>n.status==="failed")}function E(e,n){if(!n)return {text:"loading",playing:true,stepId:"sent"};if(x(e))return {text:"failed",playing:false,stepId:"filed"};if(m(e))return {text:u.filed.done,playing:false,stepId:"filed"};let o=p(e)?.id??"sent";return {text:u[o].progressing,playing:true,stepId:o}}function v(...e){return tailwindMerge.twMerge(clsx.clsx(e))}function P(e){return e==null?"0":e>=1e9?`${(e/1e9).toFixed(1)}B`:e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:e.toLocaleString()}function z(e,n){return n<=0?0:Math.min(100,Math.round(e/n*100))}function U(e){return e>=70?"bg-green-500":e>=30?"bg-yellow-500":"bg-red-500"}function K(e,n={}){let t=n.withSymbol!==false,o=Math.max(0,Math.min(6,n.maxDecimals??2));if(e==null||e==="")return t?"$0.00":"0.00";let s;try{if(typeof e=="bigint")s=e;else if(typeof e=="number")s=BigInt(Math.trunc(e));else {let l=String(e).trim().replace(/^\+/,"");if(/^-?\d+$/.test(l))s=BigInt(l);else {let f=Number(l);if(!Number.isFinite(f))return t?"$0.00":"0.00";s=BigInt(Math.trunc(f));}}}catch{return t?"$0.00":"0.00"}let r=s<0n,i=r?-s:s,a=i/1000000n,S=(i%1000000n).toString().padStart(6,"0").slice(0,o),h=a.toLocaleString("en-US"),y=o>0?`.${S}`:"";return `${r?"-":""}${t?"$":""}${h}${y}`}function O(e){if(e==null||e==="")return "";let n=typeof e=="string"&&/^\d+$/.test(e)?Number(e):e,t=new Date(n);return isNaN(t.getTime())?"":t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"})}function H(){if(typeof navigator>"u")return "mac";let e=navigator.userAgent.toLowerCase();return /iphone|ipad|ipod/.test(e)?"ios":/android/.test(e)?"android":/win/.test(e)?"windows":/linux/.test(e)?"linux":"mac"}exports.MAX_ERROR_LEN=b;exports.TRACKER_LABELS=c;exports.TRACKER_VERBS=u;exports.calculateBalancePercentage=z;exports.clampErrorMessage=g;exports.cn=v;exports.csrfHeaders=_;exports.currentState=p;exports.deriveTrackerStates=T;exports.detectPlatform=H;exports.formatDate=O;exports.formatPaperUsd=K;exports.formatTokens=P;exports.getBalanceColor=U;exports.hasFailed=x;exports.isAllDone=m;exports.pickHeadline=E;exports.readErrorMessage=R;exports.safeUrl=F;
@@ -1,7 +1,20 @@
1
1
  import { ClassValue } from 'clsx';
2
- import { Platform } from '../types/index.cjs';
2
+ import { FilingTrackerStep, FilingTrackerState, Platform } from '../types/index.cjs';
3
3
 
4
- /** Read __csrf cookie and return as x-csrf-token header for mutations */
4
+ /**
5
+ * Read the `__csrf` cookie and return it as the `x-csrf-token` header for
6
+ * mutating requests.
7
+ *
8
+ * Returns `{}` outside the browser (so native callers can use the same
9
+ * helper as a no-op). In the browser, returns `{}` when the cookie is
10
+ * missing — the server is the trust boundary and must reject — but logs
11
+ * a one-time warning so the missing token surfaces in the dev console
12
+ * rather than failing silently.
13
+ *
14
+ * The cookie value is `decodeURIComponent`'d to match the encoding the
15
+ * server applies when setting it; without this, tokens containing `+`,
16
+ * `=`, or `%` would desync between client and server.
17
+ */
5
18
  declare function csrfHeaders(): Record<string, string>;
6
19
 
7
20
  /**
@@ -30,11 +43,53 @@ declare function readErrorMessage(res: Response, fallback: string): Promise<stri
30
43
  */
31
44
  declare function safeUrl(url: string | undefined | null, fallback?: string): string;
32
45
 
46
+ declare const TRACKER_LABELS: Record<FilingTrackerStep, string>;
47
+ /**
48
+ * Verb tenses per state. `pending` is the neutral noun used when the row
49
+ * hasn't been entered yet; `progressing` is the gerund shown while the step
50
+ * is in-flight (terminal typing animation); `done` is the past-tense label
51
+ * used once the step completes. All lowercase by design.
52
+ */
53
+ declare const TRACKER_VERBS: Record<FilingTrackerStep, {
54
+ pending: string;
55
+ progressing: string;
56
+ done: string;
57
+ }>;
58
+ /** Pure normalization of a server-shaped tracker. Cascade-forward + current pointer. */
59
+ declare function deriveTrackerStates(raw: FilingTrackerState[] | null | undefined): FilingTrackerState[];
60
+ declare function currentState(states: FilingTrackerState[]): FilingTrackerState | null;
61
+ declare function isAllDone(states: FilingTrackerState[]): boolean;
62
+ declare function hasFailed(states: FilingTrackerState[]): boolean;
63
+ /** Pick the headline word for the big typing-text animation. */
64
+ declare function pickHeadline(states: FilingTrackerState[], loaded: boolean): {
65
+ text: string;
66
+ playing: boolean;
67
+ stepId: FilingTrackerStep;
68
+ };
69
+
33
70
  declare function cn(...inputs: ClassValue[]): string;
34
71
  declare function formatTokens(amount: number | undefined | null): string;
35
72
  declare function calculateBalancePercentage(current: number, max: number): number;
36
73
  declare function getBalanceColor(percentage: number): string;
74
+ /**
75
+ * Format a paperwork USD amount for display.
76
+ *
77
+ * Stacknet stores `paper_work` as a 6-decimal fixed-point integer string —
78
+ * so `'1500000'` means $1.50. Pass any of (string | number | bigint | null
79
+ * | undefined) and get back a plain dollar string. By default, returns
80
+ * with 2 decimals + `$` prefix; pass `{ withSymbol: false }` for a bare
81
+ * decimal number, or `{ maxDecimals: 6 }` to surface sub-cent precision.
82
+ *
83
+ * formatPaperUsd('0') → '$0.00'
84
+ * formatPaperUsd('1500000') → '$1.50'
85
+ * formatPaperUsd('1234567') → '$1.23'
86
+ * formatPaperUsd('1234567', { maxDecimals: 6 }) → '$1.234567'
87
+ */
88
+ declare function formatPaperUsd(raw: string | number | bigint | null | undefined, opts?: {
89
+ withSymbol?: boolean;
90
+ maxDecimals?: number;
91
+ }): string;
37
92
  declare function formatDate(dateString: string | number | null | undefined): string;
38
93
  declare function detectPlatform(): Platform;
39
94
 
40
- export { MAX_ERROR_LEN, calculateBalancePercentage, clampErrorMessage, cn, csrfHeaders, detectPlatform, formatDate, formatTokens, getBalanceColor, readErrorMessage, safeUrl };
95
+ export { MAX_ERROR_LEN, TRACKER_LABELS, TRACKER_VERBS, calculateBalancePercentage, clampErrorMessage, cn, csrfHeaders, currentState, deriveTrackerStates, detectPlatform, formatDate, formatPaperUsd, formatTokens, getBalanceColor, hasFailed, isAllDone, pickHeadline, readErrorMessage, safeUrl };
@@ -1,7 +1,20 @@
1
1
  import { ClassValue } from 'clsx';
2
- import { Platform } from '../types/index.js';
2
+ import { FilingTrackerStep, FilingTrackerState, Platform } from '../types/index.js';
3
3
 
4
- /** Read __csrf cookie and return as x-csrf-token header for mutations */
4
+ /**
5
+ * Read the `__csrf` cookie and return it as the `x-csrf-token` header for
6
+ * mutating requests.
7
+ *
8
+ * Returns `{}` outside the browser (so native callers can use the same
9
+ * helper as a no-op). In the browser, returns `{}` when the cookie is
10
+ * missing — the server is the trust boundary and must reject — but logs
11
+ * a one-time warning so the missing token surfaces in the dev console
12
+ * rather than failing silently.
13
+ *
14
+ * The cookie value is `decodeURIComponent`'d to match the encoding the
15
+ * server applies when setting it; without this, tokens containing `+`,
16
+ * `=`, or `%` would desync between client and server.
17
+ */
5
18
  declare function csrfHeaders(): Record<string, string>;
6
19
 
7
20
  /**
@@ -30,11 +43,53 @@ declare function readErrorMessage(res: Response, fallback: string): Promise<stri
30
43
  */
31
44
  declare function safeUrl(url: string | undefined | null, fallback?: string): string;
32
45
 
46
+ declare const TRACKER_LABELS: Record<FilingTrackerStep, string>;
47
+ /**
48
+ * Verb tenses per state. `pending` is the neutral noun used when the row
49
+ * hasn't been entered yet; `progressing` is the gerund shown while the step
50
+ * is in-flight (terminal typing animation); `done` is the past-tense label
51
+ * used once the step completes. All lowercase by design.
52
+ */
53
+ declare const TRACKER_VERBS: Record<FilingTrackerStep, {
54
+ pending: string;
55
+ progressing: string;
56
+ done: string;
57
+ }>;
58
+ /** Pure normalization of a server-shaped tracker. Cascade-forward + current pointer. */
59
+ declare function deriveTrackerStates(raw: FilingTrackerState[] | null | undefined): FilingTrackerState[];
60
+ declare function currentState(states: FilingTrackerState[]): FilingTrackerState | null;
61
+ declare function isAllDone(states: FilingTrackerState[]): boolean;
62
+ declare function hasFailed(states: FilingTrackerState[]): boolean;
63
+ /** Pick the headline word for the big typing-text animation. */
64
+ declare function pickHeadline(states: FilingTrackerState[], loaded: boolean): {
65
+ text: string;
66
+ playing: boolean;
67
+ stepId: FilingTrackerStep;
68
+ };
69
+
33
70
  declare function cn(...inputs: ClassValue[]): string;
34
71
  declare function formatTokens(amount: number | undefined | null): string;
35
72
  declare function calculateBalancePercentage(current: number, max: number): number;
36
73
  declare function getBalanceColor(percentage: number): string;
74
+ /**
75
+ * Format a paperwork USD amount for display.
76
+ *
77
+ * Stacknet stores `paper_work` as a 6-decimal fixed-point integer string —
78
+ * so `'1500000'` means $1.50. Pass any of (string | number | bigint | null
79
+ * | undefined) and get back a plain dollar string. By default, returns
80
+ * with 2 decimals + `$` prefix; pass `{ withSymbol: false }` for a bare
81
+ * decimal number, or `{ maxDecimals: 6 }` to surface sub-cent precision.
82
+ *
83
+ * formatPaperUsd('0') → '$0.00'
84
+ * formatPaperUsd('1500000') → '$1.50'
85
+ * formatPaperUsd('1234567') → '$1.23'
86
+ * formatPaperUsd('1234567', { maxDecimals: 6 }) → '$1.234567'
87
+ */
88
+ declare function formatPaperUsd(raw: string | number | bigint | null | undefined, opts?: {
89
+ withSymbol?: boolean;
90
+ maxDecimals?: number;
91
+ }): string;
37
92
  declare function formatDate(dateString: string | number | null | undefined): string;
38
93
  declare function detectPlatform(): Platform;
39
94
 
40
- export { MAX_ERROR_LEN, calculateBalancePercentage, clampErrorMessage, cn, csrfHeaders, detectPlatform, formatDate, formatTokens, getBalanceColor, readErrorMessage, safeUrl };
95
+ export { MAX_ERROR_LEN, TRACKER_LABELS, TRACKER_VERBS, calculateBalancePercentage, clampErrorMessage, cn, csrfHeaders, currentState, deriveTrackerStates, detectPlatform, formatDate, formatPaperUsd, formatTokens, getBalanceColor, hasFailed, isAllDone, pickHeadline, readErrorMessage, safeUrl };
@@ -1 +1 @@
1
- import {clsx}from'clsx';import {twMerge}from'tailwind-merge';function i(){if(typeof document>"u")return {};let r=document.cookie.match(/(?:^|;\s*)__csrf=([^;]*)/);return r?{"x-csrf-token":r[1]}:{}}var s=500;function o(r,e){if(typeof r!="string"||r.length===0)return e;let t=r.replace(/[\u0000-\u001F\u007F]/g," ");return t.length>500?t.slice(0,500)+"\u2026":t}async function u(r,e){try{let t=await r.json();if(t&&typeof t=="object"&&"error"in t)return o(t.error,e)}catch{}return e}function f(r,e="#"){if(!r||typeof r!="string")return e;let t=r.trim();if(t===""||t==="#")return e;if(t.startsWith("/")||t.startsWith("./")||t.startsWith("../"))return t;try{let n=new URL(t);if(n.protocol==="http:"||n.protocol==="https:")return n.toString()}catch{}return e}function _(...r){return twMerge(clsx(r))}function x(r){return r==null?"0":r>=1e9?`${(r/1e9).toFixed(1)}B`:r>=1e6?`${(r/1e6).toFixed(1)}M`:r>=1e3?`${(r/1e3).toFixed(1)}K`:r.toLocaleString()}function h(r,e){return e<=0?0:Math.min(100,Math.round(r/e*100))}function y(r){return r>=70?"bg-green-500":r>=30?"bg-yellow-500":"bg-red-500"}function R(r){if(r==null||r==="")return "";let e=typeof r=="string"&&/^\d+$/.test(r)?Number(r):r,t=new Date(e);return isNaN(t.getTime())?"":t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"})}function E(){if(typeof navigator>"u")return "mac";let r=navigator.userAgent.toLowerCase();return /iphone|ipad|ipod/.test(r)?"ios":/android/.test(r)?"android":/win/.test(r)?"windows":/linux/.test(r)?"linux":"mac"}export{s as MAX_ERROR_LEN,h as calculateBalancePercentage,o as clampErrorMessage,_ as cn,i as csrfHeaders,E as detectPlatform,R as formatDate,x as formatTokens,y as getBalanceColor,u as readErrorMessage,f as safeUrl};
1
+ import {clsx}from'clsx';import {twMerge}from'tailwind-merge';var d=false;function _(){if(typeof document>"u")return {};let e=document.cookie.match(/(?:^|;\s*)__csrf=([^;]*)/);if(!e)return d||(d=true,console.warn("[keyutils] __csrf cookie not found; mutations will be sent without a CSRF token")),{};let n=e[1];try{n=decodeURIComponent(n);}catch{}return {"x-csrf-token":n}}var b=500;function g(e,n){if(typeof e!="string"||e.length===0)return n;let t=e.replace(/[\u0000-\u001F\u007F]/g," ");return t.length>500?t.slice(0,500)+"\u2026":t}async function R(e,n){try{let t=await e.json();if(t&&typeof t=="object"&&"error"in t)return g(t.error,n)}catch{}return n}function F(e,n="#"){if(!e||typeof e!="string")return n;let t=e.trim();if(t===""||t==="#")return n;if(t.startsWith("/")||t.startsWith("./")||t.startsWith("../"))return t;try{let o=new URL(t);if(o.protocol==="http:"||o.protocol==="https:")return o.toString()}catch{}return n}var c={sent:"sent",routed:"routed",confirmed:"confirmed",finalized:"finalized",filed:"filed"},u={sent:{pending:"sent",progressing:"sending",done:"sent"},routed:{pending:"routed",progressing:"routing",done:"routed"},confirmed:{pending:"confirmed",progressing:"confirming",done:"confirmed"},finalized:{pending:"finalized",progressing:"finalizing",done:"finalized"},filed:{pending:"filed",progressing:"filing",done:"filed"}},k=["sent","routed","confirmed","finalized","filed"];function T(e){let n=new Map;(e??[]).forEach(r=>n.set(r.id,r));let t=k.map(r=>{let i=n.get(r);return i?{...i,label:i.label||c[r]}:{id:r,label:c[r],status:"pending",timestampMs:null,detail:null}}),o=t.reduce((r,i,a)=>i.status==="done"||i.status==="failed"?a:r,-1);if(o>0)for(let r=0;r<o;r++)t[r].status!=="failed"&&(t[r]={...t[r],status:"done"});if(!t.some(r=>r.status==="failed")){let r=t.findIndex(i=>i.status!=="done");if(r>=0){let i=t[r];i.status!=="current"&&(t[r]={...i,status:"current"});}}return t}function p(e){return e.find(n=>n.status==="current")??null}function m(e){return e.length>0&&e.every(n=>n.status==="done")}function x(e){return e.some(n=>n.status==="failed")}function E(e,n){if(!n)return {text:"loading",playing:true,stepId:"sent"};if(x(e))return {text:"failed",playing:false,stepId:"filed"};if(m(e))return {text:u.filed.done,playing:false,stepId:"filed"};let o=p(e)?.id??"sent";return {text:u[o].progressing,playing:true,stepId:o}}function v(...e){return twMerge(clsx(e))}function P(e){return e==null?"0":e>=1e9?`${(e/1e9).toFixed(1)}B`:e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}K`:e.toLocaleString()}function z(e,n){return n<=0?0:Math.min(100,Math.round(e/n*100))}function U(e){return e>=70?"bg-green-500":e>=30?"bg-yellow-500":"bg-red-500"}function K(e,n={}){let t=n.withSymbol!==false,o=Math.max(0,Math.min(6,n.maxDecimals??2));if(e==null||e==="")return t?"$0.00":"0.00";let s;try{if(typeof e=="bigint")s=e;else if(typeof e=="number")s=BigInt(Math.trunc(e));else {let l=String(e).trim().replace(/^\+/,"");if(/^-?\d+$/.test(l))s=BigInt(l);else {let f=Number(l);if(!Number.isFinite(f))return t?"$0.00":"0.00";s=BigInt(Math.trunc(f));}}}catch{return t?"$0.00":"0.00"}let r=s<0n,i=r?-s:s,a=i/1000000n,S=(i%1000000n).toString().padStart(6,"0").slice(0,o),h=a.toLocaleString("en-US"),y=o>0?`.${S}`:"";return `${r?"-":""}${t?"$":""}${h}${y}`}function O(e){if(e==null||e==="")return "";let n=typeof e=="string"&&/^\d+$/.test(e)?Number(e):e,t=new Date(n);return isNaN(t.getTime())?"":t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"})}function H(){if(typeof navigator>"u")return "mac";let e=navigator.userAgent.toLowerCase();return /iphone|ipad|ipod/.test(e)?"ios":/android/.test(e)?"android":/win/.test(e)?"windows":/linux/.test(e)?"linux":"mac"}export{b as MAX_ERROR_LEN,c as TRACKER_LABELS,u as TRACKER_VERBS,z as calculateBalancePercentage,g as clampErrorMessage,v as cn,_ as csrfHeaders,p as currentState,T as deriveTrackerStates,H as detectPlatform,O as formatDate,K as formatPaperUsd,P as formatTokens,U as getBalanceColor,x as hasFailed,m as isAllDone,E as pickHeadline,R as readErrorMessage,F as safeUrl};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stacknet/keyutils",
3
- "version": "0.4.8",
3
+ "version": "0.6.0",
4
4
  "description": "Reusable components for buying and managing StackNet node keys",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",