forked from Disciplr-Org/Disciplr-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvaultValidation.ts
More file actions
115 lines (95 loc) · 3.51 KB
/
Copy pathvaultValidation.ts
File metadata and controls
115 lines (95 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
export interface CreateVaultFormValues {
amount: string;
deadline: string;
successAddress: string;
failureAddress: string;
milestones?: CreateVaultMilestoneInput[];
}
export interface CreateVaultMilestoneInput {
title: string;
criteria: string;
}
export interface CreateVaultMilestoneRowErrors {
title?: string;
criteria?: string;
}
export interface CreateVaultMilestoneErrors {
form?: string;
rows?: CreateVaultMilestoneRowErrors[];
}
type CreateVaultFieldName = Exclude<keyof CreateVaultFormValues, "milestones">;
export type CreateVaultErrors = Partial<
Record<CreateVaultFieldName, string>
> & {
milestones?: CreateVaultMilestoneErrors;
};
const STELLAR_PUBLIC_KEY = /^G[A-Z2-7]{55}$/;
const USDC_AMOUNT = /^(?:0|[1-9]\d*)(?:\.\d{1,7})?$/;
export function isValidStellarAddress(address: string): boolean {
return STELLAR_PUBLIC_KEY.test(address.trim());
}
export function isValidUsdcAmount(amount: string): boolean {
const normalized = amount.trim();
if (!USDC_AMOUNT.test(normalized)) return false;
return Number(normalized) > 0;
}
export function isFutureDeadline(deadline: string, now = new Date()): boolean {
const timestamp = new Date(deadline).getTime();
return Number.isFinite(timestamp) && timestamp > now.getTime();
}
export function validateCreateVault(
values: CreateVaultFormValues,
now = new Date(),
): CreateVaultErrors {
const errors: CreateVaultErrors = {};
const successAddress = values.successAddress.trim();
const failureAddress = values.failureAddress.trim();
const verifierAddress = values.verifierAddress.trim();
if (!isValidUsdcAmount(values.amount)) {
errors.amount = "Enter a positive USDC amount with up to 7 decimal places.";
}
if (!isFutureDeadline(values.deadline, now)) {
errors.deadline = "Choose a future deadline.";
}
if (!isValidStellarAddress(successAddress)) {
errors.successAddress = "Enter a valid Stellar public key starting with G.";
}
if (!isValidStellarAddress(failureAddress)) {
errors.failureAddress = "Enter a valid Stellar public key starting with G.";
} else if (successAddress === failureAddress) {
errors.failureAddress =
"Failure destination must be different from success destination.";
}
const milestoneErrors = validateMilestones(values.milestones);
if (milestoneErrors) {
errors.milestones = milestoneErrors;
}
const title = values.milestoneTitle.trim();
if (!title) {
errors.milestoneTitle = 'Enter a milestone title.';
} else if (title.length > MILESTONE_TITLE_MAX) {
errors.milestoneTitle = `Milestone title must be ${MILESTONE_TITLE_MAX} characters or fewer.`;
}
const criteria = values.milestoneCriteria.trim();
if (!criteria) {
errors.milestoneCriteria = 'Enter the milestone criteria.';
} else if (criteria.length > MILESTONE_CRITERIA_MAX) {
errors.milestoneCriteria = `Milestone criteria must be ${MILESTONE_CRITERIA_MAX} characters or fewer.`;
}
return errors;
}
export function hasCreateVaultErrors(errors: CreateVaultErrors): boolean {
return Object.keys(errors).length > 0;
}
/**
* Returns true when amount is a valid positive number that strictly exceeds the
* available balance. Returns false when either value is not a finite number so
* the caller can treat an unknown balance as non-blocking.
*/
export function exceedsBalance(amount: string, balance: string | null): boolean {
if (balance === null) return false;
const a = Number(amount);
const b = Number(balance);
if (!Number.isFinite(a) || !Number.isFinite(b)) return false;
return a > b;
}