@t402/tezos 2.3.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.
Files changed (42) hide show
  1. package/README.md +161 -0
  2. package/dist/exact-direct/client/index.d.cts +92 -0
  3. package/dist/exact-direct/client/index.d.ts +92 -0
  4. package/dist/exact-direct/client/index.js +204 -0
  5. package/dist/exact-direct/client/index.js.map +1 -0
  6. package/dist/exact-direct/client/index.mjs +176 -0
  7. package/dist/exact-direct/client/index.mjs.map +1 -0
  8. package/dist/exact-direct/facilitator/index.d.cts +110 -0
  9. package/dist/exact-direct/facilitator/index.d.ts +110 -0
  10. package/dist/exact-direct/facilitator/index.js +331 -0
  11. package/dist/exact-direct/facilitator/index.js.map +1 -0
  12. package/dist/exact-direct/facilitator/index.mjs +303 -0
  13. package/dist/exact-direct/facilitator/index.mjs.map +1 -0
  14. package/dist/exact-direct/server/index.d.cts +109 -0
  15. package/dist/exact-direct/server/index.d.ts +109 -0
  16. package/dist/exact-direct/server/index.js +226 -0
  17. package/dist/exact-direct/server/index.js.map +1 -0
  18. package/dist/exact-direct/server/index.mjs +198 -0
  19. package/dist/exact-direct/server/index.mjs.map +1 -0
  20. package/dist/index.d.cts +124 -0
  21. package/dist/index.d.ts +124 -0
  22. package/dist/index.js +228 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/index.mjs +170 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/types-DQMtUOa_.d.cts +125 -0
  27. package/dist/types-DQMtUOa_.d.ts +125 -0
  28. package/package.json +100 -0
  29. package/src/constants.ts +53 -0
  30. package/src/exact-direct/client/index.ts +13 -0
  31. package/src/exact-direct/client/register.ts +71 -0
  32. package/src/exact-direct/client/scheme.ts +177 -0
  33. package/src/exact-direct/facilitator/index.ts +13 -0
  34. package/src/exact-direct/facilitator/register.ts +74 -0
  35. package/src/exact-direct/facilitator/scheme.ts +311 -0
  36. package/src/exact-direct/server/index.ts +13 -0
  37. package/src/exact-direct/server/register.ts +64 -0
  38. package/src/exact-direct/server/scheme.ts +205 -0
  39. package/src/index.ts +32 -0
  40. package/src/tokens.ts +86 -0
  41. package/src/types.ts +160 -0
  42. package/src/utils.ts +128 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Tezos Exact-Direct Facilitator Scheme
