@shuffle-protocol/sdk 0.1.0 → 0.2.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 CHANGED
@@ -22,33 +22,21 @@ npm install -g @shuffle-protocol/sdk
22
22
 
23
23
  # Configure for devnet
24
24
  solana config set --url devnet
25
-
26
- # Get devnet SOL
27
- solana airdrop 2
28
25
  ```
29
26
 
30
- ### Commands
27
+ ### Get Started (Devnet)
31
28
 
32
29
  ```bash
33
- # Create your privacy account
30
+ # 1. Create your privacy account
34
31
  shuffle init
35
32
 
36
- # View encrypted balances
37
- shuffle balance
38
-
39
- # Get test tokens (devnet)
33
+ # 2. Get test tokens (also airdrops 1 SOL for fees)
40
34
  shuffle faucet 10000
41
35
 
42
- # Deposit into privacy account
43
- shuffle deposit USDC 1000
44
-
45
- # Withdraw from privacy account
46
- shuffle withdraw USDC 500
36
+ # 3. Deposit into privacy account
37
+ shuffle shield USDC 1000
47
38
 
48
- # Private transfer to another user
49
- shuffle transfer <solana-address> 100
50
-
51
- # Place encrypted order
39
+ # 4. Place encrypted order
52
40
  shuffle order TSLA_USDC buy 500
53
41
 
54
42
  # Check status
@@ -58,6 +46,22 @@ shuffle status
58
46
  shuffle settle
59
47
  ```
60
48
 
49
+ ### Other Commands
50
+
51
+ ```bash
52
+ # View encrypted balances
53
+ shuffle balance
54
+
55
+ # Withdraw from privacy account
56
+ shuffle unshield USDC 500
57
+
58
+ # Private transfer to another user
59
+ shuffle transfer <solana-address> 100
60
+
61
+ # Get more SOL if needed
62
+ shuffle airdrop 2
63
+ ```
64
+
61
65
  ## 📦 SDK Usage
62
66
 
63
67
  ```typescript
@@ -41,7 +41,7 @@ export declare function executeCommand(): Promise<void>;
41
41
  */
42
42
  export declare function statusCommand(): Promise<void>;
43
43
  /**
44
- * shuffle faucet <amount> - Mint USDC to wallet
44
+ * shuffle faucet <amount> - Claim USDC from program faucet (also airdrops 1 SOL for fees)
45
45
  */
46
46
  export declare function faucetCommand(amountStr: string): Promise<void>;
47
47
  /**
@@ -55,10 +55,35 @@ exports.airdropCommand = airdropCommand;
55
55
  exports.historyCommand = historyCommand;
56
56
  const chalk_1 = __importDefault(require("chalk"));
57
57
  const web3_js_1 = require("@solana/web3.js");
58
+ const spl_token_1 = require("@solana/spl-token");
58
59
  const config_1 = require("./config");
59
60
  const output_1 = require("./output");
60
61
  const devnet_1 = require("./devnet");
62
+ const pda_1 = require("../pda");
61
63
  const constants_1 = require("../constants");
64
+ function getErrorLogs(error) {
65
+ if (!error)
66
+ return [];
67
+ if (Array.isArray(error.logs))
68
+ return error.logs;
69
+ if (Array.isArray(error.transactionLogs))
70
+ return error.transactionLogs;
71
+ if (Array.isArray(error.data?.logs))
72
+ return error.data.logs;
73
+ return [];
74
+ }
75
+ function formatUsdc(amount) {
76
+ const num = Number(amount) / 1000000;
77
+ if (num === 0)
78
+ return "0.00";
79
+ if (num < 0.01) {
80
+ return num.toLocaleString("en-US", { minimumFractionDigits: 6, maximumFractionDigits: 6 });
81
+ }
82
+ if (num < 1) {
83
+ return num.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 4 });
84
+ }
85
+ return num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
86
+ }
62
87
  // ============================================================================
63
88
  // ACCOUNT COMMANDS
64
89
  // ============================================================================
@@ -136,25 +161,31 @@ async function balanceCommand() {
136
161
  }
137
162
  try {
138
163
  // 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];
164
+ const [shielded, unshielded, estimatedPayout, solBalance] = await (0, output_1.withSpinner)("Fetching balances...", async () => {
165
+ const [shieldedBalances, unshieldedBalances, payout, sol] = await Promise.all([
166
+ config.shuffleClient.getBalance(),
167
+ config.shuffleClient.getUnshieldedBalances(),
168
+ config.shuffleClient.estimatePayout(),
169
+ config.connection.getBalance(config.wallet.publicKey),
170
+ ]);
171
+ return [shieldedBalances, unshieldedBalances, payout, sol];
144
172
  }, "Balances loaded!");
145
173
  // Pass pending payout to display it directly in the balance (cyan color)
146
174
  const pendingPayout = estimatedPayout
147
175
  ? { amount: estimatedPayout.estimatedPayout, assetId: estimatedPayout.outputAssetId }
148
176
  : null;
149
- (0, output_1.printBalanceTable)(shielded, unshielded, pendingPayout);
177
+ (0, output_1.printBalanceTable)(shielded, unshielded, pendingPayout, solBalance);
150
178
  }
151
179
  catch (e) {
152
180
  // If account doesn't exist, show only unshielded
153
181
  if (e.message?.includes("Account does not exist") || e.message?.includes("not found")) {
154
182
  try {
155
- const unshielded = await config.shuffleClient.getUnshieldedBalances();
183
+ const [unshielded, solBalance] = await Promise.all([
184
+ config.shuffleClient.getUnshieldedBalances(),
185
+ config.connection.getBalance(config.wallet.publicKey),
186
+ ]);
156
187
  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);
188
+ (0, output_1.printBalanceTable)({ usdc: BigInt(0), tsla: BigInt(0), spy: BigInt(0), aapl: BigInt(0) }, unshielded, null, solBalance);
158
189
  }
159
190
  catch {
160
191
  (0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
@@ -1058,7 +1089,7 @@ async function statusCommand() {
1058
1089
  // DEVNET COMMANDS
1059
1090
  // ============================================================================
1060
1091
  /**
1061
- * shuffle faucet <amount> - Mint USDC to wallet
1092
+ * shuffle faucet <amount> - Claim USDC from program faucet (also airdrops 1 SOL for fees)
1062
1093
  */
