@stacknet/keyutils 0.4.7 → 0.5.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,102 @@ 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 account. One method per user, by design — switching
98
+ * methods replaces the previous record. Stripe Connect uses Express accounts
99
+ * driven by the stack's platform credentials; Solana uses a wallet address
100
+ * the user proved control of by signing a server-issued nonce.
101
+ */
102
+ interface PayoutAccount {
103
+ /** Which method is currently configured. `null` when nothing is set up. */
104
+ method: PayoutAccountMethod | null;
105
+ stripe?: {
106
+ /** acct_*. Masked for display. */
107
+ accountIdMasked: string;
108
+ /** Stripe-reported flags from `accounts.retrieve`. */
109
+ chargesEnabled: boolean;
110
+ payoutsEnabled: boolean;
111
+ detailsSubmitted: boolean;
112
+ /** When non-null, the user needs to finish onboarding at this URL. */
113
+ pendingOnboardingUrl?: string | null;
114
+ /** Stack whose Stripe platform credentials are driving this connection. */
115
+ stackId: string;
116
+ updatedAt: string;
117
+ };
118
+ solana?: {
119
+ addressMasked: string;
120
+ /** Full address — safe to expose since the proof of control is on the
121
+ * server (we verified the signature). The client uses this to display. */
122
+ address: string;
123
+ verifiedAt: string;
124
+ };
125
+ }
126
+ interface CryptoNonceResponse {
127
+ nonce: string;
128
+ /** Canonical message the wallet must sign — includes the nonce. */
129
+ message: string;
130
+ expiresAt: string;
131
+ }
132
+ interface StripeConnectStartResponse {
133
+ accountIdMasked: string;
134
+ /** Hosted Stripe AccountLink URL — redirect the user here. */
135
+ onboardingUrl: string;
136
+ }
137
+ /**
138
+ * Per-stack payout configuration, as resolved by stacknet from the stack's
139
+ * config + secrets. Drives the payout-method picker and locks the destination.
140
+ */
141
+ interface StackPayoutCapabilities {
142
+ stackId: string;
143
+ stackName: string;
144
+ stripe: {
145
+ enabled: boolean;
146
+ /** Human-readable reason when disabled (rendered as "disabled by {stackName} stack" UX). */
147
+ disabledReason?: string;
148
+ /** Masked / display-only descriptor — never the raw account id. */
149
+ destinationLabel?: string;
150
+ };
151
+ usdc: {
152
+ enabled: boolean;
153
+ disabledReason?: string;
154
+ /** Resolved on-chain payout address from the stack config. */
155
+ defaultDestination: string;
156
+ };
157
+ }
158
+ type FilingTrackerStep = 'sent' | 'routed' | 'confirmed' | 'finalized' | 'filed';
159
+ type FilingTrackerStatus = 'pending' | 'current' | 'done' | 'failed';
160
+ interface FilingTrackerState {
161
+ id: FilingTrackerStep;
162
+ label: string;
163
+ status: FilingTrackerStatus;
164
+ timestampMs: number | null;
165
+ detail: string | null;
166
+ approvalsCount?: number;
167
+ approvalsThreshold?: number;
168
+ /** Optional outbound link (Solscan / Stripe dashboard). */
169
+ externalUrl?: string;
170
+ }
171
+ interface FilingDetail extends FilingResult {
172
+ payoutMethod: PayoutMethod;
173
+ /** Server-supplied; not editable from the client. */
174
+ memo: string;
175
+ /** Server-supplied; not editable from the client. USDC raw units (string to avoid bigint loss). */
176
+ amountUsdc: string;
177
+ tokensUsed: string;
178
+ tracker: FilingTrackerState[];
179
+ proofPda?: string;
180
+ intentHashHex?: string;
181
+ stripePaymentIntentId?: string;
182
+ failureReason?: string;
183
+ /**
184
+ * The Solana wallet recorded as the intent's beneficiary on-chain. For
185
+ * usdc filings this matches `beneficiary`; for stripe filings the
186
+ * `beneficiary` is the off-chain Stripe account id and this is the
187
+ * wallet the resolver uses to look up the credentials mapping.
188
+ */
189
+ onchainBeneficiary?: string;
190
+ }
92
191
  type Platform = 'ios' | 'android' | 'windows' | 'mac' | 'linux';
