|
| 1 | +// ── Wallet status & transition guards ─── |
| 2 | + |
| 3 | +export const WalletStatus = { |
| 4 | + PROVISIONING: 'PROVISIONING', |
| 5 | + ACTIVE: 'ACTIVE', |
| 6 | + FAILED: 'FAILED', |
| 7 | + EXPORT: 'EXPORT', |
| 8 | + MIGRATED: 'MIGRATED', |
| 9 | + DISABLED: 'DISABLED', |
| 10 | +} as const |
| 11 | + |
| 12 | +export type WalletStatusValue = (typeof WalletStatus)[keyof typeof WalletStatus] |
| 13 | + |
| 14 | +export const WalletNetwork = { |
| 15 | + TESTNET: 'TESTNET', |
| 16 | + MAINNET: 'MAINNET', |
| 17 | +} as const |
| 18 | + |
| 19 | +export type WalletNetworkValue = (typeof WalletNetwork)[keyof typeof WalletNetwork] |
| 20 | + |
| 21 | +export const WalletCustody = { |
| 22 | + MANAGED: 'MANAGED', |
| 23 | + EXTERNAL: 'EXTERNAL', |
| 24 | +} as const |
| 25 | + |
| 26 | +export type WalletCustodyValue = (typeof WalletCustody)[keyof typeof WalletCustody] |
| 27 | + |
| 28 | +/** |
| 29 | + * Allowed status transitions. |
| 30 | + * Key = current status, Value = set of statuses it may transition to. |
| 31 | + */ |
| 32 | +export const WalletTransitions: Readonly<Record<WalletStatusValue, ReadonlySet<WalletStatusValue>>> = { |
| 33 | + [WalletStatus.PROVISIONING]: new Set([WalletStatus.ACTIVE, WalletStatus.FAILED]), |
| 34 | + [WalletStatus.ACTIVE]: new Set([WalletStatus.EXPORT, WalletStatus.MIGRATED, WalletStatus.DISABLED]), |
| 35 | + [WalletStatus.FAILED]: new Set([WalletStatus.PROVISIONING, WalletStatus.DISABLED]), |
| 36 | + [WalletStatus.EXPORT]: new Set([WalletStatus.MIGRATED, WalletStatus.DISABLED]), |
| 37 | + [WalletStatus.MIGRATED]: new Set([WalletStatus.DISABLED]), |
| 38 | + [WalletStatus.DISABLED]: new Set([]), |
| 39 | +} as const |
| 40 | + |
| 41 | +/** |
| 42 | + * Returns true if transitioning from `from` to `to` is allowed. |
| 43 | + */ |
| 44 | +export function isValidWalletTransition(from: WalletStatusValue, to: WalletStatusValue): boolean { |
| 45 | + return WalletTransitions[from]?.has(to) ?? false |
| 46 | +} |
| 47 | + |
| 48 | +// ── DTOs (no plaintext secrets) ─── |
| 49 | + |
| 50 | +export interface WalletDto { |
| 51 | + id: string |
| 52 | + userId: string |
| 53 | + network: WalletNetworkValue |
| 54 | + custody: WalletCustodyValue |
| 55 | + publicKey: string |
| 56 | + status: WalletStatusValue |
| 57 | + managedKeyId: string | null |
| 58 | + statusChangedAt: string | null |
| 59 | + createdAt: string |
| 60 | + updatedAt: string |
| 61 | +} |
| 62 | + |
| 63 | +export interface ManagedKeyDto { |
| 64 | + id: string |
| 65 | + provider: string |
| 66 | + keyId: string |
| 67 | + keyVersion: string | null |
| 68 | + /** Opaque envelope – never contains plaintext secrets */ |
| 69 | + envelope: string | null |
| 70 | + createdAt: string |
| 71 | + updatedAt: string |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * Redacts sensitive fields from a wallet object before returning to API consumers. |
| 76 | + * Ensures managed key references never leak into ordinary responses. |
| 77 | + */ |
| 78 | +export function redactWallet(wallet: Record<string, unknown>): WalletDto { |
| 79 | + return { |
| 80 | + id: wallet.id as string, |
| 81 | + userId: wallet.userId as string, |
| 82 | + network: wallet.network as WalletNetworkValue, |
| 83 | + custody: wallet.custody as WalletCustodyValue, |
| 84 | + publicKey: wallet.publicKey as string, |
| 85 | + status: wallet.status as WalletStatusValue, |
| 86 | + managedKeyId: null, // never expose internal KMS reference |
| 87 | + statusChangedAt: wallet.statusChangedAt ? String(wallet.statusChangedAt) : null, |
| 88 | + createdAt: String(wallet.createdAt), |
| 89 | + updatedAt: String(wallet.updatedAt), |
| 90 | + } |
| 91 | +} |
0 commit comments