1063
1094
  async function faucetCommand(amountStr) {
1064
1095
  const config = (0, config_1.getConfig)();
@@ -1072,13 +1103,16 @@ async function faucetCommand(amountStr) {
1072
1103
  (0, output_1.printMockWarning)();
1073
1104
  const state = (0, devnet_1.getMockState)();
1074
1105
  const progress = (0, output_1.createProgressSpinner)([
1106
+ "Requesting 1 SOL for transaction fees...",
1075
1107
  "Connecting to USDC faucet...",
1076
- `Minting ${amount.toLocaleString()} USDC to your wallet...`,
1108
+ `Claiming ${amount.toLocaleString()} USDC to your wallet...`,
1077
1109
  "Tokens received!",
1078
1110
  ]);
1079
1111
  progress.start();
1080
1112
  await (0, devnet_1.mockDelay)("fast");
1081
1113
  progress.nextStep();
1114
+ await (0, devnet_1.mockDelay)("fast");
1115
+ progress.nextStep();
1082
1116
  await (0, devnet_1.mockDelay)("medium");
1083
1117
  progress.nextStep();
1084
1118
  await (0, devnet_1.mockDelay)("fast");
@@ -1087,61 +1121,112 @@ async function faucetCommand(amountStr) {
1087
1121
  state.balances.usdc += amountRaw;
1088
1122
  (0, devnet_1.updateMockState)({ balances: state.balances });
1089
1123
  }
1090
- progress.succeed(`Received ${amount.toLocaleString()} USDC!`);
1124
+ progress.succeed(`Received 1 SOL + ${amount.toLocaleString()} USDC!`);
1091
1125
  console.log(chalk_1.default.gray(` Token: ${devnet_1.DEVNET_CONFIG.mints.USDC.toBase58().slice(0, 20)}...`));
1092
1126
  (0, output_1.printTxSuccess)((0, devnet_1.mockSignature)(), config.network);
1093
1127
  return;
1094
1128
  }
1095
- // Real mode - mint USDC via ShuffleClient
1129
+ // Real mode - first airdrop SOL, then claim USDC via program faucet
1096
1130
  if (!config.shuffleClient) {
1097
1131
  (0, output_1.printError)("Not connected to Shuffle protocol");
1098
1132
  return;
1099
1133
  }
1100
1134
  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));