93
192
  interface PlatformDownload {
94
193
  platform: Platform;
@@ -125,4 +224,4 @@ interface KeyUtilsCallbacks {
125
224
  onFilingError?: (error: Error) => void;
126
225
  }
127
226
 
128
- export type { DirectTransferResult, EligibleKey, FilingHistoryEntry, FilingResult, FilingStatus, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, Platform, PlatformDownload, PricingInfo };
227
+ 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 };
@@ -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,102 @@ 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 account. One method per user, by design — switching
98
+ * methods replaces the previous record. Stripe Connect uses Express accounts
99
+ * driven by the stack's platform credentials; Solana uses a wallet address
100
+ * the user proved control of by signing a server-issued nonce.
101
+ */
102
+ interface PayoutAccount {
103
+ /** Which method is currently configured. `null` when nothing is set up. */
104
+ method: PayoutAccountMethod | null;
105
+ stripe?: {
106
+ /** acct_*. Masked for display. */
107
+ accountIdMasked: string;
108
+ /** Stripe-reported flags from `accounts.retrieve`. */
109
+ chargesEnabled: boolean;
110
+ payoutsEnabled: boolean;
111
+ detailsSubmitted: boolean;
112
+ /** When non-null, the user needs to finish onboarding at this URL. */
113
+ pendingOnboardingUrl?: string | null;
114
+ /** Stack whose Stripe platform credentials are driving this connection. */
115
+ stackId: string;
116
+ updatedAt: string;
117
+ };
118
+ solana?: {
119
+ addressMasked: string;
120
+ /** Full address — safe to expose since the proof of control is on the
121
+ * server (we verified the signature). The client uses this to display. */
122
+ address: string;
123
+ verifiedAt: string;
124
+ };
125
+ }
126
+ interface CryptoNonceResponse {
127
+ nonce: string;
128
+ /** Canonical message the wallet must sign — includes the nonce. */
129
+ message: string;
130
+ expiresAt: string;
131
+ }
132
+ interface StripeConnectStartResponse {
133
+ accountIdMasked: string;
134
+ /** Hosted Stripe AccountLink URL — redirect the user here. */
135
+ onboardingUrl: string;
136
+ }
137
+ /**
138
+ * Per-stack payout configuration, as resolved by stacknet from the stack's
139
+ * config + secrets. Drives the payout-method picker and locks the destination.
140
+ */
141
+ interface StackPayoutCapabilities {
142
+ stackId: string;
143
+ stackName: string;
144
+ stripe: {
145
+ enabled: boolean;
146
+ /** Human-readable reason when disabled (rendered as "disabled by {stackName} stack" UX). */
147
+ disabledReason?: string;
148
+ /** Masked / display-only descriptor — never the raw account id. */
149
+ destinationLabel?: string;
150
+ };
151
+ usdc: {
152
+ enabled: boolean;
153
+ disabledReason?: string;
154
+ /** Resolved on-chain payout address from the stack config. */
155
+ defaultDestination: string;
156
+ };
157
+ }
158
+ type FilingTrackerStep = 'sent' | 'routed' | 'confirmed' | 'finalized' | 'filed';
159
+ type FilingTrackerStatus = 'pending' | 'current' | 'done' | 'failed';
160
+ interface FilingTrackerState {
161
+ id: FilingTrackerStep;
162
+ label: string;
163
+ status: FilingTrackerStatus;
164
+ timestampMs: number | null;
165
+ detail: string | null;
166
+ approvalsCount?: number;
167
+ approvalsThreshold?: number;
168
+ /** Optional outbound link (Solscan / Stripe dashboard). */
169
+ externalUrl?: string;
170
+ }
171
+ interface FilingDetail extends FilingResult {
172
+ payoutMethod: PayoutMethod;
173
+ /** Server-supplied; not editable from the client. */
174
+ memo: string;
175
+ /** Server-supplied; not editable from the client. USDC raw units (string to avoid bigint loss). */
176
+ amountUsdc: string;
177
+ tokensUsed: string;
178
+ tracker: FilingTrackerState[];
179
+ proofPda?: string;
180
+ intentHashHex?: string;
181
+ stripePaymentIntentId?: string;
182
+ failureReason?: string;
183
+ /**
184
+ * The Solana wallet recorded as the intent's beneficiary on-chain. For
185
+ * usdc filings this matches `beneficiary`; for stripe filings the
186
+ * `beneficiary` is the off-chain Stripe account id and this is the
187
+ * wallet the resolver uses to look up the credentials mapping.
188
+ */
189
+ onchainBeneficiary?: string;
190
+ }
92
191
  type Platform = 'ios' | 'android' | 'windows' | 'mac' | 'linux';
93
192
  interface PlatformDownload {
94
193
  platform: Platform;
@@ -125,4 +224,4 @@ interface KeyUtilsCallbacks {
125
224
  onFilingError?: (error: Error) => void;
126
225
  }
127
226
 
128
- export type { DirectTransferResult, EligibleKey, FilingHistoryEntry, FilingResult, FilingStatus, KeyListing, KeyUtilsCallbacks, KeyUtilsConfig, KeyWidgetTab, LedgerEntry, ListingResult, MintResult, NodeKeyInfo, PaymentMethod, Platform, PlatformDownload, PricingInfo };
227
+ 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 };
@@ -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.7",
3
+ "version": "0.5.0",
4
4
  "description": "Reusable components for buying and managing StackNet node keys",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",