@t402/stacks 2.4.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.
- package/README.md +178 -0
- package/dist/exact-direct/client/index.cjs +167 -0
- package/dist/exact-direct/client/index.cjs.map +1 -0
- package/dist/exact-direct/client/index.d.cts +39 -0
- package/dist/exact-direct/client/index.d.ts +39 -0
- package/dist/exact-direct/client/index.mjs +139 -0
- package/dist/exact-direct/client/index.mjs.map +1 -0
- package/dist/exact-direct/facilitator/index.cjs +395 -0
- package/dist/exact-direct/facilitator/index.cjs.map +1 -0
- package/dist/exact-direct/facilitator/index.d.cts +55 -0
- package/dist/exact-direct/facilitator/index.d.ts +55 -0
- package/dist/exact-direct/facilitator/index.mjs +367 -0
- package/dist/exact-direct/facilitator/index.mjs.map +1 -0
- package/dist/exact-direct/server/index.cjs +247 -0
- package/dist/exact-direct/server/index.cjs.map +1 -0
- package/dist/exact-direct/server/index.d.cts +109 -0
- package/dist/exact-direct/server/index.d.ts +109 -0
- package/dist/exact-direct/server/index.mjs +218 -0
- package/dist/exact-direct/server/index.mjs.map +1 -0
- package/dist/index.cjs +261 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.mjs +212 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-Bxzo3eQ1.d.cts +172 -0
- package/dist/types-Bxzo3eQ1.d.ts +172 -0
- package/package.json +102 -0
- package/src/constants.ts +66 -0
- package/src/exact-direct/client/index.ts +5 -0
- package/src/exact-direct/client/scheme.ts +115 -0
- package/src/exact-direct/facilitator/index.ts +4 -0
- package/src/exact-direct/facilitator/scheme.ts +308 -0
- package/src/exact-direct/server/index.ts +9 -0
- package/src/exact-direct/server/register.ts +57 -0
- package/src/exact-direct/server/scheme.ts +216 -0
- package/src/index.ts +78 -0
- package/src/tokens.ts +96 -0
- package/src/types.ts +184 -0
- package/src/utils.ts +198 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stacks Utility Functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { StacksTransactionResult, ParsedTokenTransfer } from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate a Stacks principal address format
|
|
9
|
+
* Stacks addresses start with SP (mainnet) or ST (testnet)
|
|
10
|
+
* followed by alphanumeric characters (base58-like encoding)
|
|
11
|
+
*/
|
|
12
|
+
export function isValidPrincipal(address: string): boolean {
|
|
13
|
+
if (!address || typeof address !== "string") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Standard principal: SP/ST prefix + base58 characters (33-41 chars total)
|
|
18
|
+
// Contract principal: standard-principal.contract-name
|
|
19
|
+
const parts = address.split(".");
|
|
20
|
+
const principal = parts[0];
|
|
21
|
+
|
|
22
|
+
// Check principal format: SP or ST prefix + alphanumeric (base58 chars)
|
|
23
|
+
const principalRegex = /^(SP|ST)[0-9A-HJ-NP-Za-km-z]{33,41}$/;
|
|
24
|
+
if (!principalRegex.test(principal)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If it's a contract principal, validate contract name
|
|
29
|
+
if (parts.length === 2) {
|
|
30
|
+
const contractName = parts[1];
|
|
31
|
+
// Contract names: 1-128 chars, alphanumeric + hyphen + underscore
|
|
32
|
+
const contractNameRegex = /^[a-zA-Z][a-zA-Z0-9\-_]{0,127}$/;
|
|
33
|
+
return contractNameRegex.test(contractName);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Standard principal (no contract part) or exactly one dot for contract
|
|
37
|
+
return parts.length === 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate a Stacks transaction ID format
|
|
42
|
+
* Transaction IDs are 0x-prefixed 64-character hex strings
|
|
43
|
+
*/
|
|
44
|
+
export function isValidTxId(hash: string): boolean {
|
|
45
|
+
if (!hash || typeof hash !== "string") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return /^0x[a-fA-F0-9]{64}$/.test(hash);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compare two Stacks principals (case-sensitive)
|
|
53
|
+
*/
|
|
54
|
+
export function comparePrincipals(a: string, b: string): boolean {
|
|
55
|
+
return a === b;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format an amount with decimals for display
|
|
60
|
+
*/
|
|
61
|
+
export function formatAmount(amount: string, decimals: number): string {
|
|
62
|
+
const amountBigInt = BigInt(amount);
|
|
63
|
+
const divisor = BigInt(10 ** decimals);
|
|
64
|
+
const wholePart = amountBigInt / divisor;
|
|
65
|
+
const fractionalPart = amountBigInt % divisor;
|
|
66
|
+
|
|
67
|
+
if (fractionalPart === 0n) {
|
|
68
|
+
return wholePart.toString();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
72
|
+
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
73
|
+
return `${wholePart}.${trimmedFractional}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse an amount string to the smallest unit (with decimals applied)
|
|
78
|
+
*/
|
|
79
|
+
export function parseAmount(amount: string, decimals: number): string {
|
|
80
|
+
const parts = amount.split(".");
|
|
81
|
+
const wholePart = parts[0] || "0";
|
|
82
|
+
const fractionalPart = (parts[1] || "").padEnd(decimals, "0").slice(0, decimals);
|
|
83
|
+
return BigInt(wholePart + fractionalPart).toString();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extract token transfer details from a Stacks transaction result
|
|
88
|
+
* Looks for ft_transfer events matching the expected contract
|
|
89
|
+
*/
|
|
90
|
+
export function extractTokenTransfer(
|
|
91
|
+
result: StacksTransactionResult,
|
|
92
|
+
contractAddress?: string,
|
|
93
|
+
): ParsedTokenTransfer | null {
|
|
94
|
+
if (result.txStatus !== "success") {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if this is a contract-call transaction
|
|
99
|
+
if (result.txType !== "contract_call") {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check contract call is a transfer function
|
|
104
|
+
if (result.contractCall) {
|
|
105
|
+
const { contractId, functionName } = result.contractCall;
|
|
106
|
+
|
|
107
|
+
if (functionName !== "transfer") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// If contractAddress specified, verify it matches
|
|
112
|
+
if (contractAddress && contractId !== contractAddress) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Look for ft_transfer event
|
|
117
|
+
const transferEvent = result.events.find(
|
|
118
|
+
(e) => e.eventType === "fungible_token_asset" && e.asset?.assetEventType === "transfer",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (transferEvent?.asset) {
|
|
122
|
+
return {
|
|
123
|
+
contractAddress: contractId,
|
|
124
|
+
from: transferEvent.asset.sender,
|
|
125
|
+
to: transferEvent.asset.recipient,
|
|
126
|
+
amount: transferEvent.asset.amount,
|
|
127
|
+
success: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Fallback: extract from function args if events not available
|
|
132
|
+
if (result.contractCall.functionArgs.length >= 3) {
|
|
133
|
+
const amountArg = result.contractCall.functionArgs[0];
|
|
134
|
+
const senderArg = result.contractCall.functionArgs[1];
|
|
135
|
+
const recipientArg = result.contractCall.functionArgs[2];
|
|
136
|
+
|
|
137
|
+
// Parse principal from repr (format: 'SP...')
|
|
138
|
+
const senderMatch = senderArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);
|
|
139
|
+
const recipientMatch = recipientArg?.repr?.match(/^'?(S[PT][0-9A-HJ-NP-Za-km-z]+)/);
|
|
140
|
+
const amountMatch = amountArg?.repr?.match(/^u(\d+)$/);
|
|
141
|
+
|
|
142
|
+
if (senderMatch && recipientMatch && amountMatch) {
|
|
143
|
+
return {
|
|
144
|
+
contractAddress: contractId,
|
|
145
|
+
from: senderMatch[1],
|
|
146
|
+
to: recipientMatch[1],
|
|
147
|
+
amount: amountMatch[1],
|
|
148
|
+
success: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extract token transfer from post conditions (alternative method)
|
|
159
|
+
*/
|
|
160
|
+
export function extractTokenTransferFromPostConditions(
|
|
161
|
+
result: StacksTransactionResult,
|
|
162
|
+
contractAddress?: string,
|
|
163
|
+
): ParsedTokenTransfer | null {
|
|
164
|
+
if (result.txStatus !== "success") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Look for fungible token post conditions
|
|
169
|
+
for (const pc of result.postConditions) {
|
|
170
|
+
if (pc.asset) {
|
|
171
|
+
const assetContractAddress = `${pc.asset.contractAddress}.${pc.asset.contractName}`;
|
|
172
|
+
|
|
173
|
+
if (contractAddress && assetContractAddress !== contractAddress) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Find corresponding ft_transfer event for recipient
|
|
178
|
+
const transferEvent = result.events.find(
|
|
179
|
+
(e) =>
|
|
180
|
+
e.eventType === "fungible_token_asset" &&
|
|
181
|
+
e.asset?.assetEventType === "transfer" &&
|
|
182
|
+
e.asset?.sender === pc.principal.address,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (transferEvent?.asset) {
|
|
186
|
+
return {
|
|
187
|
+
contractAddress: assetContractAddress,
|
|
188
|
+
from: transferEvent.asset.sender,
|
|
189
|
+
to: transferEvent.asset.recipient,
|
|
190
|
+
amount: transferEvent.asset.amount,
|
|
191
|
+
success: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|