3
+ *
4
+ * Verifies FA2 transfer operations and manages replay protection.
5
+ */
6
+
7
+ import type {
8
+ SchemeNetworkFacilitator,
9
+ PaymentPayload,
10
+ PaymentRequirements,
11
+ VerifyResponse,
12
+ SettleResponse,
13
+ Network,
14
+ } from "@t402/core/types";
15
+ import { SCHEME_EXACT_DIRECT, TEZOS_CAIP2_NAMESPACE } from "../../constants.js";
16
+ import type {
17
+ FacilitatorTezosSigner,
18
+ ExactDirectTezosPayload,
19
+ } from "../../types.js";
20
+ import { isValidOperationHash, isTezosNetwork } from "../../types.js";
21
+ import {
22
+ compareAddresses,
23
+ extractFA2TransferDetails,
24
+ } from "../../utils.js";
25
+ import { getDefaultToken } from "../../tokens.js";
26
+
27
+ /**
28
+ * Configuration for ExactDirectTezosFacilitator
29
+ */
30
+ export interface ExactDirectTezosFacilitatorConfig {
31
+ /**
32
+ * Maximum age of operation in seconds (default: 3600 = 1 hour)
33
+ */
34
+ maxOperationAge?: number;
35
+
36
+ /**
37
+ * Duration to cache used operation hashes (in milliseconds)
38
+ */
39
+ usedOpCacheDuration?: number;
40
+ }
41
+
42
+ /**
43
+ * Tezos Exact-Direct Facilitator
44
+ *
45
+ * Implements the facilitator-side verification and settlement.
46
+ * For exact-direct, settlement is a no-op since client already executed.
47
+ */
48
+ export class ExactDirectTezosFacilitator implements SchemeNetworkFacilitator {
49
+ readonly scheme = SCHEME_EXACT_DIRECT;
50
+ readonly caipFamily = `${TEZOS_CAIP2_NAMESPACE}:*`;
51
+
52
+ private readonly config: Required<ExactDirectTezosFacilitatorConfig>;
53
+ private usedOps: Map<string, number> = new Map();
54
+
55
+ constructor(
56
+ private readonly signer: FacilitatorTezosSigner,
57
+ config?: ExactDirectTezosFacilitatorConfig,
58
+ ) {
59
+ this.config = {
60
+ maxOperationAge: config?.maxOperationAge ?? 3600,
61
+ usedOpCacheDuration: config?.usedOpCacheDuration ?? 24 * 60 * 60 * 1000, // 24 hours
62
+ };
63
+
64
+ // Start cleanup interval
65
+ this.startCleanupInterval();
66
+ }
67
+
68
+ /**
69
+ * Get extra data for a supported kind
70
+ */
71
+ getExtra(network: Network): Record<string, unknown> | undefined {
72
+ const token = getDefaultToken(network);
73
+ if (!token) {
74
+ return undefined;
75
+ }
76
+ return {
77
+ assetSymbol: token.symbol,
78
+ assetDecimals: token.decimals,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Get facilitator signer addresses for a network
84
+ */
85
+ getSigners(network: Network): string[] {
86
+ return this.signer.getAddresses(network);
87
+ }
88
+
89
+ /**
90
+ * Verify a payment payload
91
+ */
92
+ async verify(
93
+ payload: PaymentPayload,
94
+ requirements: PaymentRequirements,
95
+ ): Promise<VerifyResponse> {
96
+ // Validate scheme
97
+ if (payload.accepted.scheme !== SCHEME_EXACT_DIRECT) {
98
+ return {
99
+ isValid: false,
100
+ invalidReason: "invalid_scheme",
101
+ };
102
+ }
103
+
104
+ // Validate network
105
+ if (!isTezosNetwork(payload.accepted.network)) {
106
+ return {
107
+ isValid: false,
108
+ invalidReason: "invalid_network",
109
+ };
110
+ }
111
+
112
+ // Extract Tezos-specific payload
113
+ const tezosPayload = payload.payload as ExactDirectTezosPayload;
114
+
115
+ // Validate operation hash format
116
+ if (!isValidOperationHash(tezosPayload.opHash)) {
117
+ return {
118
+ isValid: false,
119
+ invalidReason: "invalid_operation_hash_format",
120
+ };
121
+ }
122
+
123
+ // Check for replay attack
124
+ if (this.isOpUsed(tezosPayload.opHash)) {
125
+ return {
126
+ isValid: false,
127
+ invalidReason: "operation_already_used",
128
+ payer: tezosPayload.from,
129
+ };
130
+ }
131
+
132
+ try {
133
+ // Query operation
134
+ const op = await this.signer.queryOperation(tezosPayload.opHash);
135
+ if (!op) {
136
+ return {
137
+ isValid: false,
138
+ invalidReason: "operation_not_found",
139
+ payer: tezosPayload.from,
140
+ };
141
+ }
142
+
143
+ // Verify operation was successful
144
+ if (op.status !== "applied") {
145
+ return {
146
+ isValid: false,
147
+ invalidReason: `operation_not_applied: status is ${op.status}`,
148
+ payer: tezosPayload.from,
149
+ };
150
+ }
151
+
152
+ // Check operation age
153
+ if (this.config.maxOperationAge > 0) {
154
+ const opTimestamp = new Date(op.timestamp).getTime() / 1000;
155
+ const now = Date.now() / 1000;
156
+ const age = now - opTimestamp;
157
+ if (age > this.config.maxOperationAge) {
158
+ return {
159
+ isValid: false,
160
+ invalidReason: `operation_too_old: ${Math.round(age)} seconds`,
161
+ payer: tezosPayload.from,
162
+ };
163
+ }
164
+ }
165
+
166
+ // Verify it's a transfer to the correct contract
167
+ if (op.target?.address !== tezosPayload.contractAddress) {
168
+ return {
169
+ isValid: false,
170
+ invalidReason: `contract_mismatch: expected ${tezosPayload.contractAddress}, got ${op.target?.address}`,
171
+ payer: tezosPayload.from,
172
+ };
173
+ }
174
+
175
+ // Verify entrypoint
176
+ if (op.entrypoint !== "transfer") {
177
+ return {
178
+ isValid: false,
179
+ invalidReason: `entrypoint_mismatch: expected transfer, got ${op.entrypoint}`,
180
+ payer: tezosPayload.from,
181
+ };
182
+ }
183
+
184
+ // Extract transfer details from parameter
185
+ const transferDetails = extractFA2TransferDetails(op.parameter);
186
+ if (!transferDetails) {
187
+ return {
188
+ isValid: false,
189
+ invalidReason: "could_not_extract_transfer_details",
190
+ payer: tezosPayload.from,
191
+ };
192
+ }
193
+
194
+ // Verify sender
195
+ if (!compareAddresses(transferDetails.from, op.sender.address)) {
196
+ return {
197
+ isValid: false,
198
+ invalidReason: `sender_mismatch: parameter says ${transferDetails.from}, but sender is ${op.sender.address}`,
199
+ payer: tezosPayload.from,
200
+ };
201
+ }
202
+
203
+ // Verify recipient
204
+ if (!compareAddresses(transferDetails.to, requirements.payTo)) {
205
+ return {
206
+ isValid: false,
207
+ invalidReason: `recipient_mismatch: expected ${requirements.payTo}, got ${transferDetails.to}`,
208
+ payer: tezosPayload.from,
209
+ };
210
+ }
211
+
212
+ // Verify token ID
213
+ if (transferDetails.tokenId !== tezosPayload.tokenId) {
214
+ return {
215
+ isValid: false,
216
+ invalidReason: `token_id_mismatch: expected ${tezosPayload.tokenId}, got ${transferDetails.tokenId}`,
217
+ payer: tezosPayload.from,
218
+ };
219
+ }
220
+
221
+ // Verify amount
222
+ const expectedAmount = BigInt(requirements.amount);
223
+ const actualAmount = BigInt(transferDetails.amount);
224
+ if (actualAmount < expectedAmount) {
225
+ return {
226
+ isValid: false,
227
+ invalidReason: `insufficient_amount: expected ${expectedAmount}, got ${actualAmount}`,
228
+ payer: tezosPayload.from,
229
+ };
230
+ }
231
+
232
+ // Mark operation as used
233
+ this.markOpUsed(tezosPayload.opHash);
234
+
235
+ return {
236
+ isValid: true,
237
+ payer: transferDetails.from,
238
+ };
239
+ } catch (error) {
240
+ return {
241
+ isValid: false,
242
+ invalidReason: `verification_error: ${error instanceof Error ? error.message : String(error)}`,
243
+ payer: tezosPayload.from,
244
+ };
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Settle a payment (no-op for exact-direct since client already executed)
250
+ */
251
+ async settle(
252
+ payload: PaymentPayload,
253
+ requirements: PaymentRequirements,
254
+ ): Promise<SettleResponse> {
255
+ // Verify first
256
+ const verifyResult = await this.verify(payload, requirements);
257
+
258
+ if (!verifyResult.isValid) {
259
+ return {
260
+ success: false,
261
+ errorReason: verifyResult.invalidReason || "verification_failed",
262
+ payer: verifyResult.payer,
263
+ transaction: "",
264
+ network: requirements.network,
265
+ };
266
+ }
267
+
268
+ const tezosPayload = payload.payload as ExactDirectTezosPayload;
269
+
270
+ // For exact-direct, settlement is already complete
271
+ return {
272
+ success: true,
273
+ transaction: tezosPayload.opHash,
274
+ network: requirements.network,
275
+ payer: tezosPayload.from,
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Check if an operation has been used
281
+ */
282
+ private isOpUsed(opHash: string): boolean {
283
+ return this.usedOps.has(opHash);
284
+ }
285
+
286
+ /**
287
+ * Mark an operation as used
288
+ */
289
+ private markOpUsed(opHash: string): void {
290
+ this.usedOps.set(opHash, Date.now());
291
+ }
292
+
293
+ /**
294
+ * Start the cleanup interval for used operations
295
+ */
296
+ private startCleanupInterval(): void {
297
+ setInterval(
298
+ () => {
299
+ const cutoff = Date.now() - this.config.usedOpCacheDuration;
300
+ for (const [opHash, usedAt] of this.usedOps.entries()) {
301
+ if (usedAt < cutoff) {
302
+ this.usedOps.delete(opHash);
303
+ }
304
+ }
305
+ },
306
+ 60 * 60 * 1000,
307
+ ); // Cleanup every hour
308
+ }
309
+ }
310
+
311
+ export default ExactDirectTezosFacilitator;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tezos Exact-Direct Server
3
+ */
4
+
5
+ export {
6
+ ExactDirectTezosServer,
7
+ type ExactDirectTezosServerConfig,
8
+ } from "./scheme.js";
9
+
10
+ export {
11
+ registerExactDirectTezosServer,
12
+ type TezosServerConfig,
13
+ } from "./register.js";
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Registration function for Tezos Exact-Direct server
3
+ */
4
+
5
+ import { t402ResourceServer } from "@t402/core/server";
6
+ import type { Network } from "@t402/core/types";
7
+ import {
8
+ ExactDirectTezosServer,
9
+ type ExactDirectTezosServerConfig,
10
+ } from "./scheme.js";
11
+
12
+ /**
13
+ * Configuration options for registering Tezos schemes to a t402ResourceServer
14
+ */
15
+ export interface TezosServerConfig {
16
+ /**
17
+ * Optional specific networks to register
18
+ * If not provided, registers wildcard support (tezos:*)
19
+ */
20
+ networks?: Network[];
21
+
22
+ /**
23
+ * Optional scheme configuration
24
+ */
25
+ schemeConfig?: ExactDirectTezosServerConfig;
26
+ }
27
+
28
+ /**
29
+ * Registers Tezos exact-direct payment scheme to a t402ResourceServer instance.
30
+ *
31
+ * @param server - The t402ResourceServer instance to register schemes to
32
+ * @param config - Configuration for Tezos server registration
33
+ * @returns The server instance for chaining
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * import { registerExactDirectTezosServer } from "@t402/tezos/exact-direct/server";
38
+ * import { t402ResourceServer } from "@t402/core/server";
39
+ *
40
+ * const server = new t402ResourceServer();
41
+ * registerExactDirectTezosServer(server, {
42
+ * networks: ["tezos:NetXdQprcVkpaWU"]
43
+ * });
44
+ * ```
45
+ */
46
+ export function registerExactDirectTezosServer(
47
+ server: t402ResourceServer,
48
+ config: TezosServerConfig = {},
49
+ ): t402ResourceServer {
50
+ const scheme = new ExactDirectTezosServer(config.schemeConfig);
51
+
52
+ // Register scheme
53
+ if (config.networks && config.networks.length > 0) {
54
+ // Register specific networks
55
+ config.networks.forEach((network) => {
56
+ server.register(network, scheme);
57
+ });
58
+ } else {
59
+ // Register wildcard for all Tezos networks
60
+ server.register("tezos:*", scheme);
61
+ }
62
+
63
+ return server;
64
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Tezos Exact-Direct Server Scheme
3
+ *
4
+ * Handles price parsing and payment requirement enhancement for
5
+ * Tezos FA2 payments using the exact-direct scheme.
6
+ */
7
+
8
+ import type {
9
+ SchemeNetworkServer,
10
+ PaymentRequirements,
11
+ Price,
12
+ AssetAmount,
13
+ Network,
14
+ MoneyParser,
15
+ } from "@t402/core/types";
16
+ import { SCHEME_EXACT_DIRECT } from "../../constants.js";
17
+ import { getTokenBySymbol, getDefaultToken, TOKEN_REGISTRY } from "../../tokens.js";
18
+ import { parseAmount } from "../../utils.js";
19
+ import { isTezosNetwork } from "../../types.js";
20
+
21
+ /**
22
+ * Configuration for ExactDirectTezosServer
23
+ */
24
+ export interface ExactDirectTezosServerConfig {
25
+ /** Preferred token symbol (e.g., "USDt"). Defaults to network's default token. */
26
+ preferredToken?: string;
27
+ }
28
+
29
+ /**
30
+ * Tezos Exact-Direct Server
31
+ *
32
+ * Implements the server-side price parsing and payment requirements enhancement.
33
+ */
34
+ export class ExactDirectTezosServer implements SchemeNetworkServer {
35
+ readonly scheme = SCHEME_EXACT_DIRECT;
36
+ private moneyParsers: MoneyParser[] = [];
37
+ private config: ExactDirectTezosServerConfig;
38
+
39
+ constructor(config: ExactDirectTezosServerConfig = {}) {
40
+ this.config = config;
41
+ }
42
+
43
+ /**
44
+ * Register a custom money parser in the parser chain.
45
+ */
46
+ registerMoneyParser(parser: MoneyParser): ExactDirectTezosServer {
47
+ this.moneyParsers.push(parser);
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * Parse price into Tezos-specific amount
53
+ */
54
+ async parsePrice(price: Price, network: Network): Promise<AssetAmount> {
55
+ // Validate network
56
+ if (!isTezosNetwork(network)) {
57
+ throw new Error(`Invalid Tezos network: ${network}`);
58
+ }
59
+
60
+ // If already an AssetAmount, return it directly
61
+ if (typeof price === "object" && price !== null && "amount" in price) {
62
+ if (!price.asset) {
63
+ throw new Error(`Asset must be specified for AssetAmount on network ${network}`);
64
+ }
65
+ return {
66
+ amount: price.amount,
67
+ asset: price.asset,
68
+ extra: price.extra || {},
69
+ };
70
+ }
71
+
72
+ // Parse Money to decimal number
73
+ const amount = this.parseMoneyToDecimal(price);
74
+
75
+ // Try each custom money parser in order
76
+ for (const parser of this.moneyParsers) {
77
+ const result = await parser(amount, network);
78
+ if (result !== null) {
79
+ return result;
80
+ }
81
+ }
82
+
83
+ // All custom parsers returned null, use default conversion
84
+ return this.defaultMoneyConversion(amount, network);
85
+ }
86
+
87
+ /**
88
+ * Enhance payment requirements with Tezos-specific details
89
+ */
90
+ async enhancePaymentRequirements(
91
+ paymentRequirements: PaymentRequirements,
92
+ supportedKind: {
93
+ t402Version: number;
94
+ scheme: string;
95
+ network: Network;
96
+ extra?: Record<string, unknown>;
97
+ },
98
+ facilitatorExtensions: string[],
99
+ ): Promise<PaymentRequirements> {
100
+ // Mark unused parameters
101
+ void facilitatorExtensions;
102
+
103
+ // Start with existing extra fields
104
+ const extra = { ...paymentRequirements.extra };
105
+
106
+ // Add any facilitator-provided extra fields
107
+ if (supportedKind.extra?.assetSymbol) {
108
+ extra.assetSymbol = supportedKind.extra.assetSymbol;
109
+ }
110
+ if (supportedKind.extra?.assetDecimals) {
111
+ extra.assetDecimals = supportedKind.extra.assetDecimals;
112
+ }
113
+
114
+ return {
115
+ ...paymentRequirements,
116
+ extra,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Parse Money (string | number) to a decimal number.
122
+ */
123
+ private parseMoneyToDecimal(money: string | number): number {
124
+ if (typeof money === "number") {
125
+ return money;
126
+ }
127
+
128
+ // Remove $ sign and whitespace, then parse
129
+ const cleanMoney = money.replace(/^\$/, "").trim();
130
+ const amount = parseFloat(cleanMoney);
131
+
132
+ if (isNaN(amount)) {
133
+ throw new Error(`Invalid money format: ${money}`);
134
+ }
135
+
136
+ return amount;
137
+ }
138
+
139
+ /**
140
+ * Default money conversion implementation.
141
+ */
142
+ private defaultMoneyConversion(amount: number, network: Network): AssetAmount {
143
+ const token = this.getDefaultAsset(network);
144
+
145
+ // Convert decimal amount to token amount
146
+ const tokenAmount = parseAmount(amount.toString(), token.decimals);
147
+
148
+ return {
149
+ amount: tokenAmount.toString(),
150
+ asset: this.createAssetIdentifier(network, token.contractAddress, token.tokenId),
151
+ extra: {
152
+ symbol: token.symbol,
153
+ name: token.name,
154
+ decimals: token.decimals,
155
+ tokenId: token.tokenId,
156
+ },
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Create a CAIP-19 asset identifier for Tezos FA2
162
+ */
163
+ private createAssetIdentifier(
164
+ network: Network,
165
+ contractAddress: string,
166
+ tokenId: number,
167
+ ): string {
168
+ return `${network}/fa2:${contractAddress}/${tokenId}`;
169
+ }
170
+
171
+ /**
172
+ * Get the default asset info for a network.
173
+ */
174
+ private getDefaultAsset(
175
+ network: Network,
176
+ ): { contractAddress: string; tokenId: number; symbol: string; name: string; decimals: number } {
177
+ // If a preferred token is configured, try to use it
178
+ if (this.config.preferredToken) {
179
+ const preferred = getTokenBySymbol(network, this.config.preferredToken);
180
+ if (preferred) return preferred;
181
+ }
182
+
183
+ // Use the network's default token
184
+ const defaultToken = getDefaultToken(network);
185
+ if (defaultToken) return defaultToken;
186
+
187
+ throw new Error(`No tokens configured for network ${network}`);
188
+ }
189
+
190
+ /**
191
+ * Get all supported networks
192
+ */
193
+ static getSupportedNetworks(): string[] {
194
+ return Object.keys(TOKEN_REGISTRY);
195
+ }
196
+
197
+ /**
198
+ * Check if a network is supported
199
+ */
200
+ static isNetworkSupported(network: string): boolean {
201
+ return network in TOKEN_REGISTRY;
202
+ }
203
+ }
204
+
205
+ export default ExactDirectTezosServer;
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @t402/tezos - Tezos (FA2) mechanism for T402 payment protocol
3
+ *
4
+ * This package provides client, server, and facilitator implementations
5
+ * for processing USDT payments on Tezos using the FA2 token standard (TZIP-12).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Client usage
10
+ * import { registerExactDirectTezosClient } from "@t402/tezos/exact-direct/client";
11
+ *
12
+ * // Server usage
13
+ * import { registerExactDirectTezosServer } from "@t402/tezos/exact-direct/server";
14
+ *
15
+ * // Facilitator usage
16
+ * import { registerExactDirectTezosFacilitator } from "@t402/tezos/exact-direct/facilitator";
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
22
+ // Constants
23
+ export * from "./constants.js";
24
+
25
+ // Types
26
+ export * from "./types.js";
27
+
28
+ // Tokens
29
+ export * from "./tokens.js";
30
+
31
+ // Utilities
32
+ export * from "./utils.js";
package/src/tokens.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Tezos token registry
3
+ */
4
+
5
+ import { TEZOS_MAINNET_CAIP2, TEZOS_GHOSTNET_CAIP2 } from "./constants.js";
6
+
7
+ /**
8
+ * Token configuration for Tezos FA2 tokens
9
+ */
10
+ export interface TokenConfig {
11
+ /** FA2 contract address (KT1...) */
12
+ contractAddress: string;
13
+ /** Token ID within the FA2 contract */
14
+ tokenId: number;
15
+ /** Token symbol */
16
+ symbol: string;
17
+ /** Token name */
18
+ name: string;
19
+ /** Token decimals */
20
+ decimals: number;
21
+ }
22
+
23
+ /**
24
+ * USDT on Tezos Mainnet
25
+ */
26
+ export const USDT_MAINNET: TokenConfig = {
27
+ contractAddress: "KT1XnTn74bUtxHfDtBmm2bGZAQfhPbvKWR8o",
28
+ tokenId: 0,
29
+ symbol: "USDt",
30
+ name: "Tether USD",
31
+ decimals: 6,
32
+ };
33
+
34
+ /**
35
+ * Token registry by network
36
+ */
37
+ export const TOKEN_REGISTRY: Record<string, TokenConfig[]> = {
38
+ [TEZOS_MAINNET_CAIP2]: [USDT_MAINNET],
39
+ [TEZOS_GHOSTNET_CAIP2]: [],
40
+ };
41
+
42
+ /**
43
+ * Default token for each network
44
+ */
45
+ export const DEFAULT_TOKENS: Record<string, TokenConfig | undefined> = {
46
+ [TEZOS_MAINNET_CAIP2]: USDT_MAINNET,
47
+ [TEZOS_GHOSTNET_CAIP2]: undefined,
48
+ };
49
+
50
+ /**
51
+ * Get token by symbol for a network
52
+ */
53
+ export function getTokenBySymbol(
54
+ network: string,
55
+ symbol: string,
56
+ ): TokenConfig | undefined {
57
+ const tokens = TOKEN_REGISTRY[network];
58
+ if (!tokens) return undefined;
59
+ return tokens.find(
60
+ (t) => t.symbol.toLowerCase() === symbol.toLowerCase(),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Get token by contract address and token ID
66
+ */
67
+ export function getTokenByContract(
68
+ network: string,
69
+ contractAddress: string,
70
+ tokenId: number,
71
+ ): TokenConfig | undefined {
72
+ const tokens = TOKEN_REGISTRY[network];
73
+ if (!tokens) return undefined;
74
+ return tokens.find(
75
+ (t) =>
76
+ t.contractAddress.toLowerCase() === contractAddress.toLowerCase() &&
77
+ t.tokenId === tokenId,
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Get default token for a network
83
+ */
84
+ export function getDefaultToken(network: string): TokenConfig | undefined {
85
+ return DEFAULT_TOKENS[network];
86
+ }