@shuffle-protocol/sdk 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.
- package/README.md +119 -0
- package/dist/cli/commands.d.ts +54 -0
- package/dist/cli/commands.js +1229 -0
- package/dist/cli/config.d.ts +50 -0
- package/dist/cli/config.js +247 -0
- package/dist/cli/devnet.d.ts +81 -0
- package/dist/cli/devnet.js +106 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +155 -0
- package/dist/cli/output.d.ts +102 -0
- package/dist/cli/output.js +251 -0
- package/dist/client.d.ts +121 -0
- package/dist/client.js +691 -0
- package/dist/constants.d.ts +30 -0
- package/dist/constants.js +61 -0
- package/dist/encryption.d.ts +24 -0
- package/dist/encryption.js +90 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +45 -0
- package/dist/idl/shuffle_protocol.json +6333 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +42 -0
- package/dist/pda.d.ts +7 -0
- package/dist/pda.js +59 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +7 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Command Implementations
|
|
4
|
+
*
|
|
5
|
+
* Each command function handles both real and mock mode operations.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.initCommand = initCommand;
|
|
45
|
+
exports.balanceCommand = balanceCommand;
|
|
46
|
+
exports.depositCommand = depositCommand;
|
|
47
|
+
exports.withdrawCommand = withdrawCommand;
|
|
48
|
+
exports.transferCommand = transferCommand;
|
|
49
|
+
exports.orderCommand = orderCommand;
|
|
50
|
+
exports.settleCommand = settleCommand;
|
|
51
|
+
exports.executeCommand = executeCommand;
|
|
52
|
+
exports.statusCommand = statusCommand;
|
|
53
|
+
exports.faucetCommand = faucetCommand;
|
|
54
|
+
exports.airdropCommand = airdropCommand;
|
|
55
|
+
exports.historyCommand = historyCommand;
|
|
56
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
57
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
58
|
+
const config_1 = require("./config");
|
|
59
|
+
const output_1 = require("./output");
|
|
60
|
+
const devnet_1 = require("./devnet");
|
|
61
|
+
const constants_1 = require("../constants");
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// ACCOUNT COMMANDS
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* shuffle init - Create privacy account
|
|
67
|
+
*/
|
|
68
|
+
async function initCommand() {
|
|
69
|
+
const config = (0, config_1.getConfig)();
|
|
70
|
+
if (config.mockMode) {
|
|
71
|
+
(0, output_1.printMockWarning)();
|
|
72
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
73
|
+
"Generating encryption keypair...",
|
|
74
|
+
"Creating privacy account on-chain...",
|
|
75
|
+
"Initializing encrypted balances...",
|
|
76
|
+
]);
|
|
77
|
+
progress.start();
|
|
78
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
79
|
+
progress.nextStep();
|
|
80
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
81
|
+
progress.nextStep();
|
|
82
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
83
|
+
(0, devnet_1.updateMockState)({ accountExists: true });
|
|
84
|
+
progress.succeed("Privacy account created!");
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(chalk_1.default.gray(` Wallet: ${config.wallet.publicKey.toBase58().slice(0, 20)}...`));
|
|
87
|
+
console.log(chalk_1.default.gray(` Network: ${config.network}`));
|
|
88
|
+
console.log();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Real mode
|
|
92
|
+
if (!config.shuffleClient) {
|
|
93
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const exists = await config.shuffleClient.accountExists();
|
|
98
|
+
if (exists) {
|
|
99
|
+
(0, output_1.printInfo)("Privacy account already exists!");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const sig = await (0, output_1.withSpinner)("Creating privacy account...", () => config.shuffleClient.createUserAccount(), "Privacy account created!");
|
|
103
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
(0, output_1.printError)(e.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* shuffle balance - View encrypted balances
|
|
111
|
+
*/
|
|
112
|
+
async function balanceCommand() {
|
|
113
|
+
const config = (0, config_1.getConfig)();
|
|
114
|
+
if (config.mockMode) {
|
|
115
|
+
(0, output_1.printMockWarning)();
|
|
116
|
+
const state = (0, devnet_1.getMockState)();
|
|
117
|
+
if (!state.accountExists) {
|
|
118
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await (0, output_1.withSpinner)("Decrypting balances with MPC...", () => (0, devnet_1.mockDelay)("fast"), "Balances decrypted!");
|
|
122
|
+
// Mock unshielded balances (0 for demo)
|
|
123
|
+
const unshielded = { usdc: 0n, tsla: 0n, spy: 0n, aapl: 0n };
|
|
124
|
+
// Mock pending payout (if there's an executed order, show payout)
|
|
125
|
+
// For demo: if pendingOrder exists and batch was "executed" (batchId > order.batchId)
|
|
126
|
+
const pendingPayout = state.pendingOrder && state.batchId > state.pendingOrder.batchId
|
|
127
|
+
? { amount: state.pendingOrder.amount / 10n, assetId: state.pendingOrder.pairId === 0 ? 1 : 2 }
|
|
128
|
+
: null;
|
|
129
|
+
(0, output_1.printBalanceTable)(state.balances, unshielded, pendingPayout);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Real mode
|
|
133
|
+
if (!config.shuffleClient) {
|
|
134
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
// Fetch shielded, unshielded, and estimated payout (for lazy settlement)
|
|
139
|
+
const [shielded, unshielded, estimatedPayout] = await (0, output_1.withSpinner)("Fetching balances...", async () => {
|
|
140
|
+
const shieldedBalances = await config.shuffleClient.getBalance();
|
|
141
|
+
const unshieldedBalances = await config.shuffleClient.getUnshieldedBalances();
|
|
142
|
+
const payout = await config.shuffleClient.estimatePayout();
|
|
143
|
+
return [shieldedBalances, unshieldedBalances, payout];
|
|
144
|
+
}, "Balances loaded!");
|
|
145
|
+
// Pass pending payout to display it directly in the balance (cyan color)
|
|
146
|
+
const pendingPayout = estimatedPayout
|
|
147
|
+
? { amount: estimatedPayout.estimatedPayout, assetId: estimatedPayout.outputAssetId }
|
|
148
|
+
: null;
|
|
149
|
+
(0, output_1.printBalanceTable)(shielded, unshielded, pendingPayout);
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
// If account doesn't exist, show only unshielded
|
|
153
|
+
if (e.message?.includes("Account does not exist") || e.message?.includes("not found")) {
|
|
154
|
+
try {
|
|
155
|
+
const unshielded = await config.shuffleClient.getUnshieldedBalances();
|
|
156
|
+
console.log(chalk_1.default.yellow("\n ⚠ No shielded account. Run 'shuffle init' to create one.\n"));
|
|
157
|
+
(0, output_1.printBalanceTable)({ usdc: BigInt(0), tsla: BigInt(0), spy: BigInt(0), aapl: BigInt(0) }, unshielded);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
(0, output_1.printError)(e.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// TOKEN COMMANDS
|
|
170
|
+
// ============================================================================
|
|
171
|
+
/**
|
|
172
|
+
* Parse asset name to AssetId
|
|
173
|
+
*/
|
|
174
|
+
function parseAsset(asset) {
|
|
175
|
+
const upper = asset.toUpperCase();
|
|
176
|
+
const mapping = {
|
|
177
|
+
USDC: constants_1.AssetId.USDC,
|
|
178
|
+
TSLA: constants_1.AssetId.TSLA,
|
|
179
|
+
SPY: constants_1.AssetId.SPY,
|
|
180
|
+
AAPL: constants_1.AssetId.AAPL,
|
|
181
|
+
};
|
|
182
|
+
if (!(upper in mapping)) {
|
|
183
|
+
throw new Error(`Invalid asset: ${asset}. Use: USDC, TSLA, SPY, AAPL`);
|
|
184
|
+
}
|
|
185
|
+
return mapping[upper];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* shuffle deposit <asset> <amount>
|
|
189
|
+
*/
|
|
190
|
+
async function depositCommand(asset, amountStr) {
|
|
191
|
+
const config = (0, config_1.getConfig)();
|
|
192
|
+
const amount = parseFloat(amountStr);
|
|
193
|
+
if (isNaN(amount) || amount <= 0) {
|
|
194
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let assetId;
|
|
198
|
+
try {
|
|
199
|
+
assetId = parseAsset(asset);
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
(0, output_1.printError)(e.message);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000)); // 6 decimals
|
|
206
|
+
if (config.mockMode) {
|
|
207
|
+
(0, output_1.printMockWarning)();
|
|
208
|
+
const state = (0, devnet_1.getMockState)();
|
|
209
|
+
if (!state.accountExists) {
|
|
210
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
214
|
+
`Encrypting ${amount} ${asset}...`,
|
|
215
|
+
"Transferring tokens to protocol vault...",
|
|
216
|
+
"Updating encrypted balance via MPC...",
|
|
217
|
+
]);
|
|
218
|
+
progress.start();
|
|
219
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
220
|
+
progress.nextStep();
|
|
221
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
222
|
+
progress.nextStep();
|
|
223
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
224
|
+
// Update mock balance
|
|
225
|
+
const key = asset.toLowerCase();
|
|
226
|
+
state.balances[key] += amountRaw;
|
|
227
|
+
(0, devnet_1.updateMockState)({ balances: state.balances });
|
|
228
|
+
progress.succeed(`Shielded ${amount.toLocaleString()} ${asset}!`);
|
|
229
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Real mode
|
|
233
|
+
if (!config.shuffleClient) {
|
|
234
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const sig = await (0, output_1.withSpinner)(`Shielding ${amount} ${asset}...`, () => config.shuffleClient.deposit(assetId, Math.floor(amount * 1000000)), `Shielded ${amount.toLocaleString()} ${asset}!`);
|
|
239
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
// Provide user-friendly error messages
|
|
243
|
+
const msg = e.message || "";
|
|
244
|
+
if (msg.includes("Unknown action") || msg.includes("undefined")) {
|
|
245
|
+
(0, output_1.printError)(`No ${asset} tokens in your wallet. Use 'shuffle faucet ${amount}' first!`);
|
|
246
|
+
}
|
|
247
|
+
else if (msg.includes("insufficient") || msg.includes("0x1")) {
|
|
248
|
+
(0, output_1.printError)(`Insufficient ${asset} balance. Get tokens with 'shuffle faucet <amount>' first.`);
|
|
249
|
+
}
|
|
250
|
+
else if (msg.includes("Account does not exist")) {
|
|
251
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
(0, output_1.printError)(msg);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* shuffle withdraw <asset> <amount>
|
|
260
|
+
*/
|
|
261
|
+
async function withdrawCommand(asset, amountStr) {
|
|
262
|
+
const config = (0, config_1.getConfig)();
|
|
263
|
+
const amount = parseFloat(amountStr);
|
|
264
|
+
if (isNaN(amount) || amount <= 0) {
|
|
265
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
let assetId;
|
|
269
|
+
try {
|
|
270
|
+
assetId = parseAsset(asset);
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
(0, output_1.printError)(e.message);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000));
|
|
277
|
+
if (config.mockMode) {
|
|
278
|
+
(0, output_1.printMockWarning)();
|
|
279
|
+
const state = (0, devnet_1.getMockState)();
|
|
280
|
+
if (!state.accountExists) {
|
|
281
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const key = asset.toLowerCase();
|
|
285
|
+
if (state.balances[key] < amountRaw) {
|
|
286
|
+
(0, output_1.printError)(`Insufficient ${asset} balance.`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
290
|
+
"Proving withdrawal via MPC...",
|
|
291
|
+
"Decrypting balance check...",
|
|
292
|
+
`Transferring ${amount} ${asset} to your wallet...`,
|
|
293
|
+
]);
|
|
294
|
+
progress.start();
|
|
295
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
296
|
+
progress.nextStep();
|
|
297
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
298
|
+
progress.nextStep();
|
|
299
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
300
|
+
state.balances[key] -= amountRaw;
|
|
301
|
+
(0, devnet_1.updateMockState)({ balances: state.balances });
|
|
302
|
+
progress.succeed(`Unshielded ${amount.toLocaleString()} ${asset}!`);
|
|
303
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Real mode
|
|
307
|
+
if (!config.shuffleClient) {
|
|
308
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
// Check shielded balance before attempting withdraw
|
|
313
|
+
const assetLabels = {
|
|
314
|
+
[constants_1.AssetId.USDC]: 'usdc',
|
|
315
|
+
[constants_1.AssetId.TSLA]: 'tsla',
|
|
316
|
+
[constants_1.AssetId.SPY]: 'spy',
|
|
317
|
+
[constants_1.AssetId.AAPL]: 'aapl',
|
|
318
|
+
};
|
|
319
|
+
const [shielded, unshielded] = await Promise.all([
|
|
320
|
+
config.shuffleClient.getBalance(),
|
|
321
|
+
config.shuffleClient.getUnshieldedBalances(),
|
|
322
|
+
]);
|
|
323
|
+
const assetKey = assetLabels[assetId];
|
|
324
|
+
const shieldedBalance = shielded[assetKey];
|
|
325
|
+
const unshieldedBalance = unshielded[assetKey];
|
|
326
|
+
const shieldedFormatted = (Number(shieldedBalance) / 1000000).toFixed(2);
|
|
327
|
+
const unshieldedFormatted = (Number(unshieldedBalance) / 1000000).toFixed(2);
|
|
328
|
+
if (shieldedBalance < amountRaw) {
|
|
329
|
+
console.log();
|
|
330
|
+
(0, output_1.printError)(`Insufficient shielded ${asset.toUpperCase()} balance!`);
|
|
331
|
+
console.log(chalk_1.default.gray(`\n Your shielded balance: ${chalk_1.default.white(shieldedFormatted)} ${asset.toUpperCase()}`));
|
|
332
|
+
console.log(chalk_1.default.gray(` Requested amount: ${chalk_1.default.white(amount.toFixed(2))} ${asset.toUpperCase()}\n`));
|
|
333
|
+
if (shieldedBalance > 0n) {
|
|
334
|
+
console.log(chalk_1.default.yellow(` 💡 You can unshield up to ${shieldedFormatted} ${asset.toUpperCase()}`));
|
|
335
|
+
console.log(chalk_1.default.gray(` Try: shuffle unshield ${asset.toLowerCase()} ${shieldedFormatted}\n`));
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const sig = await (0, output_1.withSpinner)(`Unshielding ${amount} ${asset}...`, () => config.shuffleClient.withdraw(assetId, Math.floor(amount * 1000000)), `Unshielded ${amount.toLocaleString()} ${asset}!`);
|
|
340
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
// Provide user-friendly error messages
|
|
344
|
+
const msg = e.message || "";
|
|
345
|
+
if (msg.includes("Unknown action") || msg.includes("undefined")) {
|
|
346
|
+
(0, output_1.printError)(`Insufficient ${asset} balance! Check your balance with 'shuffle balance'.`);
|
|
347
|
+
}
|
|
348
|
+
else if (msg.includes("insufficient") || msg.includes("0x1")) {
|
|
349
|
+
(0, output_1.printError)(`Insufficient ${asset} balance to withdraw ${amount}. Check your balance first.`);
|
|
350
|
+
}
|
|
351
|
+
else if (msg.includes("Account does not exist")) {
|
|
352
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
353
|
+
}
|
|
354
|
+
else if (msg.includes("custom program error")) {
|
|
355
|
+
(0, output_1.printError)(`Cannot withdraw ${amount} ${asset}. Your balance may be too low.`);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
(0, output_1.printError)(msg);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* shuffle transfer <address> <amount>
|
|
364
|
+
*/
|
|
365
|
+
async function transferCommand(address, amountStr) {
|
|
366
|
+
const config = (0, config_1.getConfig)();
|
|
367
|
+
const amount = parseFloat(amountStr);
|
|
368
|
+
if (isNaN(amount) || amount <= 0) {
|
|
369
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
let recipientPubkey;
|
|
373
|
+
try {
|
|
374
|
+
recipientPubkey = new web3_js_1.PublicKey(address);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
(0, output_1.printError)("Invalid Solana address.");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000));
|
|
381
|
+
if (config.mockMode) {
|
|
382
|
+
(0, output_1.printMockWarning)();
|
|
383
|
+
const state = (0, devnet_1.getMockState)();
|
|
384
|
+
if (!state.accountExists) {
|
|
385
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (state.balances.usdc < amountRaw) {
|
|
389
|
+
(0, output_1.printError)("Insufficient USDC balance.");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
393
|
+
"Encrypting transfer amount...",
|
|
394
|
+
"Computing private transfer via MPC...",
|
|
395
|
+
"Updating both accounts' balances...",
|
|
396
|
+
]);
|
|
397
|
+
progress.start();
|
|
398
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
399
|
+
progress.nextStep();
|
|
400
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
401
|
+
progress.nextStep();
|
|
402
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
403
|
+
state.balances.usdc -= amountRaw;
|
|
404
|
+
(0, devnet_1.updateMockState)({ balances: state.balances });
|
|
405
|
+
progress.succeed(`Transferred ${amount.toLocaleString()} USDC privately!`);
|
|
406
|
+
console.log(chalk_1.default.gray(` To: ${address.slice(0, 20)}...`));
|
|
407
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Real mode
|
|
411
|
+
if (!config.shuffleClient) {
|
|
412
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
// Check shielded USDC balance before transfer
|
|
417
|
+
const [shielded, unshielded] = await Promise.all([
|
|
418
|
+
config.shuffleClient.getBalance(),
|
|
419
|
+
config.shuffleClient.getUnshieldedBalances(),
|
|
420
|
+
]);
|
|
421
|
+
const shieldedBalance = shielded.usdc;
|
|
422
|
+
const unshieldedBalance = unshielded.usdc;
|
|
423
|
+
const shieldedFormatted = (Number(shieldedBalance) / 1000000).toFixed(2);
|
|
424
|
+
const unshieldedFormatted = (Number(unshieldedBalance) / 1000000).toFixed(2);
|
|
425
|
+
if (shieldedBalance < amountRaw) {
|
|
426
|
+
console.log();
|
|
427
|
+
(0, output_1.printError)(`Insufficient shielded USDC balance!`);
|
|
428
|
+
console.log(chalk_1.default.gray(`\n Your shielded balance: ${chalk_1.default.white(shieldedFormatted)} USDC`));
|
|
429
|
+
console.log(chalk_1.default.gray(` Requested amount: ${chalk_1.default.white(amount.toFixed(2))} USDC\n`));
|
|
430
|
+
if (unshieldedBalance > 0n) {
|
|
431
|
+
console.log(chalk_1.default.yellow(` 💡 You have ${unshieldedFormatted} USDC unshielded in your wallet.`));
|
|
432
|
+
console.log(chalk_1.default.gray(` Shield it first with: shuffle shield usdc ${unshieldedFormatted}\n`));
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const sig = await (0, output_1.withSpinner)(`Transferring ${amount} USDC privately...`, () => config.shuffleClient.transfer(recipientPubkey, Math.floor(amount * 1000000)), `Transferred ${amount.toLocaleString()} USDC!`);
|
|
437
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
// Provide user-friendly error messages
|
|
441
|
+
const msg = e.message || "";
|
|
442
|
+
if (msg.includes("Unknown action") || msg.includes("undefined")) {
|
|
443
|
+
(0, output_1.printError)(`Insufficient USDC balance for transfer! Check your balance with 'shuffle balance'.`);
|
|
444
|
+
}
|
|
445
|
+
else if (msg.includes("insufficient") || msg.includes("0x1")) {
|
|
446
|
+
(0, output_1.printError)(`Insufficient USDC balance to transfer ${amount}. Check your balance first.`);
|
|
447
|
+
}
|
|
448
|
+
else if (msg.includes("Account does not exist") || msg.includes("not found")) {
|
|
449
|
+
(0, output_1.printError)("Recipient doesn't have a Shuffle account. They need to run 'shuffle init' first.");
|
|
450
|
+
}
|
|
451
|
+
else if (msg.includes("invalid public key")) {
|
|
452
|
+
(0, output_1.printError)("Invalid recipient address. Please check and try again.");
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
(0, output_1.printError)(msg);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// ============================================================================
|
|
460
|
+
// TRADING COMMANDS
|
|
461
|
+
// ============================================================================
|
|
462
|
+
/**
|
|
463
|
+
* Parse pair name to PairId
|
|
464
|
+
*/
|
|
465
|
+
function parsePair(pair) {
|
|
466
|
+
const upper = pair.toUpperCase().replace("/", "_");
|
|
467
|
+
const mapping = {
|
|
468
|
+
TSLA_USDC: constants_1.PairId.TSLA_USDC,
|
|
469
|
+
SPY_USDC: constants_1.PairId.SPY_USDC,
|
|
470
|
+
AAPL_USDC: constants_1.PairId.AAPL_USDC,
|
|
471
|
+
TSLA_SPY: constants_1.PairId.TSLA_SPY,
|
|
472
|
+
TSLA_AAPL: constants_1.PairId.TSLA_AAPL,
|
|
473
|
+
SPY_AAPL: constants_1.PairId.SPY_AAPL,
|
|
474
|
+
};
|
|
475
|
+
if (!(upper in mapping)) {
|
|
476
|
+
throw new Error(`Invalid pair: ${pair}. Use: TSLA_USDC, SPY_USDC, AAPL_USDC, TSLA_SPY, TSLA_AAPL, SPY_AAPL`);
|
|
477
|
+
}
|
|
478
|
+
return mapping[upper];
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Parse direction string to Direction enum
|
|
482
|
+
*/
|
|
483
|
+
function parseDirection(dir) {
|
|
484
|
+
const lower = dir.toLowerCase();
|
|
485
|
+
// "buy" means buying the BASE token (e.g., TSLA in TSLA/USDC)
|
|
486
|
+
// That means selling the QUOTE token (USDC) → BtoA direction
|
|
487
|
+
if (lower === "buy" || lower === "b_to_a" || lower === "1") {
|
|
488
|
+
return constants_1.Direction.BtoA;
|
|
489
|
+
}
|
|
490
|
+
// "sell" means selling the BASE token (e.g., TSLA in TSLA/USDC)
|
|
491
|
+
// That means buying the QUOTE token (USDC) → AtoB direction
|
|
492
|
+
if (lower === "sell" || lower === "a_to_b" || lower === "0") {
|
|
493
|
+
return constants_1.Direction.AtoB;
|
|
494
|
+
}
|
|
495
|
+
throw new Error(`Invalid direction: ${dir}. Use: buy, sell`);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Infer source asset from pair and direction
|
|
499
|
+
*/
|
|
500
|
+
function inferSourceAsset(pairId, direction) {
|
|
501
|
+
// Pair definitions: [baseAsset, quoteAsset]
|
|
502
|
+
const pairAssets = {
|
|
503
|
+
[constants_1.PairId.TSLA_USDC]: [constants_1.AssetId.TSLA, constants_1.AssetId.USDC],
|
|
504
|
+
[constants_1.PairId.SPY_USDC]: [constants_1.AssetId.SPY, constants_1.AssetId.USDC],
|
|
505
|
+
[constants_1.PairId.AAPL_USDC]: [constants_1.AssetId.AAPL, constants_1.AssetId.USDC],
|
|
506
|
+
[constants_1.PairId.TSLA_SPY]: [constants_1.AssetId.TSLA, constants_1.AssetId.SPY],
|
|
507
|
+
[constants_1.PairId.TSLA_AAPL]: [constants_1.AssetId.TSLA, constants_1.AssetId.AAPL],
|
|
508
|
+
[constants_1.PairId.SPY_AAPL]: [constants_1.AssetId.SPY, constants_1.AssetId.AAPL],
|
|
509
|
+
};
|
|
510
|
+
const [base, quote] = pairAssets[pairId];
|
|
511
|
+
// AtoB = selling A (base) for B (quote), so source is base
|
|
512
|
+
// BtoA = selling B (quote) for A (base), so source is quote
|
|
513
|
+
return direction === constants_1.Direction.AtoB ? base : quote;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* shuffle order [pair] [direction] [amount]
|
|
517
|
+
* Interactive mode if no args provided
|
|
518
|
+
*/
|
|
519
|
+
async function orderCommand(pair, direction, amountStr) {
|
|
520
|
+
const config = (0, config_1.getConfig)();
|
|
521
|
+
// If all args provided, use direct mode
|
|
522
|
+
if (pair && direction && amountStr) {
|
|
523
|
+
return executeDirectOrder(config, pair, direction, amountStr);
|
|
524
|
+
}
|
|
525
|
+
// Interactive mode
|
|
526
|
+
return executeInteractiveOrder(config);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Execute order with direct CLI arguments (original behavior)
|
|
530
|
+
*/
|
|
531
|
+
async function executeDirectOrder(config, pair, direction, amountStr) {
|
|
532
|
+
const amount = parseFloat(amountStr);
|
|
533
|
+
if (isNaN(amount) || amount <= 0) {
|
|
534
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
let pairId;
|
|
538
|
+
let dir;
|
|
539
|
+
try {
|
|
540
|
+
pairId = parsePair(pair);
|
|
541
|
+
dir = parseDirection(direction);
|
|
542
|
+
}
|
|
543
|
+
catch (e) {
|
|
544
|
+
(0, output_1.printError)(e.message);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000));
|
|
548
|
+
const sourceAsset = inferSourceAsset(pairId, dir);
|
|
549
|
+
if (config.mockMode) {
|
|
550
|
+
(0, output_1.printMockWarning)();
|
|
551
|
+
const state = (0, devnet_1.getMockState)();
|
|
552
|
+
if (!state.accountExists) {
|
|
553
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (state.pendingOrder) {
|
|
557
|
+
(0, output_1.printError)("You have a pending order. Settle it first with 'shuffle settle'.");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
561
|
+
"Encrypting order details...",
|
|
562
|
+
"Submitting to batch aggregator...",
|
|
563
|
+
"Accumulating in MPC cluster...",
|
|
564
|
+
"Order encrypted and queued!",
|
|
565
|
+
]);
|
|
566
|
+
progress.start();
|
|
567
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
568
|
+
progress.nextStep();
|
|
569
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
570
|
+
progress.nextStep();
|
|
571
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
572
|
+
progress.nextStep();
|
|
573
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
574
|
+
(0, devnet_1.updateMockState)({
|
|
575
|
+
pendingOrder: {
|
|
576
|
+
batchId: state.batchId,
|
|
577
|
+
pairId,
|
|
578
|
+
direction: dir,
|
|
579
|
+
amount: amountRaw,
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
progress.succeed("Order placed!");
|
|
583
|
+
console.log();
|
|
584
|
+
console.log(chalk_1.default.gray(` Pair: ${pair.toUpperCase()}`));
|
|
585
|
+
console.log(chalk_1.default.gray(` Direction: ${direction === "buy" ? "BUY" : "SELL"}`));
|
|
586
|
+
console.log(chalk_1.default.gray(` Amount: ${amount.toLocaleString()}`));
|
|
587
|
+
console.log(chalk_1.default.gray(` Batch: #${state.batchId}`));
|
|
588
|
+
console.log();
|
|
589
|
+
(0, output_1.printSuccess)("Your order is encrypted and hidden until batch execution!");
|
|
590
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
// Real mode
|
|
594
|
+
if (!config.shuffleClient) {
|
|
595
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
// Check if user already has a pending order
|
|
600
|
+
const existingOrder = await config.shuffleClient.getDecryptedOrder();
|
|
601
|
+
if (existingOrder) {
|
|
602
|
+
const pairLabels = ["TSLA/USDC", "SPY/USDC", "AAPL/USDC"];
|
|
603
|
+
const dirLabel = existingOrder.direction === 0 ? "BUY" : "SELL";
|
|
604
|
+
const orderAmount = (Number(existingOrder.amount) / 1000000).toFixed(2);
|
|
605
|
+
console.log();
|
|
606
|
+
(0, output_1.printError)("You already have a pending order!");
|
|
607
|
+
console.log(chalk_1.default.gray(`\n Pending order: ${chalk_1.default.white(dirLabel)} ${pairLabels[existingOrder.pairId] || "Unknown"}`));
|
|
608
|
+
console.log(chalk_1.default.gray(` Amount: ${chalk_1.default.white(orderAmount)} USDC`));
|
|
609
|
+
console.log(chalk_1.default.gray(` Batch ID: ${chalk_1.default.white(existingOrder.batchId)}\n`));
|
|
610
|
+
console.log(chalk_1.default.yellow(` 💡 Wait for batch execution, then settle with: shuffle settle`));
|
|
611
|
+
console.log(chalk_1.default.gray(` Check status with: shuffle status\n`));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// Check shielded USDC balance before placing order
|
|
615
|
+
// Orders use USDC as the source asset (buying TSLA/SPY with USDC)
|
|
616
|
+
const [shielded, unshielded] = await Promise.all([
|
|
617
|
+
config.shuffleClient.getBalance(),
|
|
618
|
+
config.shuffleClient.getUnshieldedBalances(),
|
|
619
|
+
]);
|
|
620
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000));
|
|
621
|
+
const shieldedBalance = shielded.usdc;
|
|
622
|
+
const unshieldedBalance = unshielded.usdc;
|
|
623
|
+
const shieldedFormatted = (Number(shieldedBalance) / 1000000).toFixed(2);
|
|
624
|
+
const unshieldedFormatted = (Number(unshieldedBalance) / 1000000).toFixed(2);
|
|
625
|
+
if (shieldedBalance < amountRaw) {
|
|
626
|
+
console.log();
|
|
627
|
+
(0, output_1.printError)(`Insufficient shielded USDC balance for this order!`);
|
|
628
|
+
console.log(chalk_1.default.gray(`\n Your shielded balance: ${chalk_1.default.white(shieldedFormatted)} USDC`));
|
|
629
|
+
console.log(chalk_1.default.gray(` Order amount: ${chalk_1.default.white(amount.toFixed(2))} USDC\n`));
|
|
630
|
+
if (unshieldedBalance > 0n) {
|
|
631
|
+
console.log(chalk_1.default.yellow(` 💡 You have ${unshieldedFormatted} USDC unshielded in your wallet.`));
|
|
632
|
+
console.log(chalk_1.default.gray(` Shield it first with: shuffle shield usdc ${unshieldedFormatted}\n`));
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
console.log(chalk_1.default.yellow(` 💡 Get USDC with: shuffle faucet ${amount}`));
|
|
636
|
+
console.log(chalk_1.default.gray(` Then shield it: shuffle shield usdc ${amount}\n`));
|
|
637
|
+
}
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const sig = await (0, output_1.withSpinner)("Placing encrypted order...", () => config.shuffleClient.placeOrder(pairId, dir, Math.floor(amount * 1000000), sourceAsset), "Order placed!");
|
|
641
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
642
|
+
}
|
|
643
|
+
catch (e) {
|
|
644
|
+
// Provide user-friendly error messages
|
|
645
|
+
const msg = e.message || "";
|
|
646
|
+
if (msg.includes("Unknown action") || msg.includes("undefined")) {
|
|
647
|
+
(0, output_1.printError)("You already have a pending order! Wait for batch execution or settle first with 'shuffle settle'.");
|
|
648
|
+
}
|
|
649
|
+
else if (msg.includes("already in use") || msg.includes("0x0")) {
|
|
650
|
+
(0, output_1.printError)("You already have a pending order in this batch. Wait for it to be settled.");
|
|
651
|
+
}
|
|
652
|
+
else if (msg.includes("insufficient") || msg.includes("balance")) {
|
|
653
|
+
(0, output_1.printError)("Insufficient balance for this order. Check your balance with 'shuffle balance'.");
|
|
654
|
+
}
|
|
655
|
+
else if (msg.includes("Account does not exist")) {
|
|
656
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
(0, output_1.printError)(msg);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Interactive order flow - asks user for each parameter
|
|
665
|
+
*/
|
|
666
|
+
async function executeInteractiveOrder(config) {
|
|
667
|
+
// @ts-ignore - inquirer types available after npm install
|
|
668
|
+
const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
|
|
669
|
+
(0, output_1.printHeader)("New Order");
|
|
670
|
+
console.log(chalk_1.default.gray(" Let's set up your private order!\n"));
|
|
671
|
+
// Check connection
|
|
672
|
+
if (!config.mockMode && !config.shuffleClient) {
|
|
673
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// Check for existing pending order BEFORE interactive prompts
|
|
677
|
+
if (!config.mockMode && config.shuffleClient) {
|
|
678
|
+
try {
|
|
679
|
+
const existingOrder = await config.shuffleClient.getDecryptedOrder();
|
|
680
|
+
if (existingOrder) {
|
|
681
|
+
const pairLabels = ["TSLA/USDC", "SPY/USDC", "AAPL/USDC"];
|
|
682
|
+
const dirLabel = existingOrder.direction === 0 ? "BUY" : "SELL";
|
|
683
|
+
const orderAmount = (Number(existingOrder.amount) / 1000000).toFixed(2);
|
|
684
|
+
(0, output_1.printError)("You already have a pending order!");
|
|
685
|
+
console.log(chalk_1.default.gray(`\n Pending order: ${chalk_1.default.white(dirLabel)} ${pairLabels[existingOrder.pairId] || "Unknown"}`));
|
|
686
|
+
console.log(chalk_1.default.gray(` Amount: ${chalk_1.default.white(orderAmount)} USDC`));
|
|
687
|
+
console.log(chalk_1.default.gray(` Batch ID: ${chalk_1.default.white(existingOrder.batchId)}\n`));
|
|
688
|
+
console.log(chalk_1.default.yellow(` 💡 Wait for batch execution, then settle with: shuffle settle`));
|
|
689
|
+
console.log(chalk_1.default.gray(` Check status with: shuffle status\n`));
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (e) {
|
|
694
|
+
// Continue if check fails - will be caught later
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// Available tokens (the ones user can BUY)
|
|
698
|
+
const allTokens = [
|
|
699
|
+
{ name: "TSLA - Tesla Stock Token", value: "TSLA" },
|
|
700
|
+
{ name: "SPY - S&P 500 ETF Token", value: "SPY" },
|
|
701
|
+
{ name: "AAPL - Apple Stock Token", value: "AAPL" },
|
|
702
|
+
{ name: "USDC - US Dollar Coin", value: "USDC" },
|
|
703
|
+
];
|
|
704
|
+
// Step 1: What do you want to BUY?
|
|
705
|
+
const { buyToken } = await inquirer.prompt([{
|
|
706
|
+
type: "list",
|
|
707
|
+
name: "buyToken",
|
|
708
|
+
message: chalk_1.default.cyan("🎯 What token do you want to BUY?"),
|
|
709
|
+
choices: allTokens,
|
|
710
|
+
pageSize: 5,
|
|
711
|
+
}]);
|
|
712
|
+
// Step 2: Get user's balances to show what they CAN pay with
|
|
713
|
+
let balances = null;
|
|
714
|
+
if (config.mockMode) {
|
|
715
|
+
const state = (0, devnet_1.getMockState)();
|
|
716
|
+
balances = state.balances;
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
try {
|
|
720
|
+
balances = await (0, output_1.withSpinner)("Checking your balances...", () => config.shuffleClient.getBalance(), "Balances loaded!");
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
(0, output_1.printError)("Could not fetch balances. Run 'shuffle init' first.");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// Filter: only show tokens user has balance in AND are different from buy token
|
|
728
|
+
const paymentOptions = [];
|
|
729
|
+
const tokenBalances = {
|
|
730
|
+
USDC: balances.usdc,
|
|
731
|
+
TSLA: balances.tsla,
|
|
732
|
+
SPY: balances.spy,
|
|
733
|
+
AAPL: balances.aapl,
|
|
734
|
+
};
|
|
735
|
+
for (const [token, balance] of Object.entries(tokenBalances)) {
|
|
736
|
+
if (token !== buyToken && balance > 0n) {
|
|
737
|
+
const displayBal = (Number(balance) / 1000000).toLocaleString("en-US", {
|
|
738
|
+
minimumFractionDigits: 2,
|
|
739
|
+
maximumFractionDigits: 6
|
|
740
|
+
});
|
|
741
|
+
paymentOptions.push({
|
|
742
|
+
name: `${token} (${displayBal} available)`,
|
|
743
|
+
value: token,
|
|
744
|
+
balance: Number(balance),
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (paymentOptions.length === 0) {
|
|
749
|
+
(0, output_1.printError)(`You don't have any tokens to pay with! Use 'shuffle faucet 100' to get USDC.`);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
// Step 3: What do you want to PAY with?
|
|
753
|
+
const { payToken } = await inquirer.prompt([{
|
|
754
|
+
type: "list",
|
|
755
|
+
name: "payToken",
|
|
756
|
+
message: chalk_1.default.cyan(`💰 What token do you want to PAY with for ${buyToken}?`),
|
|
757
|
+
choices: paymentOptions.map(p => ({ name: p.name, value: p.value })),
|
|
758
|
+
pageSize: 5,
|
|
759
|
+
}]);
|
|
760
|
+
const maxBalance = Number(tokenBalances[payToken]) / 1000000;
|
|
761
|
+
// Step 4: How much?
|
|
762
|
+
const { amount } = await inquirer.prompt([{
|
|
763
|
+
type: "input",
|
|
764
|
+
name: "amount",
|
|
765
|
+
message: chalk_1.default.cyan(`📊 How much ${payToken} do you want to spend? (max: ${maxBalance.toLocaleString()})`),
|
|
766
|
+
validate: (input) => {
|
|
767
|
+
const num = parseFloat(input);
|
|
768
|
+
if (isNaN(num) || num <= 0)
|
|
769
|
+
return "Please enter a positive number";
|
|
770
|
+
if (num > maxBalance)
|
|
771
|
+
return `Maximum available: ${maxBalance.toLocaleString()}`;
|
|
772
|
+
return true;
|
|
773
|
+
},
|
|
774
|
+
}]);
|
|
775
|
+
// Step 5: Confirmation
|
|
776
|
+
console.log();
|
|
777
|
+
console.log(chalk_1.default.bold.white(" 📋 Order Summary:"));
|
|
778
|
+
console.log(chalk_1.default.gray(` ├─ Buy: ${chalk_1.default.green(buyToken)}`));
|
|
779
|
+
console.log(chalk_1.default.gray(` ├─ Pay: ${chalk_1.default.yellow(amount)} ${payToken}`));
|
|
780
|
+
console.log(chalk_1.default.gray(` └─ Type: Private batch order`));
|
|
781
|
+
console.log();
|
|
782
|
+
const { confirm } = await inquirer.prompt([{
|
|
783
|
+
type: "confirm",
|
|
784
|
+
name: "confirm",
|
|
785
|
+
message: chalk_1.default.cyan("Confirm and place order?"),
|
|
786
|
+
default: true,
|
|
787
|
+
}]);
|
|
788
|
+
if (!confirm) {
|
|
789
|
+
(0, output_1.printInfo)("Order cancelled.");
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
// Convert to pair and direction
|
|
793
|
+
const pairInfo = convertToPairAndDirection(buyToken, payToken);
|
|
794
|
+
if (!pairInfo) {
|
|
795
|
+
(0, output_1.printError)(`Trading pair ${buyToken}/${payToken} not supported.`);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
// Execute the order
|
|
799
|
+
await executeDirectOrder(config, pairInfo.pair, pairInfo.direction, amount);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Convert buy/pay tokens to pair and direction
|
|
803
|
+
*/
|
|
804
|
+
function convertToPairAndDirection(buyToken, payToken) {
|
|
805
|
+
// Define valid pairs: [base, quote] where base is first token in pair name
|
|
806
|
+
const pairs = [
|
|
807
|
+
{ pair: "TSLA_USDC", base: "TSLA", quote: "USDC" },
|
|
808
|
+
{ pair: "SPY_USDC", base: "SPY", quote: "USDC" },
|
|
809
|
+
{ pair: "AAPL_USDC", base: "AAPL", quote: "USDC" },
|
|
810
|
+
{ pair: "TSLA_SPY", base: "TSLA", quote: "SPY" },
|
|
811
|
+
{ pair: "TSLA_AAPL", base: "TSLA", quote: "AAPL" },
|
|
812
|
+
{ pair: "SPY_AAPL", base: "SPY", quote: "AAPL" },
|
|
813
|
+
];
|
|
814
|
+
for (const p of pairs) {
|
|
815
|
+
// Buying base, paying quote = BtoA (quote → base)
|
|
816
|
+
if (buyToken === p.base && payToken === p.quote) {
|
|
817
|
+
return { pair: p.pair, direction: "buy" };
|
|
818
|
+
}
|
|
819
|
+
// Buying quote, paying base = AtoB (base → quote)
|
|
820
|
+
if (buyToken === p.quote && payToken === p.base) {
|
|
821
|
+
return { pair: p.pair, direction: "sell" };
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* shuffle settle - Settle pending order
|
|
828
|
+
*/
|
|
829
|
+
async function settleCommand() {
|
|
830
|
+
const config = (0, config_1.getConfig)();
|
|
831
|
+
if (config.mockMode) {
|
|
832
|
+
(0, output_1.printMockWarning)();
|
|
833
|
+
const state = (0, devnet_1.getMockState)();
|
|
834
|
+
if (!state.accountExists) {
|
|
835
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (!state.pendingOrder) {
|
|
839
|
+
(0, output_1.printError)("No pending order to settle.");
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
843
|
+
"Fetching batch execution results...",
|
|
844
|
+
"Calculating your pro-rata payout via MPC...",
|
|
845
|
+
"Updating encrypted balance...",
|
|
846
|
+
]);
|
|
847
|
+
progress.start();
|
|
848
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
849
|
+
progress.nextStep();
|
|
850
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
851
|
+
progress.nextStep();
|
|
852
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
853
|
+
// Simulate payout (slightly different amount due to "slippage")
|
|
854
|
+
const payoutAmount = (state.pendingOrder.amount * 99n) / 100n;
|
|
855
|
+
const assetLabels = ["USDC", "TSLA", "SPY", "AAPL"];
|
|
856
|
+
const outputAsset = state.pendingOrder.direction === 0 ? 0 : 1; // simplified
|
|
857
|
+
state.balances.usdc += payoutAmount; // Simplified: always pay in USDC
|
|
858
|
+
(0, devnet_1.updateMockState)({
|
|
859
|
+
balances: state.balances,
|
|
860
|
+
pendingOrder: null,
|
|
861
|
+
batchId: state.batchId + 1,
|
|
862
|
+
});
|
|
863
|
+
const payoutDisplay = (Number(payoutAmount) / 1000000).toLocaleString();
|
|
864
|
+
progress.succeed(`Settled! Received ${payoutDisplay} USDC`);
|
|
865
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
// Real mode
|
|
869
|
+
if (!config.shuffleClient) {
|
|
870
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const order = await config.shuffleClient.getDecryptedOrder();
|
|
875
|
+
if (!order) {
|
|
876
|
+
(0, output_1.printError)("No pending order to settle.");
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const sig = await (0, output_1.withSpinner)("Settling order...", () => config.shuffleClient.settleOrder(order.pairId, order.direction), "Order settled!");
|
|
880
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
881
|
+
}
|
|
882
|
+
catch (e) {
|
|
883
|
+
// Provide user-friendly error messages
|
|
884
|
+
const msg = e.message || "";
|
|
885
|
+
if (msg.includes("Unknown action") || msg.includes("undefined")) {
|
|
886
|
+
(0, output_1.printError)("No pending order to settle. Place an order first with 'shuffle order'.");
|
|
887
|
+
}
|
|
888
|
+
else if (msg.includes("not found") || msg.includes("Account does not exist")) {
|
|
889
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
890
|
+
}
|
|
891
|
+
else if (msg.includes("batch") || msg.includes("executed")) {
|
|
892
|
+
(0, output_1.printError)("Batch not yet executed. Wait for batch execution before settling.");
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
(0, output_1.printError)(msg);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* shuffle execute - Trigger batch execution
|
|
901
|
+
*/
|
|
902
|
+
async function executeCommand() {
|
|
903
|
+
const config = (0, config_1.getConfig)();
|
|
904
|
+
if (config.mockMode) {
|
|
905
|
+
(0, output_1.printMockWarning)();
|
|
906
|
+
const state = (0, devnet_1.getMockState)();
|
|
907
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
908
|
+
"Checking batch status...",
|
|
909
|
+
"Triggering batch execution...",
|
|
910
|
+
"Waiting for MPC computation...",
|
|
911
|
+
"Batch executed!",
|
|
912
|
+
]);
|
|
913
|
+
progress.start();
|
|
914
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
915
|
+
progress.nextStep();
|
|
916
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
917
|
+
progress.nextStep();
|
|
918
|
+
await (0, devnet_1.mockDelay)("slow");
|
|
919
|
+
progress.nextStep();
|
|
920
|
+
(0, devnet_1.updateMockState)({ batchId: state.batchId + 1 });
|
|
921
|
+
progress.succeed("Batch executed successfully!");
|
|
922
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
// Real mode
|
|
926
|
+
if (!config.shuffleClient) {
|
|
927
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
// First check batch status
|
|
932
|
+
const batch = await config.shuffleClient.getBatchInfo();
|
|
933
|
+
console.log(chalk_1.default.gray(`\n Current batch: #${batch.batchId}`));
|
|
934
|
+
console.log(chalk_1.default.gray(` Orders: ${batch.orderCount}/8\n`));
|
|
935
|
+
if (batch.orderCount < 8) {
|
|
936
|
+
(0, output_1.printError)(`Not enough orders: ${batch.orderCount}/8. Need 8 orders to execute.`);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const sig = await (0, output_1.withSpinner)("Executing batch (this may take ~60 seconds)...", () => config.shuffleClient.executeBatch(), "Batch executed!");
|
|
940
|
+
(0, output_1.printSuccess)(`Batch #${batch.batchId} executed successfully!`);
|
|
941
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
942
|
+
console.log(chalk_1.default.cyan("\n Users can now settle their orders with 'shuffle settle'\n"));
|
|
943
|
+
}
|
|
944
|
+
catch (e) {
|
|
945
|
+
const msg = e.message || "";
|
|
946
|
+
if (msg.includes("Not enough orders")) {
|
|
947
|
+
(0, output_1.printError)(msg);
|
|
948
|
+
}
|
|
949
|
+
else if (msg.includes("already executed")) {
|
|
950
|
+
(0, output_1.printError)("Batch already executed. Orders have been reset for the next batch.");
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
(0, output_1.printError)(msg);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* shuffle status - View batch and order status
|
|
959
|
+
*/
|
|
960
|
+
async function statusCommand() {
|
|
961
|
+
const config = (0, config_1.getConfig)();
|
|
962
|
+
if (config.mockMode) {
|
|
963
|
+
(0, output_1.printMockWarning)();
|
|
964
|
+
const state = (0, devnet_1.getMockState)();
|
|
965
|
+
if (!state.accountExists) {
|
|
966
|
+
(0, output_1.printError)("Account not found. Run 'shuffle init' first.");
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
await (0, output_1.withSpinner)("Fetching status...", () => (0, devnet_1.mockDelay)("fast"), "Status retrieved!");
|
|
970
|
+
(0, output_1.printHeader)("Batch Status");
|
|
971
|
+
(0, output_1.printBatchStatus)({
|
|
972
|
+
batchId: state.batchId,
|
|
973
|
+
orderCount: state.pendingOrder ? 1 : 0,
|
|
974
|
+
});
|
|
975
|
+
console.log();
|
|
976
|
+
(0, output_1.printHeader)("Your Order Status");
|
|
977
|
+
if (!state.pendingOrder) {
|
|
978
|
+
console.log(chalk_1.default.gray(" No pending order\n"));
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
const pairLabels = ["TSLA/USDC", "SPY/USDC", "AAPL/USDC"];
|
|
982
|
+
const dirLabel = state.pendingOrder.direction === 0 ? "BUY" : "SELL";
|
|
983
|
+
const orderAmount = (Number(state.pendingOrder.amount) / 1000000).toFixed(2);
|
|
984
|
+
const isExecuted = state.batchId > state.pendingOrder.batchId;
|
|
985
|
+
console.log(chalk_1.default.gray(` Pair: ${chalk_1.default.white(pairLabels[state.pendingOrder.pairId] || "Unknown")}`));
|
|
986
|
+
console.log(chalk_1.default.gray(` Direction: ${chalk_1.default.white(dirLabel)}`));
|
|
987
|
+
console.log(chalk_1.default.gray(` Amount: ${chalk_1.default.white(orderAmount)} USDC`));
|
|
988
|
+
console.log(chalk_1.default.gray(` Batch ID: ${chalk_1.default.white(state.pendingOrder.batchId)}`));
|
|
989
|
+
if (isExecuted) {
|
|
990
|
+
const payoutAmount = (Number(state.pendingOrder.amount) / 10000000).toFixed(2);
|
|
991
|
+
const outputAsset = state.pendingOrder.pairId === 0 ? "TSLA" : "SPY";
|
|
992
|
+
console.log();
|
|
993
|
+
console.log(chalk_1.default.green(` ✓ Executed`));
|
|
994
|
+
console.log(chalk_1.default.gray(` Payout: ${chalk_1.default.cyan(payoutAmount)} ${outputAsset}`));
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
console.log();
|
|
998
|
+
console.log(chalk_1.default.yellow(` ⏳ Pending`));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
console.log();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
// Real mode
|
|
1005
|
+
if (!config.shuffleClient) {
|
|
1006
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
const [batchInfo, order, estimatedPayout] = await Promise.all([
|
|
1011
|
+
config.shuffleClient.getBatchInfo(),
|
|
1012
|
+
config.shuffleClient.getDecryptedOrder(),
|
|
1013
|
+
config.shuffleClient.estimatePayout(),
|
|
1014
|
+
]);
|
|
1015
|
+
(0, output_1.printHeader)("Batch Status");
|
|
1016
|
+
(0, output_1.printBatchStatus)(batchInfo);
|
|
1017
|
+
console.log();
|
|
1018
|
+
(0, output_1.printHeader)("Your Order Status");
|
|
1019
|
+
if (!order) {
|
|
1020
|
+
console.log(chalk_1.default.gray(" No pending order\n"));
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
const pairLabels = ["TSLA/USDC", "SPY/USDC", "AAPL/USDC"];
|
|
1024
|
+
const dirLabel = order.direction === 0 ? "BUY" : "SELL";
|
|
1025
|
+
const orderAmount = (Number(order.amount) / 1000000).toFixed(2);
|
|
1026
|
+
// Check if batch was executed by seeing if estimatePayout returned a value
|
|
1027
|
+
const isExecuted = estimatedPayout !== null;
|
|
1028
|
+
console.log(chalk_1.default.gray(` Pair: ${chalk_1.default.white(pairLabels[order.pairId] || "Unknown")}`));
|
|
1029
|
+
console.log(chalk_1.default.gray(` Direction: ${chalk_1.default.white(dirLabel)}`));
|
|
1030
|
+
console.log(chalk_1.default.gray(` Amount: ${chalk_1.default.white(orderAmount)} USDC`));
|
|
1031
|
+
console.log(chalk_1.default.gray(` Batch ID: ${chalk_1.default.white(order.batchId)}`));
|
|
1032
|
+
if (isExecuted) {
|
|
1033
|
+
const payoutAmount = (Number(estimatedPayout.estimatedPayout) / 1000000).toFixed(2);
|
|
1034
|
+
const outputAsset = ["USDC", "TSLA", "SPY", "AAPL"][estimatedPayout.outputAssetId] || "tokens";
|
|
1035
|
+
console.log();
|
|
1036
|
+
console.log(chalk_1.default.green(` ✓ Executed`));
|
|
1037
|
+
console.log(chalk_1.default.gray(` Payout: ${chalk_1.default.cyan(payoutAmount)} ${outputAsset}`));
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
console.log();
|
|
1041
|
+
console.log(chalk_1.default.yellow(` ⏳ Pending`));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
console.log();
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1047
|
+
// Provide user-friendly error messages
|
|
1048
|
+
const msg = e.message || "";
|
|
1049
|
+
if (msg.includes("Account does not exist") || msg.includes("not found")) {
|
|
1050
|
+
(0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
(0, output_1.printError)(msg);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
// ============================================================================
|
|
1058
|
+
// DEVNET COMMANDS
|
|
1059
|
+
// ============================================================================
|
|
1060
|
+
/**
|
|
1061
|
+
* shuffle faucet <amount> - Mint USDC to wallet
|
|
1062
|
+
*/
|
|
1063
|
+
async function faucetCommand(amountStr) {
|
|
1064
|
+
const config = (0, config_1.getConfig)();
|
|
1065
|
+
const amount = parseFloat(amountStr);
|
|
1066
|
+
if (isNaN(amount) || amount <= 0) {
|
|
1067
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const amountRaw = BigInt(Math.floor(amount * 1000000));
|
|
1071
|
+
if (config.mockMode) {
|
|
1072
|
+
(0, output_1.printMockWarning)();
|
|
1073
|
+
const state = (0, devnet_1.getMockState)();
|
|
1074
|
+
const progress = (0, output_1.createProgressSpinner)([
|
|
1075
|
+
"Connecting to USDC faucet...",
|
|
1076
|
+
`Minting ${amount.toLocaleString()} USDC to your wallet...`,
|
|
1077
|
+
"Tokens received!",
|
|
1078
|
+
]);
|
|
1079
|
+
progress.start();
|
|
1080
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
1081
|
+
progress.nextStep();
|
|
1082
|
+
await (0, devnet_1.mockDelay)("medium");
|
|
1083
|
+
progress.nextStep();
|
|
1084
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
1085
|
+
// In mock mode, also update account balance if it exists
|
|
1086
|
+
if (state.accountExists) {
|
|
1087
|
+
state.balances.usdc += amountRaw;
|
|
1088
|
+
(0, devnet_1.updateMockState)({ balances: state.balances });
|
|
1089
|
+
}
|
|
1090
|
+
progress.succeed(`Received ${amount.toLocaleString()} USDC!`);
|
|
1091
|
+
console.log(chalk_1.default.gray(` Token: ${devnet_1.DEVNET_CONFIG.mints.USDC.toBase58().slice(0, 20)}...`));
|
|
1092
|
+
(0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
// Real mode - mint USDC via ShuffleClient
|
|
1096
|
+
if (!config.shuffleClient) {
|
|
1097
|
+
(0, output_1.printError)("Not connected to Shuffle protocol");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
const sig = await (0, output_1.withSpinner)(`Minting ${amount.toLocaleString()} USDC to your wallet...`, async () => {
|
|
1102
|
+
// Use the client's mintToWallet method if available, otherwise show helpful error
|
|
1103
|
+
if (typeof config.shuffleClient.mintUsdcToWallet === "function") {
|
|
1104
|
+
return await config.shuffleClient.mintUsdcToWallet(Math.floor(amount * 1000000));
|
|
1105
|
+
}
|
|
1106
|
+
// Fallback: try to get mint and mint directly
|
|
1107
|
+
const { mintTo, getOrCreateAssociatedTokenAccount } = await Promise.resolve().then(() => __importStar(require("@solana/spl-token")));
|
|
1108
|
+
const { Keypair } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
1109
|
+
const fs = await Promise.resolve().then(() => __importStar(require("fs")));
|
|
1110
|
+
const path = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1111
|
+
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
1112
|
+
const pool = await config.shuffleClient.program.account.pool.fetch(config.shuffleClient.poolPDA);
|
|
1113
|
+
const usdcMint = pool.usdcMint;
|
|
1114
|
+
const connection = config.connection;
|
|
1115
|
+
// Load the DEFAULT keypair (has mint authority) instead of current user
|
|
1116
|
+
const defaultKeypairPath = path.join(os.homedir(), ".config", "solana", "id.json");
|
|
1117
|
+
if (!fs.existsSync(defaultKeypairPath)) {
|
|
1118
|
+
throw new Error("Main wallet not found. Faucet requires default Solana keypair with mint authority.");
|
|
1119
|
+
}
|
|
1120
|
+
const defaultRaw = JSON.parse(fs.readFileSync(defaultKeypairPath, "utf-8"));
|
|
1121
|
+
const mintAuthority = Keypair.fromSecretKey(Uint8Array.from(defaultRaw));
|
|
1122
|
+
// Current user's public key (where tokens will go)
|
|
1123
|
+
const recipientPubkey = config.wallet.publicKey;
|
|
1124
|
+
// Get or create user's token account (use mint authority as payer since they have SOL)
|
|
1125
|
+
const tokenAccount = await getOrCreateAssociatedTokenAccount(connection, mintAuthority, // payer
|
|
1126
|
+
usdcMint, recipientPubkey // owner (the current user profile)
|
|
1127
|
+
);
|
|
1128
|
+
// Mint tokens using the mint authority
|
|
1129
|
+
const mintSig = await mintTo(connection, mintAuthority, usdcMint, tokenAccount.address, mintAuthority, // mint authority
|
|
1130
|
+
Math.floor(amount * 1000000));
|
|
1131
|
+
return mintSig;
|
|
1132
|
+
}, `Received ${amount.toLocaleString()} USDC!`);
|
|
1133
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
1134
|
+
}
|
|
1135
|
+
catch (e) {
|
|
1136
|
+
// Improve error messages
|
|
1137
|
+
if (e.message?.includes("mint authority")) {
|
|
1138
|
+
(0, output_1.printError)("Cannot mint: you don't have mint authority. Only works on localnet.");
|
|
1139
|
+
}
|
|
1140
|
+
else if (e.message?.includes("insufficient funds")) {
|
|
1141
|
+
(0, output_1.printError)("Insufficient SOL for transaction fees. Request an airdrop first with 'shuffle airdrop'.");
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
(0, output_1.printError)(e.message || "Faucet failed");
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* shuffle airdrop [amount] - Airdrop SOL on localnet
|
|
1150
|
+
*/
|
|
1151
|
+
async function airdropCommand(amountStr) {
|
|
1152
|
+
const config = (0, config_1.getConfig)();
|
|
1153
|
+
const amount = amountStr ? parseFloat(amountStr) : 2; // Default 2 SOL
|
|
1154
|
+
if (isNaN(amount) || amount <= 0) {
|
|
1155
|
+
(0, output_1.printError)("Invalid amount. Must be a positive number.");
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (config.mockMode) {
|
|
1159
|
+
(0, output_1.printMockWarning)();
|
|
1160
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
1161
|
+
(0, output_1.printSuccess)(`Airdropped ${amount} SOL (mock)`);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
// Real mode
|
|
1165
|
+
if (!config.connection) {
|
|
1166
|
+
(0, output_1.printError)("Not connected");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const pubkey = config.wallet.publicKey;
|
|
1171
|
+
console.log(chalk_1.default.gray(`\n Wallet: ${pubkey.toBase58()}`));
|
|
1172
|
+
const sig = await (0, output_1.withSpinner)(`Requesting ${amount} SOL airdrop...`, async () => {
|
|
1173
|
+
const signature = await config.connection.requestAirdrop(pubkey, amount * 1000000000 // Convert SOL to lamports
|
|
1174
|
+
);
|
|
1175
|
+
// Wait for confirmation
|
|
1176
|
+
await config.connection.confirmTransaction(signature, "confirmed");
|
|
1177
|
+
return signature;
|
|
1178
|
+
}, `Received ${amount} SOL!`);
|
|
1179
|
+
const balance = await config.connection.getBalance(pubkey);
|
|
1180
|
+
console.log(chalk_1.default.gray(` Balance: ${(balance / 1000000000).toFixed(4)} SOL\n`));
|
|
1181
|
+
(0, output_1.printTxSuccess)(sig, config.network);
|
|
1182
|
+
}
|
|
1183
|
+
catch (e) {
|
|
1184
|
+
if (e.message?.includes("airdrop request")) {
|
|
1185
|
+
(0, output_1.printError)("Airdrop limit reached. Try again later or use a different network.");
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
(0, output_1.printError)(e.message || "Airdrop failed");
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* shuffle history - Show all executed batch logs
|
|
1194
|
+
*/
|
|
1195
|
+
async function historyCommand() {
|
|
1196
|
+
const config = (0, config_1.getConfig)();
|
|
1197
|
+
if (config.mockMode) {
|
|
1198
|
+
(0, output_1.printMockWarning)();
|
|
1199
|
+
await (0, devnet_1.mockDelay)("fast");
|
|
1200
|
+
// Mock data for demo
|
|
1201
|
+
const mockLogs = [
|
|
1202
|
+
{
|
|
1203
|
+
batchId: 1,
|
|
1204
|
+
results: [
|
|
1205
|
+
{ totalAIn: { toString: () => "1000000" }, totalBIn: { toString: () => "250000" }, finalPoolA: { toString: () => "750000" }, finalPoolB: { toString: () => "250000" } },
|
|
1206
|
+
{ totalAIn: { toString: () => "0" }, totalBIn: { toString: () => "0" }, finalPoolA: { toString: () => "0" }, finalPoolB: { toString: () => "0" } },
|
|
1207
|
+
{ totalAIn: { toString: () => "0" }, totalBIn: { toString: () => "0" }, finalPoolA: { toString: () => "0" }, finalPoolB: { toString: () => "0" } },
|
|
1208
|
+
{ totalAIn: { toString: () => "0" }, totalBIn: { toString: () => "0" }, finalPoolA: { toString: () => "0" }, finalPoolB: { toString: () => "0" } },
|
|
1209
|
+
{ totalAIn: { toString: () => "0" }, totalBIn: { toString: () => "0" }, finalPoolA: { toString: () => "0" }, finalPoolB: { toString: () => "0" } },
|
|
1210
|
+
{ totalAIn: { toString: () => "0" }, totalBIn: { toString: () => "0" }, finalPoolA: { toString: () => "0" }, finalPoolB: { toString: () => "0" } },
|
|
1211
|
+
],
|
|
1212
|
+
},
|
|
1213
|
+
];
|
|
1214
|
+
(0, output_1.printBatchHistory)(mockLogs);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
// Real mode
|
|
1218
|
+
if (!config.shuffleClient) {
|
|
1219
|
+
(0, output_1.printError)("Not connected. Run 'shuffle balance' first to initialize.");
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
try {
|
|
1223
|
+
const logs = await (0, output_1.withSpinner)("Fetching batch history...", () => config.shuffleClient.getAllBatchLogs(), "History loaded!");
|
|
1224
|
+
(0, output_1.printBatchHistory)(logs);
|
|
1225
|
+
}
|
|
1226
|
+
catch (e) {
|
|
1227
|
+
(0, output_1.printError)(e.message || "Failed to fetch batch history");
|
|
1228
|
+
}
|
|
1229
|
+
}
|