1135
+ // Step 1: Airdrop 1 SOL for transaction fees (only on devnet/localnet)
1136
+ const pubkey = config.wallet.publicKey;
1137
+ let currentBalance = await config.connection.getBalance(pubkey);
1138
+ const minBalance = 0.1 * 1000000000; // 0.1 SOL minimum
1139
+ const minBalanceForTx = 0.005 * 1000000000; // 0.005 SOL absolute minimum for transaction
1140
+ if (currentBalance < minBalance) {
1141
+ console.log(chalk_1.default.gray(`\n 💰 Requesting 1 SOL for transaction fees...`));
1142
+ try {
1143
+ const airdropSig = await config.connection.requestAirdrop(pubkey, 1000000000 // 1 SOL
1144
+ );
1145
+ await config.connection.confirmTransaction(airdropSig, "confirmed");
1146
+ console.log(chalk_1.default.green(` ✓ Received 1 SOL`));
1147
+ // Refresh balance after successful airdrop
1148
+ currentBalance = await config.connection.getBalance(pubkey);
1105
1149
  }
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.");
1150
+ catch (airdropError) {
1151
+ // Non-fatal: warn but continue with USDC faucet
1152
+ const msg = airdropError.message || "";
1153
+ if (msg.includes("429") || msg.includes("airdrop limit") || msg.includes("Too Many Requests")) {
1154
+ // Rate limited - show helpful guide
1155
+ console.log(chalk_1.default.yellow(`\n ⚠ Airdrop rate limit reached. Get SOL manually:\n`));
1156
+ console.log(chalk_1.default.cyan(` 📍 Visit: ${chalk_1.default.white.bold("https://faucet.solana.com")}`));
1157
+ console.log(chalk_1.default.gray(` 1. Paste your wallet address: ${chalk_1.default.white(pubkey.toBase58())}`));
1158
+ console.log(chalk_1.default.gray(` 2. Request ${chalk_1.default.white("0.5 SOL")} (minimum recommended)`));
1159
+ console.log(chalk_1.default.gray(` 3. Complete the CAPTCHA\n`));
1160
+ }
1161
+ else {
1162
+ console.log(chalk_1.default.yellow(` ⚠ SOL airdrop failed: ${msg}`));
1163
+ }
1119
1164
  }
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;
1165
+ }
1166
+ else {
1167
+ console.log(chalk_1.default.gray(`\n ✓ Sufficient SOL balance (${(currentBalance / 1000000000).toFixed(4)} SOL)`));
1168
+ }
1169
+ // Check if we have enough SOL for the transaction after airdrop attempt
1170
+ if (currentBalance < minBalanceForTx) {
1171
+ console.log();
1172
+ (0, output_1.printError)(`Insufficient SOL for transaction fees (${(currentBalance / 1000000000).toFixed(4)} SOL).`);
1173
+ console.log(chalk_1.default.yellow(`\n You need at least 0.005 SOL to claim USDC from the faucet.`));
1174
+ console.log(chalk_1.default.cyan(` 📍 Get SOL from: ${chalk_1.default.white.bold("https://faucet.solana.com")}`));
1175
+ console.log(chalk_1.default.gray(` 1. Paste your wallet: ${chalk_1.default.white(pubkey.toBase58())}`));
1176
+ console.log(chalk_1.default.gray(` 2. Request ${chalk_1.default.white("0.5 SOL")}`));
1177
+ console.log(chalk_1.default.gray(` 3. Then run: ${chalk_1.default.white("shuffle faucet " + amountStr)}\n`));
1178
+ return;
1179
+ }
1180
+ // Step 2: Preflight check - ensure faucet vault has enough USDC
1181
+ try {
1182
+ const programId = config.network === "localnet"
1183
+ ? devnet_1.LOCALNET_CONFIG.programId
1184
+ : devnet_1.DEVNET_CONFIG.programId;
1185
+ const [faucetVaultPDA] = (0, pda_1.getFaucetVaultPDA)(programId);
1186
+ const faucetVault = await (0, spl_token_1.getAccount)(config.connection, faucetVaultPDA);
1187
+ if (faucetVault.amount < amountRaw) {
1188
+ (0, output_1.printError)(`Faucet vault has insufficient USDC (available: ${formatUsdc(faucetVault.amount)}). ` +
1189
+ "Ask an admin to refill the faucet.");
1190
+ return;
1191
+ }
1192
+ }
1193
+ catch (e) {
1194
+ // If the faucet vault isn't initialized or can't be fetched, surface a clear message
1195
+ const msg = e?.message?.toLowerCase?.() || "";
1196
+ if (msg.includes("failed to find account") || msg.includes("could not find account") || msg.includes("not found")) {
1197
+ (0, output_1.printError)("Faucet vault is not initialized. Ask an admin to initialize and fund the faucet.");
1198
+ return;
1199
+ }
1200
+ // Fall through to attempt faucet call; it may still succeed
1201
+ }
1202
+ // Step 3: Claim USDC from faucet
1203
+ const sig = await (0, output_1.withSpinner)(`Claiming ${amount.toLocaleString()} USDC to your wallet...`, async () => {
1204
+ if (typeof config.shuffleClient.faucet !== "function") {
1205
+ throw new Error("Faucet not supported by this SDK version.");
1206
+ }
1207
+ return await config.shuffleClient.faucet(Math.floor(amount * 1000000));
1132
1208
  }, `Received ${amount.toLocaleString()} USDC!`);
1133
1209
  (0, output_1.printTxSuccess)(sig, config.network);
1134
1210
  }
