nebulon-escrow-cli 0.1.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.
@@ -0,0 +1,102 @@
1
+ const fs = require("fs");
2
+ const { prompt } = require("enquirer");
3
+ const {
4
+ activeCapsulePath,
5
+ capsuleRoot,
6
+ capsulesDir,
7
+ legacyConfigPath,
8
+ legacyWalletsDir,
9
+ nebulonHome,
10
+ } = require("../paths");
11
+ const {
12
+ getActiveCapsule,
13
+ listCapsules,
14
+ readActiveState,
15
+ writeActiveState,
16
+ } = require("../capsules");
17
+
18
+ const safeRemove = (targetPath) => {
19
+ if (!targetPath || !fs.existsSync(targetPath)) {
20
+ return false;
21
+ }
22
+ const stats = fs.statSync(targetPath);
23
+ if (stats.isDirectory()) {
24
+ fs.rmSync(targetPath, { recursive: true, force: true });
25
+ } else {
26
+ fs.rmSync(targetPath, { force: true });
27
+ }
28
+ return true;
29
+ };
30
+
31
+ const runReset = async (scope) => {
32
+ const resetAll = scope === "capsules";
33
+ if (scope && !resetAll) {
34
+ console.log("Usage: nebulon reset [capsules]");
35
+ return;
36
+ }
37
+
38
+ if (resetAll) {
39
+ console.log("This will reset all Nebulon CLI capsules.");
40
+ } else {
41
+ const capsule = getActiveCapsule();
42
+ console.log(`This will reset capsule "${capsule}".`);
43
+ }
44
+ console.log("Imported wallets in C:\\nebulon\\wallets are preserved.");
45
+
46
+ const answer = await prompt({
47
+ type: "confirm",
48
+ name: "confirm",
49
+ message: "Proceed with reset?",
50
+ initial: false,
51
+ });
52
+
53
+ if (!answer.confirm) {
54
+ console.log("Reset canceled.");
55
+ return;
56
+ }
57
+
58
+ if (resetAll) {
59
+ listCapsules().forEach((capsule) => {
60
+ safeRemove(capsuleRoot(capsule));
61
+ });
62
+ safeRemove(capsulesDir());
63
+ safeRemove(activeCapsulePath());
64
+ safeRemove(legacyConfigPath());
65
+ safeRemove(legacyWalletsDir());
66
+ } else {
67
+ const capsule = getActiveCapsule();
68
+ safeRemove(capsuleRoot(capsule));
69
+ const state = readActiveState();
70
+ const remaining = listCapsules().filter((name) => name !== capsule);
71
+ if (state.sessions) {
72
+ Object.keys(state.sessions).forEach((key) => {
73
+ if (state.sessions[key] === capsule) {
74
+ delete state.sessions[key];
75
+ }
76
+ });
77
+ }
78
+ state.lastUsed = remaining.length ? remaining[0] : null;
79
+ if (remaining.length) {
80
+ writeActiveState(state);
81
+ } else {
82
+ safeRemove(activeCapsulePath());
83
+ safeRemove(capsulesDir());
84
+ }
85
+ }
86
+
87
+ const home = nebulonHome();
88
+ if (fs.existsSync(home)) {
89
+ const remaining = fs.readdirSync(home);
90
+ if (remaining.length === 0) {
91
+ fs.rmSync(home, { recursive: true, force: true });
92
+ }
93
+ }
94
+
95
+ console.log(
96
+ "The Nebulon CLI has been reset successfully. Run nebulon init to get started."
97
+ );
98
+ };
99
+
100
+ module.exports = {
101
+ runReset,
102
+ };
@@ -0,0 +1,72 @@
1
+ const { loadConfig } = require("../config");
2
+ const { getActiveCapsule } = require("../capsules");
3
+ const { loadWalletKeypair } = require("../wallets");
4
+ const { me } = require("../hosted");
5
+ const {
6
+ formatAmount,
7
+ getConnection,
8
+ getSolBalance,
9
+ getTokenBalance,
10
+ toPublicKey,
11
+ } = require("../solana");
12
+ const { keyValue } = require("../ui");
13
+
14
+ const runStatus = async () => {
15
+ const config = loadConfig();
16
+
17
+ console.log("Nebulon Status");
18
+ console.log("");
19
+
20
+ keyValue("Capsule", getActiveCapsule());
21
+
22
+ let loggedIn = false;
23
+ let profile = null;
24
+ if (config.mode === "hosted" && config.auth && config.auth.token) {
25
+ try {
26
+ profile = await me(config.backendUrl, config.auth.token);
27
+ loggedIn = true;
28
+ } catch (error) {
29
+ loggedIn = false;
30
+ }
31
+ }
32
+
33
+ keyValue("LoggedIn", loggedIn ? "true" : "false");
34
+ keyValue(
35
+ "NebulonID",
36
+ loggedIn
37
+ ? profile.nebulonId || profile.handle || "none"
38
+ : "none"
39
+ );
40
+
41
+ if (!config.activeWallet) {
42
+ keyValue("SOL", "n/a");
43
+ keyValue("USDC", "n/a");
44
+ } else {
45
+ const keypair = loadWalletKeypair(config, config.activeWallet);
46
+ try {
47
+ const connection = getConnection(config.rpcUrl);
48
+ const sol = await getSolBalance(connection, keypair.publicKey);
49
+ const usdc = await getTokenBalance(
50
+ connection,
51
+ keypair.publicKey,
52
+ toPublicKey(config.usdcMint)
53
+ );
54
+ keyValue("SOL", `${formatAmount(sol, 6)} SOL`);
55
+ keyValue("USDC", `${formatAmount(usdc, 6)} USDC`);
56
+ } catch (error) {
57
+ keyValue("SOL", "unavailable");
58
+ keyValue("USDC", "unavailable");
59
+ }
60
+ }
61
+
62
+ console.log("");
63
+ keyValue("Mode", config.mode);
64
+ keyValue("Network", config.network);
65
+ console.log("");
66
+ keyValue("Pending Invites", "n/a");
67
+ keyValue("Active Contracts", "n/a");
68
+ };
69
+
70
+ module.exports = {
71
+ runStatus,
72
+ };
@@ -0,0 +1,169 @@
1
+ const nacl = require("tweetnacl");
2
+ const { Connection } = require("@solana/web3.js");
3
+ const { getAuthToken } = require("@magicblock-labs/ephemeral-rollups-sdk");
4
+ const { loadConfig } = require("../config");
5
+ const { loadWalletKeypair } = require("../wallets");
6
+
7
+ const normalizeEndpoint = (endpoint) => endpoint.replace(/\/$/, "");
8
+
9
+ const checkTeeAvailability = async (config, keypair, options = {}) => {
10
+ const base = normalizeEndpoint(
11
+ config.ephemeralTeeEndpoint ||
12
+ config.ephemeralPermissionEndpoint ||
13
+ "https://tee.magicblock.app"
14
+ );
15
+ try {
16
+ const auth = await getAuthToken(
17
+ base,
18
+ keypair.publicKey,
19
+ (message) => nacl.sign.detached(message, keypair.secretKey)
20
+ );
21
+ if (!auth?.token) {
22
+ return { ok: false, reason: "no_token" };
23
+ }
24
+ const rpcEndpoint = `${base}?token=${auth.token}`;
25
+ let wsEndpoint =
26
+ config.ephemeralTeeWsEndpoint ||
27
+ rpcEndpoint.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
28
+ if (!wsEndpoint.includes("token=")) {
29
+ wsEndpoint += wsEndpoint.includes("?")
30
+ ? `&token=${auth.token}`
31
+ : `?token=${auth.token}`;
32
+ }
33
+
34
+ const connection = new Connection(rpcEndpoint, {
35
+ commitment: "confirmed",
36
+ wsEndpoint,
37
+ });
38
+ await connection.getVersion();
39
+ let notified = false;
40
+ await new Promise((resolve) => {
41
+ const timeout = setTimeout(resolve, options.timeoutMs || 4000);
42
+ connection.onSlotChange((info) => {
43
+ if (!notified) {
44
+ notified = true;
45
+ clearTimeout(timeout);
46
+ resolve(info.slot);
47
+ }
48
+ });
49
+ });
50
+ if (!notified) {
51
+ return { ok: false, reason: "ws_timeout" };
52
+ }
53
+ return { ok: true };
54
+ } catch (error) {
55
+ if (options.debug) {
56
+ console.error(error);
57
+ }
58
+ return { ok: false, reason: "error" };
59
+ }
60
+ };
61
+
62
+ const runTestTee = async (options = {}) => {
63
+ const config = loadConfig();
64
+ if (!config.activeWallet) {
65
+ console.error("No active wallet. Run: nebulon init");
66
+ process.exit(1);
67
+ }
68
+ const keypair = loadWalletKeypair(config, config.activeWallet);
69
+ const base = normalizeEndpoint(
70
+ config.ephemeralTeeEndpoint ||
71
+ config.ephemeralPermissionEndpoint ||
72
+ "https://tee.magicblock.app"
73
+ );
74
+ const pubkey = keypair.publicKey.toBase58();
75
+
76
+ console.log("Testing TEE endpoints...");
77
+ console.log(`TEE base: ${base}`);
78
+ console.log(`Wallet: ${pubkey}`);
79
+
80
+ try {
81
+ const challengeUrl = `${base}/auth/challenge?pubkey=${pubkey}`;
82
+ const response = await fetch(challengeUrl);
83
+ const contentType = response.headers.get("content-type") || "unknown";
84
+ const bodyText = await response.text();
85
+ console.log(`Challenge status: ${response.status}`);
86
+ console.log(`Challenge content-type: ${contentType}`);
87
+ if (options.debug) {
88
+ console.log(
89
+ `Challenge body head: ${bodyText.slice(0, 200).replace(/\s+/g, " ")}`
90
+ );
91
+ }
92
+ } catch (error) {
93
+ console.error("Challenge request failed.");
94
+ if (options.debug) {
95
+ console.error(error);
96
+ }
97
+ }
98
+
99
+ let auth = null;
100
+ try {
101
+ auth = await getAuthToken(
102
+ base,
103
+ keypair.publicKey,
104
+ (message) => nacl.sign.detached(message, keypair.secretKey)
105
+ );
106
+ console.log("Auth token acquired.");
107
+ if (auth?.expiresAt) {
108
+ console.log(`Token expiresAt: ${auth.expiresAt}`);
109
+ }
110
+ } catch (error) {
111
+ console.error("Auth token request failed.");
112
+ if (options.debug) {
113
+ console.error(error);
114
+ }
115
+ return;
116
+ }
117
+
118
+ const token = auth?.token;
119
+ if (!token) {
120
+ console.error("No token returned from TEE.");
121
+ return;
122
+ }
123
+ const rpcEndpoint = `${base}?token=${token}`;
124
+ let wsEndpoint =
125
+ config.ephemeralTeeWsEndpoint ||
126
+ rpcEndpoint.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
127
+ if (!wsEndpoint.includes("token=")) {
128
+ wsEndpoint += wsEndpoint.includes("?") ? `&token=${token}` : `?token=${token}`;
129
+ }
130
+
131
+ console.log(`TEE RPC: ${rpcEndpoint}`);
132
+ console.log(`TEE WS: ${wsEndpoint}`);
133
+
134
+ try {
135
+ const connection = new Connection(rpcEndpoint, {
136
+ commitment: "confirmed",
137
+ wsEndpoint,
138
+ });
139
+ const version = await connection.getVersion();
140
+ console.log(`TEE RPC OK. Solana version: ${version["solana-core"]}`);
141
+
142
+ let notified = false;
143
+ const slotPromise = new Promise((resolve) => {
144
+ const timeout = setTimeout(resolve, 4000);
145
+ connection.onSlotChange((info) => {
146
+ if (!notified) {
147
+ notified = true;
148
+ clearTimeout(timeout);
149
+ console.log(`TEE WS OK. Slot: ${info.slot}`);
150
+ resolve();
151
+ }
152
+ });
153
+ });
154
+ await slotPromise;
155
+ if (!notified) {
156
+ console.warn("TEE WS did not emit a slot notification within 4s.");
157
+ }
158
+ } catch (error) {
159
+ console.error("TEE RPC check failed.");
160
+ if (options.debug) {
161
+ console.error(error);
162
+ }
163
+ }
164
+ };
165
+
166
+ module.exports = {
167
+ runTestTee,
168
+ checkTeeAvailability,
169
+ };
@@ -0,0 +1,166 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { prompt } = require("enquirer");
4
+ const { loadConfig, saveConfig } = require("../config");
5
+ const { getConnection, getSolBalance, getTokenBalance, toPublicKey, formatAmount } = require("../solana");
6
+ const { successMessage } = require("../ui");
7
+ const {
8
+ addWalletEntry,
9
+ generateWallet,
10
+ importWalletFromFile,
11
+ listWallets,
12
+ setActiveWallet,
13
+ loadWalletKeypair,
14
+ } = require("../wallets");
15
+
16
+ const runWalletInit = async () => {
17
+ const config = loadConfig();
18
+ const actionAnswer = await prompt({
19
+ type: "select",
20
+ name: "action",
21
+ message: "Wallet action",
22
+ choices: [
23
+ { name: "generate", message: "Generate new wallet" },
24
+ { name: "import", message: "Import keypair file" },
25
+ ],
26
+ });
27
+ const nameAnswer = await prompt({
28
+ type: "input",
29
+ name: "name",
30
+ message: "Wallet name",
31
+ initial: "default",
32
+ });
33
+ const name = nameAnswer.name.trim();
34
+
35
+ if (actionAnswer.action === "generate") {
36
+ const { path } = generateWallet(name);
37
+ addWalletEntry(config, name, path);
38
+ setActiveWallet(config, name);
39
+ saveConfig(config);
40
+ console.log(`Generated wallet: ${name}`);
41
+ return;
42
+ }
43
+
44
+ const fileAnswer = await prompt({
45
+ type: "input",
46
+ name: "path",
47
+ message: "Keypair file path",
48
+ validate: (value) => {
49
+ const trimmed = value.trim();
50
+ if (!trimmed || !fs.existsSync(trimmed)) {
51
+ return "File not found.";
52
+ }
53
+ return true;
54
+ },
55
+ });
56
+ const { path } = importWalletFromFile(name, fileAnswer.path.trim());
57
+ addWalletEntry(config, name, path);
58
+ setActiveWallet(config, name);
59
+ saveConfig(config);
60
+ console.log(`Imported wallet: ${name}`);
61
+ };
62
+
63
+ const runWalletList = () => {
64
+ const config = loadConfig();
65
+ const wallets = listWallets(config);
66
+ if (!wallets.length) {
67
+ console.log("No wallets configured.");
68
+ return;
69
+ }
70
+ wallets.forEach((wallet) => {
71
+ const suffix = wallet.active ? " (active)" : "";
72
+ console.log(`${wallet.name}${suffix}`);
73
+ });
74
+ };
75
+
76
+ const runWalletUse = (name) => {
77
+ const config = loadConfig();
78
+ setActiveWallet(config, name);
79
+ saveConfig(config);
80
+ console.log(`Active wallet: ${name}`);
81
+ };
82
+
83
+ const runWalletShow = (name) => {
84
+ const config = loadConfig();
85
+ const target = name || config.activeWallet;
86
+ if (!target) {
87
+ console.error("No wallet specified.");
88
+ process.exit(1);
89
+ }
90
+ const keypair = loadWalletKeypair(config, target);
91
+ console.log(keypair.publicKey.toBase58());
92
+ };
93
+
94
+ const runWalletBalance = async () => {
95
+ const config = loadConfig();
96
+ const target = config.activeWallet;
97
+ if (!target) {
98
+ console.error("No active wallet. Run: nebulon init");
99
+ process.exit(1);
100
+ }
101
+ const keypair = loadWalletKeypair(config, target);
102
+ const walletKey = keypair.publicKey;
103
+ const connection = getConnection(config.rpcUrl);
104
+ const mintKey = toPublicKey(config.usdcMint);
105
+ const [sol, usdc] = await Promise.all([
106
+ getSolBalance(connection, walletKey),
107
+ getTokenBalance(connection, walletKey, mintKey),
108
+ ]);
109
+ console.log(`SOL: ${formatAmount(sol, 6)} SOL`);
110
+ console.log(`USDC: ${formatAmount(usdc, 6)} USDC`);
111
+ };
112
+
113
+ const runWalletExport = async () => {
114
+ const config = loadConfig();
115
+ const target = config.activeWallet;
116
+ if (!target) {
117
+ console.error("No active wallet. Run: nebulon init");
118
+ process.exit(1);
119
+ }
120
+ const keypair = loadWalletKeypair(config, target);
121
+ const exportDir = path.join("C:\\", "Nebulon", "Wallets");
122
+ if (!fs.existsSync(exportDir)) {
123
+ fs.mkdirSync(exportDir, { recursive: true });
124
+ }
125
+ const nameAnswer = await prompt({
126
+ type: "input",
127
+ name: "filename",
128
+ message: "Export filename",
129
+ initial: `${target}.json`,
130
+ });
131
+ let filename = (nameAnswer.filename || "").trim();
132
+ if (!filename) {
133
+ console.error("Filename required.");
134
+ process.exit(1);
135
+ }
136
+ if (!filename.toLowerCase().endsWith(".json")) {
137
+ filename = `${filename}.json`;
138
+ }
139
+ const targetPath = path.join(exportDir, filename);
140
+ if (fs.existsSync(targetPath)) {
141
+ console.log("Warning: a file already exists with that name.");
142
+ const overwrite = await prompt({
143
+ type: "confirm",
144
+ name: "overwrite",
145
+ message: "File exists. Overwrite?",
146
+ initial: false,
147
+ });
148
+ if (!overwrite.overwrite) {
149
+ console.log("Export canceled.");
150
+ return;
151
+ }
152
+ }
153
+ fs.writeFileSync(targetPath, JSON.stringify(Array.from(keypair.secretKey)));
154
+ console.log(`Exported to ${targetPath}`);
155
+ console.log("Store securely and delete the file when you're done.");
156
+ successMessage("Wallet exported.");
157
+ };
158
+
159
+ module.exports = {
160
+ runWalletInit,
161
+ runWalletList,
162
+ runWalletUse,
163
+ runWalletShow,
164
+ runWalletBalance,
165
+ runWalletExport,
166
+ };
package/src/config.js ADDED
@@ -0,0 +1,80 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { capsuleConfigPath, capsuleRoot } = require("./paths");
4
+ const { getActiveCapsule, ensureCapsuleDirs } = require("./capsules");
5
+ const { DEFAULT_CONFIG } = require("./constants");
6
+
7
+ const ensureDirs = (capsuleName) => {
8
+ const capsule = getActiveCapsule(capsuleName);
9
+ ensureCapsuleDirs(capsule);
10
+ };
11
+
12
+ const mergeDefaults = (config) => {
13
+ return {
14
+ ...DEFAULT_CONFIG,
15
+ ...config,
16
+ wallets: config.wallets || {},
17
+ sessions: config.sessions || {},
18
+ privacy: {
19
+ ...(DEFAULT_CONFIG.privacy || {}),
20
+ ...(config.privacy || {}),
21
+ contractKeys: (config.privacy && config.privacy.contractKeys) || {},
22
+ },
23
+ auth: {
24
+ ...DEFAULT_CONFIG.auth,
25
+ ...(config.auth || {}),
26
+ },
27
+ };
28
+ };
29
+
30
+ const loadConfig = (capsuleName) => {
31
+ const capsule = getActiveCapsule(capsuleName);
32
+ ensureDirs(capsule);
33
+ const filePath = capsuleConfigPath(capsule);
34
+ if (!fs.existsSync(filePath)) {
35
+ const initial = mergeDefaults({});
36
+ fs.writeFileSync(filePath, JSON.stringify(initial, null, 2));
37
+ return initial;
38
+ }
39
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
40
+ return mergeDefaults(raw);
41
+ };
42
+
43
+ const saveConfig = (config, capsuleName) => {
44
+ const capsule = getActiveCapsule(capsuleName);
45
+ ensureDirs(capsule);
46
+ const filePath = capsuleConfigPath(capsule);
47
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
48
+ };
49
+
50
+ const setNestedValue = (obj, key, value) => {
51
+ const parts = key.split(".").filter(Boolean);
52
+ let current = obj;
53
+ for (let i = 0; i < parts.length - 1; i += 1) {
54
+ const part = parts[i];
55
+ if (!current[part] || typeof current[part] !== "object") {
56
+ current[part] = {};
57
+ }
58
+ current = current[part];
59
+ }
60
+ current[parts[parts.length - 1]] = value;
61
+ };
62
+
63
+ const resolveWalletPath = (walletEntry, capsuleName) => {
64
+ if (!walletEntry || !walletEntry.path) {
65
+ return null;
66
+ }
67
+ const capsule = getActiveCapsule(capsuleName);
68
+ const baseDir = capsuleRoot(capsule);
69
+ return path.isAbsolute(walletEntry.path)
70
+ ? walletEntry.path
71
+ : path.join(baseDir, walletEntry.path);
72
+ };
73
+
74
+ module.exports = {
75
+ ensureDirs,
76
+ loadConfig,
77
+ saveConfig,
78
+ setNestedValue,
79
+ resolveWalletPath,
80
+ };
@@ -0,0 +1,70 @@
1
+ const PROGRAM_ID_DEFAULT = "6UqkmQ2iCkf3acBB71DdXtVd49EyuaftMz8V3E74USbC";
2
+ const TUSDC_MINT_DEFAULT = "HwhrDAorVU2YSHUghNQgxHKBWJyYbvEYC9EC5XBoKNEM";
3
+ const USDC_MINT_MAINNET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
4
+
5
+ const NETWORK_PRESETS = {
6
+ localnet: {
7
+ rpcUrl: "http://127.0.0.1:8899",
8
+ wsUrl: "ws://127.0.0.1:8900",
9
+ usdcMint: TUSDC_MINT_DEFAULT,
10
+ },
11
+ devnet: {
12
+ rpcUrl: "https://api.devnet.solana.com",
13
+ wsUrl: "wss://api.devnet.solana.com",
14
+ usdcMint: TUSDC_MINT_DEFAULT,
15
+ },
16
+ mainnet: {
17
+ rpcUrl: "https://api.mainnet-beta.solana.com",
18
+ wsUrl: "wss://api.mainnet-beta.solana.com",
19
+ usdcMint: USDC_MINT_MAINNET,
20
+ },
21
+ };
22
+
23
+ const DEFAULT_CONFIG = {
24
+ mode: "hosted",
25
+ network: "localnet",
26
+ rpcUrl: NETWORK_PRESETS.localnet.rpcUrl,
27
+ wsUrl: NETWORK_PRESETS.localnet.wsUrl,
28
+ programId: PROGRAM_ID_DEFAULT,
29
+ backendUrl: "http://174.138.42.117:3333",
30
+ usdcMint: NETWORK_PRESETS.localnet.usdcMint,
31
+ programIdSource: "custom",
32
+ usdcMintSource: "custom",
33
+ rpcUrlSource: "custom",
34
+ wsUrlSource: "custom",
35
+ ephemeralProviderUrlSource: "custom",
36
+ ephemeralWsUrlSource: "custom",
37
+ ephemeralValidatorIdentitySource: "custom",
38
+ ephemeralPermissionEndpointSource: "custom",
39
+ ephemeralTeeEndpointSource: "custom",
40
+ ephemeralTeeWsEndpointSource: "custom",
41
+ networkSource: "custom",
42
+ backendSource: "official",
43
+ ephemeralProviderUrl: "",
44
+ ephemeralWsUrl: "",
45
+ ephemeralValidatorIdentity: "",
46
+ ephemeralPermissionEndpoint: "",
47
+ ephemeralTeeEndpoint: "",
48
+ ephemeralTeeWsEndpoint: "",
49
+ activeWallet: null,
50
+ wallets: {},
51
+ sessions: {},
52
+ privacy: {
53
+ contractKeys: {},
54
+ },
55
+ auth: {
56
+ token: null,
57
+ wallet: null,
58
+ handle: null,
59
+ role: null,
60
+ lastAuthAt: null,
61
+ },
62
+ };
63
+
64
+ module.exports = {
65
+ DEFAULT_CONFIG,
66
+ NETWORK_PRESETS,
67
+ PROGRAM_ID_DEFAULT,
68
+ TUSDC_MINT_DEFAULT,
69
+ USDC_MINT_MAINNET,
70
+ };