forked from Lilly-Protocol/lily-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidation.ts
More file actions
156 lines (138 loc) · 4.62 KB
/
Copy pathvalidation.ts
File metadata and controls
156 lines (138 loc) · 4.62 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { LilyValidationError } from './errors/sdk-error';
import type { MoneyAmount } from './models/common';
import type {
ExecutePaymentRequest,
PaymentQuoteRequest,
} from './models/payment';
import type { ResolveIdentityRequest } from './models/identity';
const NON_EMPTY_STRING_PATTERN = /\S/;
const DECIMAL_AMOUNT_PATTERN = /^\d+(\.\d+)?$/;
const STELLAR_ASSET_CODE_PATTERN = /^[A-Za-z0-9]{1,12}$/;
const MAX_STELLAR_FRACTIONAL_DIGITS = 7;
const MAX_MEMO_TEXT_LENGTH = 28;
const MEMO_HEX_PATTERN = /^(?:[0-9a-fA-F]{2})*$/;
const MAX_MEMO_HEX_LENGTH = 64;
export function validateNonEmptyString(
value: unknown,
fieldName: string,
): void {
if (typeof value !== 'string' || !NON_EMPTY_STRING_PATTERN.test(value)) {
throw new LilyValidationError(
`\`${fieldName}\` must be a non-empty string.`,
);
}
}
export function validateMoneyAmount(
amount: MoneyAmount,
context: string,
): void {
if (!amount || typeof amount !== 'object') {
throw new LilyValidationError(`${context}: \`amount\` is required.`);
}
if (
typeof amount.assetCode !== 'string' ||
!STELLAR_ASSET_CODE_PATTERN.test(amount.assetCode)
) {
throw new LilyValidationError(
`${context}: \`assetCode\` must be a 1-12 character alphanumeric Stellar asset code.`,
);
}
if (
typeof amount.amount !== 'string' ||
!DECIMAL_AMOUNT_PATTERN.test(amount.amount)
) {
throw new LilyValidationError(
`${context}: \`amount\` must be a non-negative decimal string (e.g. "10.50").`,
);
}
const dotIndex = amount.amount.indexOf('.');
if (dotIndex !== -1) {
const fractionalDigits = amount.amount.length - dotIndex - 1;
if (fractionalDigits > MAX_STELLAR_FRACTIONAL_DIGITS) {
throw new LilyValidationError(
`${context}: \`amount\` must have at most ${MAX_STELLAR_FRACTIONAL_DIGITS} fractional digits (Stellar limit). Got ${fractionalDigits}.`,
);
}
}
if (amount.assetIssuer !== undefined) {
if (
typeof amount.assetIssuer !== 'string' ||
!NON_EMPTY_STRING_PATTERN.test(amount.assetIssuer)
) {
throw new LilyValidationError(
`${context}: \`assetIssuer\` must be a non-empty string when provided.`,
);
}
}
}
export function validateMemo(memo: unknown, context: string): void {
if (memo === undefined || memo === null) {
return;
}
if (typeof memo !== 'string') {
throw new LilyValidationError(
`${context}: \`memo\` must be a string when provided.`,
);
}
if (MEMO_HEX_PATTERN.test(memo) && memo.length > 0) {
if (memo.length > MAX_MEMO_HEX_LENGTH) {
throw new LilyValidationError(
`${context}: \`memo\` hex string must be at most ${MAX_MEMO_HEX_LENGTH} characters. Got ${memo.length}.`,
);
}
return;
}
const memoBytes = new TextEncoder().encode(memo).length;
if (memoBytes > MAX_MEMO_TEXT_LENGTH) {
throw new LilyValidationError(
`${context}: \`memo\` text must be at most ${MAX_MEMO_TEXT_LENGTH} bytes (Stellar limit). Got ${memoBytes}.`,
);
}
}
export function validateResolveIdentityRequest(
request: ResolveIdentityRequest,
): void {
const keys = [request.agentId, request.stellarAddress, request.domain].filter(
(v) => v !== undefined && v !== null,
);
if (keys.length === 0) {
throw new LilyValidationError(
'`ResolveIdentityRequest` requires exactly one of `agentId`, `stellarAddress`, or `domain`.',
);
}
if (keys.length > 1) {
throw new LilyValidationError(
'`ResolveIdentityRequest` accepts only one resolver key at a time. Provide exactly one of `agentId`, `stellarAddress`, or `domain`.',
);
}
const providedKey =
request.agentId !== undefined
? 'agentId'
: request.stellarAddress !== undefined
? 'stellarAddress'
: 'domain';
const providedValue = request[providedKey as keyof ResolveIdentityRequest];
if (
typeof providedValue !== 'string' ||
!NON_EMPTY_STRING_PATTERN.test(providedValue)
) {
throw new LilyValidationError(
`\`${providedKey}\` must be a non-empty string.`,
);
}
}
export function validateExecutePaymentRequest(
request: ExecutePaymentRequest,
): void {
validateNonEmptyString(request.fromWalletId, 'fromWalletId');
validateNonEmptyString(request.toAddress, 'toAddress');
validateMoneyAmount(request.amount, 'ExecutePaymentRequest');
validateMemo(request.memo, 'ExecutePaymentRequest');
}
export function validatePaymentQuoteRequest(
request: PaymentQuoteRequest,
): void {
validateNonEmptyString(request.fromWalletId, 'fromWalletId');
validateNonEmptyString(request.toAddress, 'toAddress');
validateMoneyAmount(request.amount, 'PaymentQuoteRequest');
}