1135
1211
  catch (e) {
1136
1212
  // 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.");
1213
+ const msg = e.message || "";
1214
+ const logs = getErrorLogs(e);
1215
+ const hasTokenInsufficient = logs.some((log) => log.toLowerCase().includes("error: insufficient funds"));
1216
+ if (msg.includes("Account does not exist") || msg.includes("not found")) {
1217
+ (0, output_1.printError)("Privacy account not found. Run 'shuffle init' first.");
1218
+ }
1219
+ else if (msg.includes("Faucet limit exceeded") || msg.includes("FaucetLimitExceeded")) {
1220
+ (0, output_1.printError)("Faucet limit exceeded. You can claim up to 1000 USDC total.");
1221
+ }
1222
+ else if (hasTokenInsufficient) {
1223
+ (0, output_1.printError)("Faucet vault has insufficient USDC. Try a smaller amount or ask an admin to refill the faucet.");
1139
1224
  }
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'.");
1225
+ else if (msg.toLowerCase().includes("insufficient funds") && (msg.toLowerCase().includes("fee") || msg.toLowerCase().includes("transaction"))) {
1226
+ (0, output_1.printError)("Insufficient SOL for transaction fees. This shouldn't happen - please report this issue.");
1142
1227
  }
1143
1228
  else {
1144
- (0, output_1.printError)(e.message || "Faucet failed");
1229
+ (0, output_1.printError)(msg || "Faucet failed");
1145
1230
  }
1146
1231
  }
1147
1232
  }
@@ -192,17 +192,20 @@ async function loadConfig(opts) {
192
192
  const connection = new web3_js_1.Connection(getRpcUrl(network), "confirmed");
193
193
  const encryptionPrivateKey = loadOrCreateEncryptionKey(userProfile);
194
194
  let shuffleClient = null;
195
- // Select program ID based on network
195
+ // Select program ID and cluster offset based on network
196
196
  const programId = network === "localnet"
197
197
  ? devnet_1.LOCALNET_CONFIG.programId
198
198
  : devnet_1.DEVNET_CONFIG.programId;
199
+ const clusterOffset = network === "localnet"
200
+ ? 0
201
+ : devnet_1.DEVNET_CONFIG.clusterOffset;
199
202
  if (!mockMode) {
200
203
  try {
201
204
  shuffleClient = await client_1.ShuffleClient.create({
202
205
  connection,
203
206
  wallet,
204
207
  programId,
205
- clusterOffset: 0,
208
+ clusterOffset,
206
209
  });
207
210
  shuffleClient.initEncryption(encryptionPrivateKey);
208
211
  }
@@ -11,7 +11,7 @@ import { PublicKey } from "@solana/web3.js";
11
11
  export declare const MOCK_MODE = false;
12
12
  /**
13
13
  * Localnet program configuration (from arcium test)
14
- * UPDATE THIS when network restarts!
14
+ * NOTE: This is automatically updated by setup-local.js
15
15
  */
16
16
  export declare const LOCALNET_CONFIG: {
17
17
  programId: PublicKey;
@@ -22,20 +22,20 @@ const web3_js_1 = require("@solana/web3.js");
22
22
  exports.MOCK_MODE = false;
23
23
  /**
24
24
  * Localnet program configuration (from arcium test)
25
- * UPDATE THIS when network restarts!
25
+ * NOTE: This is automatically updated by setup-local.js
26
26
  */
27
27
  exports.LOCALNET_CONFIG = {
28
- programId: new web3_js_1.PublicKey("BzaakuSahkVtEXKqZnD9tSPBoiJCMLa1nzQHUjtY1xRM"),
28
+ programId: new web3_js_1.PublicKey("DQ29rUToHVTyp2QxP3C7nt1MuYp6p6PKYNaDpGooPAFq"),
29
29
  rpcUrl: "http://127.0.0.1:8899",
30
30
  };
31
31
  /**
32
32
  * Devnet program and token configuration
33
33
  */
34
34
  exports.DEVNET_CONFIG = {
35
- // Program ID from successful devnet deployment (2026-02-01)
36
- programId: new web3_js_1.PublicKey("BzaakuSahkVtEXKqZnD9tSPBoiJCMLa1nzQHUjtY1xRM"),
35
+ // Program ID from successful devnet deployment (2026-02-03 v0.7.0 - fresh deploy with synced IDs)
36
+ programId: new web3_js_1.PublicKey("D5hXtvqYeBHM4f8DqJuYyioPNDsQS6jhSRqj9DmFFvCH"),
37
37
  rpcUrl: "https://devnet.helius-rpc.com/?api-key=a8e1a5ce-29c6-4356-b3f9-54c1c650ac08",
38
- // Arcium cluster offset for v0.6.3 (required for account derivations)
38
+ // Arcium cluster offset for v0.7.0 (required for account derivations)
39
39
  clusterOffset: 456,
40
40
  // Token mints - deployed 2026-02-01
41
41
  mints: {
package/dist/cli/index.js CHANGED
@@ -134,7 +134,7 @@ program
134
134
  // Devnet Utilities
135
135
  program
136
136
  .command("faucet <amount>")
137
- .description("Mint devnet USDC to your wallet")
137
+ .description("Claim devnet USDC (also airdrops 1 SOL for transaction fees)")
138
138
  .action(commands_1.faucetCommand);
139
139
  program
140
140
  .command("airdrop [amount]")
@@ -43,7 +43,7 @@ export declare function printBalanceTable(shielded: {
43
43
  }, pendingPayout?: {
44
44
  amount: bigint;
45
45
  assetId: number;
46
- } | null): void;
46
+ } | null, solBalanceLamports?: number | bigint): void;
47
47
  /**
48
48
  * Print order status
49
49
  */
@@ -87,8 +87,26 @@ function printHeader(title) {
87
87
  * Print balance table showing shielded and unshielded balances side-by-side
88
88
  * If pendingPayout is provided, add it to the relevant asset in a different color
89
89
  */
90
- function printBalanceTable(shielded, unshielded, pendingPayout) {
90
+ function printBalanceTable(shielded, unshielded, pendingPayout, solBalanceLamports) {
91
91
  printHeader("Your Balances");
92
+ if (solBalanceLamports !== undefined) {
93
+ const sol = Number(solBalanceLamports) / 1000000000;
94
+ let solDisplay;
95
+ if (sol === 0) {
96
+ solDisplay = "0.0000";
97
+ }
98
+ else if (sol < 0.01) {
99
+ solDisplay = sol.toLocaleString("en-US", { minimumFractionDigits: 6, maximumFractionDigits: 6 });
100
+ }
101
+ else if (sol < 1) {
102
+ solDisplay = sol.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 4 });
103
+ }
104
+ else {
105
+ solDisplay = sol.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
106
+ }
107
+ console.log(chalk_1.default.gray(` SOL (wallet): ${chalk_1.default.white(solDisplay)} SOL`));
108
+ console.log();
109
+ }
92
110
  const format = (val) => {
93
111
  const num = Number(val) / 1000000; // 6 decimals
94
112
  // Use more decimal places for small values to avoid showing 0.00 for non-zero amounts
package/dist/client.d.ts CHANGED
@@ -41,6 +41,11 @@ export declare class ShuffleClient {
41
41
  fetchUserAccount(owner?: PublicKey): Promise<any>;
42
42
  /** Check if account exists */
43
43
  accountExists(owner?: PublicKey): Promise<boolean>;
44
+ /**
45
+ * Claim USDC from the program faucet.
46
+ * @param amount Amount in base units (6 decimals).
47
+ */
48
+ faucet(amount: number): Promise<string>;
44
49
  /** Deposit tokens into the protocol (add_balance). Uses internal encryption if params omitted. */
45
50
  deposit(assetId: AssetId, amount: number, cipher?: RescueCipher, encryptionPublicKey?: Uint8Array): Promise<string>;
46
51
  /** Withdraw tokens from the protocol (sub_balance). Uses internal encryption if params omitted. */
package/dist/client.js CHANGED
@@ -153,6 +153,49 @@ class ShuffleClient {
153
153
  }
154
154
  }
155
155
  // =========================================================================
156
+ // DEVNET / FAUCET METHODS
157
+ // =========================================================================
158
+ /**
159
+ * Claim USDC from the program faucet.
160
+ * @param amount Amount in base units (6 decimals).
161
+ */
162
+ async faucet(amount) {
163
+ const owner = this.wallet.publicKey;
164
+ const [userAccountPDA] = (0, pda_1.getUserAccountPDA)(this.programId, owner);
165
+ const [faucetVaultPDA] = (0, pda_1.getFaucetVaultPDA)(this.programId);
166
+ // Fetch pool to find the USDC mint
167
+ const pool = await this.program.account.pool.fetch(this.poolPDA);
168
+ const usdcMint = pool.usdcMint;
169
+ // Ensure the user's USDC ATA exists (create if missing)
170
+ const userUsdcAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(usdcMint, owner);
171
+ try {
172
+ await (0, spl_token_1.getAccount)(this.connection, userUsdcAccount);
173
+ }
174
+ catch (e) {
175
+ if (e instanceof spl_token_1.TokenAccountNotFoundError) {
176
+ const ix = (0, spl_token_1.createAssociatedTokenAccountInstruction)(owner, // payer
177
+ userUsdcAccount, owner, usdcMint);
178
+ const tx = new web3_js_1.Transaction().add(ix);
179
+ await this.provider.sendAndConfirm(tx, []);
180
+ }
181
+ else {
182
+ throw e;
183
+ }
184
+ }
185
+ const sig = await this.program.methods
186
+ .faucet(new anchor.BN(amount))
187
+ .accounts({
188
+ user: owner,
189
+ userAccount: userAccountPDA,
190
+ userUsdcAccount,
191
+ pool: this.poolPDA,
192
+ faucetVault: faucetVaultPDA,
193
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
194
+ })
195
+ .rpc({ commitment: "confirmed" });
196
+ return sig;
197
+ }
198
+ // =========================================================================
156
199
  // BALANCE METHODS
157
200
  // =========================================================================
158
201
  /** Deposit tokens into the protocol (add_balance). Uses internal encryption if params omitted. */
@@ -25,6 +25,7 @@ export declare const USER_SEED = "user";
25
25
  export declare const BATCH_ACCUMULATOR_SEED = "batch_accumulator";
26
26
  export declare const BATCH_LOG_SEED = "batch_log";
27
27
  export declare const VAULT_SEED = "vault";
28
+ export declare const FAUCET_USDC_SEED = "faucet_usdc";
28
29
  export declare const VAULT_ASSET_SEEDS: Record<AssetId, string>;
29
30
  export declare const ASSET_LABELS: Record<AssetId, string>;
30
31
  export declare const PAIR_TOKENS: Record<PairId, [AssetId, AssetId]>;
package/dist/constants.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PAIR_TOKENS = exports.ASSET_LABELS = exports.VAULT_ASSET_SEEDS = exports.VAULT_SEED = exports.BATCH_LOG_SEED = exports.BATCH_ACCUMULATOR_SEED = exports.USER_SEED = exports.POOL_SEED = exports.NUM_ASSETS = exports.NUM_PAIRS = exports.Direction = exports.PairId = exports.AssetId = exports.PROGRAM_ID = void 0;
3
+ exports.PAIR_TOKENS = exports.ASSET_LABELS = exports.VAULT_ASSET_SEEDS = exports.FAUCET_USDC_SEED = exports.VAULT_SEED = exports.BATCH_LOG_SEED = exports.BATCH_ACCUMULATOR_SEED = exports.USER_SEED = exports.POOL_SEED = exports.NUM_ASSETS = exports.NUM_PAIRS = exports.Direction = exports.PairId = exports.AssetId = exports.PROGRAM_ID = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
- // Program ID (localnet default from Anchor.toml)
6
- exports.PROGRAM_ID = new web3_js_1.PublicKey("BzaakuSahkVtEXKqZnD9tSPBoiJCMLa1nzQHUjtY1xRM");
5
+ // Program ID (localnet default, use DEVNET_CONFIG for devnet)
6
+ exports.PROGRAM_ID = new web3_js_1.PublicKey("DQ29rUToHVTyp2QxP3C7nt1MuYp6p6PKYNaDpGooPAFq");
7
7
  // Asset IDs matching contract/programs/shuffle_protocol/src/constants.rs
8
8
  var AssetId;
9
9
  (function (AssetId) {
@@ -36,6 +36,7 @@ exports.USER_SEED = "user";
36
36
  exports.BATCH_ACCUMULATOR_SEED = "batch_accumulator";
37
37
  exports.BATCH_LOG_SEED = "batch_log";
38
38
  exports.VAULT_SEED = "vault";
39
+ exports.FAUCET_USDC_SEED = "faucet_usdc";
39
40
  // Per-asset vault sub-seeds
40
41
  exports.VAULT_ASSET_SEEDS = {
41
42
  [AssetId.USDC]: "usdc",
package/dist/errors.js CHANGED
@@ -8,7 +8,7 @@ exports.ERROR_MAP = {
8
8
  6001: { name: "Unauthorized", message: "Unauthorized" },
9
9
  6002: { name: "InvalidAmount", message: "Invalid amount" },
10
10
  6003: { name: "InvalidAsset", message: "Invalid asset" },
11
- 6004: { name: "InvalidAssetId", message: "Invalid asset ID (must be 0-3)" },
11
+ 6004: { name: "InvalidAssetId", message: "Invalid asset ID (must be 0-3 for USDC, TSLA, SPY, AAPL)" },
12
12
  6005: { name: "InvalidPairId", message: "Invalid pair ID (must be 0-5)" },
13
13
  6006: { name: "InvalidMint", message: "Invalid token mint" },
14
14
  6007: { name: "InvalidOwner", message: "Invalid token account owner" },
@@ -17,13 +17,16 @@ exports.ERROR_MAP = {
17
17
  6010: { name: "NoPendingOrder", message: "No pending order to settle" },
18
18
  6011: { name: "BatchNotFinalized", message: "Batch not yet executed" },
19
19
  6012: { name: "BatchIdMismatch", message: "Batch ID mismatch" },
20
- 6013: { name: "InsufficientBalance", message: "Insufficient balance" },
21
- 6014: { name: "MinOutputNotMet", message: "Minimum output not met" },
22
- 6015: { name: "DivisionByZero", message: "Division by zero in settlement" },
23
- 6016: { name: "AbortedComputation", message: "The computation was aborted" },
24
- 6017: { name: "ComputationFailed", message: "MPC computation failed" },
25
- 6018: { name: "ClusterNotSet", message: "Cluster not set" },
26
- 6019: { name: "RecipientAccountNotFound", message: "Recipient account not found" },
20
+ 6013: { name: "InvalidBatchId", message: "Invalid batch ID - doesn't match BatchLog" },
21
+ 6014: { name: "SwapsAlreadyExecuted", message: "Swaps already executed for this batch" },
22
+ 6015: { name: "InsufficientBalance", message: "Insufficient balance" },
23
+ 6016: { name: "MinOutputNotMet", message: "Minimum output not met" },
24
+ 6017: { name: "DivisionByZero", message: "Division by zero in settlement - no input for this pair" },
25
+ 6018: { name: "AbortedComputation", message: "The computation was aborted" },
26
+ 6019: { name: "ComputationFailed", message: "MPC computation failed" },
27
+ 6020: { name: "ClusterNotSet", message: "Cluster not set" },
28
+ 6021: { name: "RecipientAccountNotFound", message: "Recipient account not found - they must create a privacy account first" },
29
+ 6022: { name: "FaucetLimitExceeded", message: "Faucet limit exceeded - you can only claim up to 1000 USDC total" },
27
30
  };
28
31
  class ShuffleError extends Error {
29
32
  constructor(code) {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { ShuffleClient } from "./client";
2
2
  export type { UserBalance, OrderInfo, DecryptedOrderInfo, BatchInfo, BatchResult, PairResult, ShuffleConfig, EstimatedPayout, EffectiveBalance, } from "./types";
3
- export { AssetId, PairId, Direction, PROGRAM_ID, NUM_PAIRS, NUM_ASSETS, POOL_SEED, USER_SEED, BATCH_ACCUMULATOR_SEED, BATCH_LOG_SEED, VAULT_SEED, VAULT_ASSET_SEEDS, ASSET_LABELS, PAIR_TOKENS, } from "./constants";
4
- export { getPoolPDA, getUserAccountPDA, getBatchAccumulatorPDA, getBatchLogPDA, getVaultPDA, } from "./pda";
3
+ export { AssetId, PairId, Direction, PROGRAM_ID, NUM_PAIRS, NUM_ASSETS, POOL_SEED, USER_SEED, BATCH_ACCUMULATOR_SEED, BATCH_LOG_SEED, VAULT_SEED, FAUCET_USDC_SEED, VAULT_ASSET_SEEDS, ASSET_LABELS, PAIR_TOKENS, } from "./constants";
4
+ export { getPoolPDA, getUserAccountPDA, getBatchAccumulatorPDA, getBatchLogPDA, getVaultPDA, getFaucetVaultPDA, } from "./pda";
5
5
  export { generateEncryptionKeypair, createCipher, encryptValue, decryptValue, fetchMXEPublicKey, nonceToBN, } from "./encryption";
6
6
  export type { EncryptionKeypair, EncryptedValue } from "./encryption";
7
7
  export { ShuffleError, parseError, ERROR_MAP } from "./errors";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ERROR_MAP = exports.parseError = exports.ShuffleError = exports.nonceToBN = exports.fetchMXEPublicKey = exports.decryptValue = exports.encryptValue = exports.createCipher = exports.generateEncryptionKeypair = exports.getVaultPDA = exports.getBatchLogPDA = exports.getBatchAccumulatorPDA = exports.getUserAccountPDA = exports.getPoolPDA = exports.PAIR_TOKENS = exports.ASSET_LABELS = exports.VAULT_ASSET_SEEDS = exports.VAULT_SEED = exports.BATCH_LOG_SEED = exports.BATCH_ACCUMULATOR_SEED = exports.USER_SEED = exports.POOL_SEED = exports.NUM_ASSETS = exports.NUM_PAIRS = exports.PROGRAM_ID = exports.Direction = exports.PairId = exports.AssetId = exports.ShuffleClient = void 0;
3
+ exports.ERROR_MAP = exports.parseError = exports.ShuffleError = exports.nonceToBN = exports.fetchMXEPublicKey = exports.decryptValue = exports.encryptValue = exports.createCipher = exports.generateEncryptionKeypair = exports.getFaucetVaultPDA = exports.getVaultPDA = exports.getBatchLogPDA = exports.getBatchAccumulatorPDA = exports.getUserAccountPDA = exports.getPoolPDA = exports.PAIR_TOKENS = exports.ASSET_LABELS = exports.VAULT_ASSET_SEEDS = exports.FAUCET_USDC_SEED = exports.VAULT_SEED = exports.BATCH_LOG_SEED = exports.BATCH_ACCUMULATOR_SEED = exports.USER_SEED = exports.POOL_SEED = exports.NUM_ASSETS = exports.NUM_PAIRS = exports.PROGRAM_ID = exports.Direction = exports.PairId = exports.AssetId = exports.ShuffleClient = void 0;
4
4
  // Main SDK exports
5
5
  var client_1 = require("./client");
6
6
  Object.defineProperty(exports, "ShuffleClient", { enumerable: true, get: function () { return client_1.ShuffleClient; } });
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "USER_SEED", { enumerable: true, get: function ()
17
17
  Object.defineProperty(exports, "BATCH_ACCUMULATOR_SEED", { enumerable: true, get: function () { return constants_1.BATCH_ACCUMULATOR_SEED; } });
18
18
  Object.defineProperty(exports, "BATCH_LOG_SEED", { enumerable: true, get: function () { return constants_1.BATCH_LOG_SEED; } });
19
19
  Object.defineProperty(exports, "VAULT_SEED", { enumerable: true, get: function () { return constants_1.VAULT_SEED; } });
20
+ Object.defineProperty(exports, "FAUCET_USDC_SEED", { enumerable: true, get: function () { return constants_1.FAUCET_USDC_SEED; } });
20
21
  Object.defineProperty(exports, "VAULT_ASSET_SEEDS", { enumerable: true, get: function () { return constants_1.VAULT_ASSET_SEEDS; } });
21
22
  Object.defineProperty(exports, "ASSET_LABELS", { enumerable: true, get: function () { return constants_1.ASSET_LABELS; } });
22
23
  Object.defineProperty(exports, "PAIR_TOKENS", { enumerable: true, get: function () { return constants_1.PAIR_TOKENS; } });
@@ -27,6 +28,7 @@ Object.defineProperty(exports, "getUserAccountPDA", { enumerable: true, get: fun
27
28
  Object.defineProperty(exports, "getBatchAccumulatorPDA", { enumerable: true, get: function () { return pda_1.getBatchAccumulatorPDA; } });
28
29
  Object.defineProperty(exports, "getBatchLogPDA", { enumerable: true, get: function () { return pda_1.getBatchLogPDA; } });
29
30
  Object.defineProperty(exports, "getVaultPDA", { enumerable: true, get: function () { return pda_1.getVaultPDA; } });
31
+ Object.defineProperty(exports, "getFaucetVaultPDA", { enumerable: true, get: function () { return pda_1.getFaucetVaultPDA; } });
30
32
  // Encryption helpers
31
33
  var encryption_1 = require("./encryption");
32
34
  Object.defineProperty(exports, "generateEncryptionKeypair", { enumerable: true, get: function () { return encryption_1.generateEncryptionKeypair; } });
package/dist/pda.d.ts CHANGED
@@ -5,3 +5,4 @@ export declare function getUserAccountPDA(programId: PublicKey, owner: PublicKey
5
5
  export declare function getBatchAccumulatorPDA(programId: PublicKey): [PublicKey, number];
6
6
  export declare function getBatchLogPDA(programId: PublicKey, batchId: number | anchor.BN): [PublicKey, number];
7
7
  export declare function getVaultPDA(programId: PublicKey, assetSeed: string): [PublicKey, number];
8
+ export declare function getFaucetVaultPDA(programId: PublicKey): [PublicKey, number];
package/dist/pda.js CHANGED
@@ -38,6 +38,7 @@ exports.getUserAccountPDA = getUserAccountPDA;
38
38
  exports.getBatchAccumulatorPDA = getBatchAccumulatorPDA;
39
39
  exports.getBatchLogPDA = getBatchLogPDA;
40
40
  exports.getVaultPDA = getVaultPDA;
41
+ exports.getFaucetVaultPDA = getFaucetVaultPDA;
41
42
  const web3_js_1 = require("@solana/web3.js");
42
43
  const anchor = __importStar(require("@coral-xyz/anchor"));
43
44
  const constants_1 = require("./constants");
@@ -57,3 +58,6 @@ function getBatchLogPDA(programId, batchId) {
57
58
  function getVaultPDA(programId, assetSeed) {
58
59
  return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.VAULT_SEED), Buffer.from(assetSeed)], programId);
59
60
  }
61
+ function getFaucetVaultPDA(programId) {
62
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.FAUCET_USDC_SEED)], programId);
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuffle-protocol/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI and SDK for the Shuffle privacy protocol on Solana",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "url": "https://github.com/tothster/shuffle/issues"
32
32
  },
33
33
  "dependencies": {
34
- "@arcium-hq/client": "0.6.3",
34
+ "@arcium-hq/client": "0.7.0",
35
35
  "@coral-xyz/anchor": "^0.32.1",
36
36
  "@solana/web3.js": "^1.98.4",
37
37
  "@solana/spl-token": "^0.4.14",